存储层:并发插入彼此隔离
存储层:并发插入与 SELECT 查询彼此隔离
存储层:合并时计算
- Replacing merges:仅保留输入 parts 中某一行的最新版本,并丢弃该行的其他所有版本。Replacing merges 可以看作一种在合并时进行的清理操作。
- Aggregating merges:将输入 part 中间的聚合状态合并为新的聚合状态。虽然这听起来不太直观,但它本质上实现的只是增量聚合。
- TTL (生存时间 (TTL)) merges:根据特定的时间规则,对行进行压缩、移动或删除。
存储层:数据剪枝
- 主键索引,用于定义表数据的排序顺序。选择得当的主键可以通过快速二分查找来判断过滤条件 (例如上述查询中的 WHERE 子句) ,而不必进行全列扫描。更技术一点地说,扫描的运行时间相对于数据规模是对数级,而不是线性级。
- 表投影,即表的另一种内部版本,存储相同的数据,但按不同的主键排序。当存在多个高频过滤条件时,投影会很有用。
- 跳过索引,它会在列中嵌入额外的数据统计信息,例如列的最小值和最大值、唯一值集合等。跳过索引与主键和表投影相互独立,并且根据列中的数据分布,它们可以显著加快过滤条件的评估。
存储层:数据压缩
先进的查询处理层
对细节的极致关注
“ClickHouse 简直是个异类系统——你们竟然做了 20 种版本的哈希表。你们有这么多了不起的设计,而大多数系统通常只有一种哈希表 … ClickHouse 之所以能有如此惊人的性能,正是因为它拥有所有这些专门化的组件” Andy Pavlo,CMU 数据库教授ClickHouse 之所以能 脱颖而出,关键就在于它对底层优化的极致打磨。做出一个“能用”的数据库是一回事,但要把它工程化到能在各种查询类型、数据结构、数据分布和索引配置下都保持高速,才真正体现出这种“异类系统”般的匠心。 哈希表。 以哈希表为例。哈希表是 join 和 aggregation 所依赖的核心数据结构。作为程序员,需要考虑以下设计决策:
- 选择哪种哈希函数,
- 如何解决冲突:开放寻址 还是 链地址法,
- 内存布局:键和值是放在同一个数组中,还是分别放在不同数组中?
- 填充因子:何时扩容、如何扩容?扩容时如何迁移值?
- 删除:哈希表是否应该允许删除条目?
- 要排序的是什么:数字、元组、字符串,还是结构体?
- 数据是否在 RAM 中?
- 是否要求排序是稳定的?
- 是必须对全部数据排序,还是部分排序就足够?