前言
前阵子我在公司的项目中调研以及应用了 ClickHouse,出于它在海量数据场景下的极致性能表现,也是因为我们的项目面向海量数据高速存储分析场景:系统一次性处理的底层生成数据量级可达 TB、千亿级别,并要求提供秒级的查询响应,而且大多数是单表检索、不涉及多表 JOIN 的复杂查询,需求恰好与 ClickHouse 的专长匹配。通过这次项目实践,同时结合我的调研、学习结果,我总结了 ClickHouse 作为在线分析高性能数据库的几个关键优势。
列式存储
列式存储,意味着数据在物理空间中是同一列存放在一起,和行式存储同一行数据连续存放不同。这种存储方式有利 于大数据分析场景,原因是查询效率高:在只需要检索个别列的时候,避免扫描整行数据,大大减少了 IO。
CH 每一列数据单独存储在 bin 文件中,按ORDER BY指定的键排序。ORDER BY的键可以支持多个。假设有三列排序键(A,B,C),数据在 part 内部物理存储顺序(按行颗粒):

排序的原则是,先按 A 排序,若 A 相同,则按 B 排序;若 A、B 相同,则按 C 排序。对于非排序键的列,数据同样按此规则排序,只不过是独立存储在自己的 bin 文件中。
按过滤条件检索的时候,为了使排序键生效,需满足最左匹配原则(和关系型数据库中联合索引的命中规则相同)。例如,对于排序键 ORDER BY (A, B, C):
-
✅ 有效:
WHERE A=1 AND B>10→ 可以用排序键跳过大量颗粒(granule) -
✅ 有效:
WHERE A>5→ 只用到第一列 -
❌ 无效:
WHERE B>10(没有 A)→ 无法用主键索引,只能全颗粒扫描 -
❌ 无效:
WHERE A=1 AND C>20(跳过 B)→ A 可以过滤,C 无法借助主键索引过滤
压缩
列式存储下,一列同类型的值连续存储,有利于压缩。ClickHouse 内置了多种数据压缩算法;对于一些数值具备规律的列,可以显式声明设置特定算法,以达到更高的压缩效率。如果是基数庞大但是取值较少的列,使用LowCardinality类型。它是 ClickHouse 用于优化低基数(重复值多)列的核心手段,本质是字典编码。原理是用一个字典结构存储值与编码的映射关系。例如国家编码列,值有 us,ch,en,aps,映射到字典就只有 1,2,3,4 这样的字典 key,存储空间大幅减少。
-
LZ4,CH 默认的压缩算法,寻找数据流中的重复片段实现压缩,特点是解压缩速度快,但压缩比不如 ZSTD。
-
ZSTD 压缩比更高,但是相对地,解压缩速度比 LZ4 慢。
-
DoubleDelta 算法,二阶差分编码:先计算相邻数值差值,再对差值再次求差,适合均匀递增的时间戳类时序数据。
-
T64,为整数设计,切除高位为 0 的部分,只保留有效 bit,以此大幅减少数据占用空间。
索引
ClickHouse 的索引是稀疏索引。它是一个有序的存储结构,可以通过二分查找定位到具体数据位置,有利于范围查找。每一个索引条目对应由 8192 行构成的块。这样有效地减少了索引的存储开销和检索索引的耗时。一个表存储为多个 part 目录,每个目录下有索引文件、各列标记文件和各列二进制文件。查找时在索引文件中通过二分查找到颗粒序号。这个颗粒序号就是一个下标,再到指定的列标记文件中通过这个下标查找对应 bin 文件中的偏移量,然后到二进制文件中读取数据、解压。
part_xxx/
├── primary.idx # 主键稀疏索引
├── id.bin # id列原始压缩数据
├── id.mrk # id列标记文件
├── byte_index.bin
├── byte_index.mrk
├── data_id.bin
├── data_id.mrk
└── checksums.txt
查询过程:
-
【分区裁剪】(如果有
PARTITION BY,最先执行,淘汰整个分区) -
【Part 粗过滤】遍历所有剩余 Part,比较 min-sort-key/max-sort-key,淘汰无关 Part
-
【每个保留 Part 内部:primary.idx 数组二分】筛选颗粒编号列表
-
【mrk 标记文件:颗粒编号直接数组下标 O (1) 寻址】获取 bin 偏移
-
【读取.bin 压缩块】解压、行过滤,返回数据
向量化计算
列式存储使得从磁盘读取数据时可以批量读取,在计算的时候也以多个数据为单位,使用 SIMD(Single Instruction, Multiple Data,单指令多数据),一条指令计算多个值。因为减少了大量逐个循环导致的分支跳转、条件判断,充分利用硬件特性与能力,所以性能提升更加明显。
并行执行
从单节点上看,ClickHouse 使用多线程执行查询和写入操作,发挥 CPU 多核优势。查询和插入分别由max_threads和max_insert_threads限制最大并行度。并行度默认会设置为物理核数的两倍。
从集群上看,ClickHouse 使用 MPP 架构,但只针对分布式(Distributed)表:写入数据时会根据分片键计算路由,将数据写入不同节点;查询的时候也根据分片键路由到指定节点执行,结果再汇总到客户端。
基于以上几个优势,CH 才能在 OLAP 领域中脱颖而出,成为各大厂最钟爱的即席查询引擎之一!