大数据系列(10) ClickHouse:OLAP查询快到飞起,秘诀是什么?
ClickHouse:OLAP 查询快到飞起,秘诀是什么?
大数据系列第 10 篇:海量数据做分析查询,Hive 跑 10 分钟,ClickHouse 跑 1 秒,差距在哪?
一个让人崩溃的场景
假设你是数据分析师,老板让你查一下:过去一个月,每天每个省份的订单总金额是多少?
数据存在 Hive 里,表有 10 亿条记录。你写了条 SQL:
SELECTdt,province,SUM(amount)astotal_amountFROMordersWHEREdt>='2024-01-01'GROUPBYdt,province;你点了执行,然后去泡了杯咖啡,刷了半天手机,回来一看——还在跑。
10 分钟后,结果终于出来了。老板在旁边等得不耐烦:“怎么这么久?”
你解释说:“数据量太大了,Hive 跑 MapReduce 就是慢……”
老板:“那能不能快点?”
这时候,ClickHouse 就可以出场了。
同样的查询,同样的数据量,ClickHouse 可能1 秒就返回了。
不是 10 分钟,是 1 秒。1000 倍的差距。
ClickHouse 是什么?
ClickHouse 是俄罗斯搜索引擎 Yandex 开源的列式数据库,专门为 OLAP(联机分析处理)场景设计。
OLAP 是什么?简单说就是"分析型查询":
- 扫描大量数据(几百万、几亿条)
- 聚合计算(SUM、COUNT、AVG、GROUP BY)
- 返回汇总结果,而不是单条记录
ClickHouse 的定位很清楚:我不做事务、不做随机更新,我就做一件事——海量数据的分析查询,而且要快。
为什么 ClickHouse 这么快?
ClickHouse 快不是某一个优化点,而是一整套为分析查询量身定做的设计。咱们一个个聊:
1. 列式存储:只读需要的列
传统数据库(MySQL、PostgreSQL)是行式存储的:
行式存储(MySQL): Row 1: [id=1, name=张三, age=25, city=北京, amount=100] Row 2: [id=2, name=李四, age=30, city=上海, amount=200] Row 3: [id=3, name=王五, age=28, city=广州, amount=150] 存储在磁盘上: [1,张三,25,北京,100][2,李四,30,上海,200][3,王五,28,广州,150] 查询 SELECT SUM(amount) 时: → 需要读整行数据,再提取 amount 列 → 读了大量不需要的列(id, name, age, city)ClickHouse 是列式存储的:
列式存储(ClickHouse): id 列: [1, 2, 3] name 列: [张三, 李四, 王五] age 列: [25, 30, 28] city 列: [北京, 上海, 广州] amount 列:[100, 200, 150] 存储在磁盘上: [1,2,3][张三,李四,王五][25,30,28][北京,上海,广州][100,200,150] 查询 SELECT SUM(amount) 时: → 只读 amount 列的数据 → 其他列完全不碰 → 磁盘 I/O 大幅减少分析查询通常只涉及少数几列,列式存储能大幅减少磁盘读取量。
2. 向量化执行:一次算一批数据
传统数据库的执行方式是"一次处理一行":
传统执行(逐行处理): for each row in table: value = row.amount sum += value → 每行都要做函数调用、条件判断 → CPU 大量时间花在控制逻辑上,而不是实际计算ClickHouse 采用向量化执行(Vectorized Execution):
向量化执行(批量处理): batch = [100, 200, 150, 300, 250, ...] // 一次读 1000 个值 sum = SIMD_SUM(batch) // 用 SIMD 指令一次算一批 → 减少函数调用开销 → 利用 CPU 的 SIMD 指令并行计算 → CPU 缓存命中率高(数据连续存放)向量化执行 + 列式存储 = CPU 和内存的高效利用。
3. 数据压缩:存储少、读得少
列式存储的数据类型相同,压缩率极高:
amount 列的数据:[100, 200, 150, 100, 200, 150, 100, 200] 行式存储压缩:类型混杂,压缩率低(可能 2-3 倍) 列式存储压缩:类型相同,压缩率高(可能 10 倍以上) ClickHouse 支持多种压缩算法: • LZ4:压缩/解压速度快,默认推荐 • ZSTD:压缩率更高,但速度稍慢压缩率高意味着:存得少、读得少、网络传得少。
4. MergeTree 引擎:为分析查询优化的存储结构
ClickHouse 有多种表引擎,最常用的是MergeTree家族:
-- 创建一个 MergeTree 表CREATETABLEorders(dtDate,user_id UInt64,province String,amountDecimal(10,2))ENGINE=MergeTree()PARTITIONBYtoYYYYMM(dt)-- 按月分区ORDERBY(province,dt)-- 排序键PRIMARYKEYprovince;-- 主键(稀疏索引)MergeTree 的关键设计:
分区(Partition):数据按分区键分成不同的目录,查询时只读相关分区。
数据目录结构: /data/orders/ ├── 202401_1_1_0/ ← 2024 年 1 月的数据 ├── 202401_2_2_0/ ├── 202402_3_3_0/ ← 2024 年 2 月的数据 ├── 202402_4_4_0/ └── ... 查询 WHERE dt = '2024-01-15' 时: → 只读 202401 相关的分区目录 → 其他月份的数据完全跳过排序键(Order By):数据按排序键有序存储,相同值聚在一起。
ORDER BY (province, dt) 意味着: 数据在磁盘上的顺序: [北京, 2024-01-01, ...] [北京, 2024-01-01, ...] [北京, 2024-01-02, ...] ... [上海, 2024-01-01, ...] [上海, 2024-01-01, ...] ... GROUP BY province 时: → 相同 province 的数据连续存放 → 扫描效率极高稀疏索引(Primary Key):不是每行都建索引,而是按粒度(默认 8192 行)采样建索引。
数据:1000 万行 索引粒度:8192 行 索引条目:1000万 / 8192 ≈ 1220 条 查询时: 1. 先查稀疏索引,定位到可能包含目标数据的数据块 2. 只读这些数据块,跳过其他数据 索引很小,可以全部加载到内存 查询时索引查找几乎不耗时间5. 并行处理:多核 CPU 充分利用
ClickHouse 会充分利用多核 CPU:
- 一个查询自动并行到多个 CPU 核心
- 多个查询之间也能并行执行
- 单机就能跑出很高的 QPS
ClickHouse 的表引擎家族
MergeTree 不是只有一个,而是一个家族:
| 引擎 | 特点 | 适用场景 |
|---|---|---|
| MergeTree | 基础引擎,支持分区、排序、索引 | 大多数分析场景 |
| ReplacingMergeTree | 自动去重,保留最新版本 | 需要最终一致的去重场景 |
| SummingMergeTree | 自动聚合数值列 | 预聚合场景,如统计报表 |
| AggregatingMergeTree | 自动聚合聚合函数状态 | 需要增量聚合的场景 |
| CollapsingMergeTree | 通过"折叠"实现更新删除 | 需要更新/删除的历史数据 |
| VersionedCollapsingMergeTree | 带版本号的折叠 | 更精确的更新控制 |
-- SummingMergeTree 示例:自动按排序键聚合CREATETABLEdaily_stats(dtDate,province String,pv UInt64,uv UInt64)ENGINE=SummingMergeTree()PARTITIONBYtoYYYYMM(dt)ORDERBY(dt,province);-- 插入多条同一天同一省份的数据INSERTINTOdaily_statsVALUES('2024-01-01','北京',100,50);INSERTINTOdaily_statsVALUES('2024-01-01','北京',200,80);-- 查询时自动聚合为:('2024-01-01', '北京', 300, 130)ClickHouse 的坑:这些要注意
坑 1:不支持事务
ClickHouse 不是 OLTP 数据库,不支持 ACID 事务。不要用它做:
- 银行转账
- 订单状态更新
- 需要强一致性的业务操作
坑 2:不支持高频更新删除
ClickHouse 设计为"追加写",更新和删除是"异步且昂贵"的操作(通过 Mutation 实现)。
如果需要频繁更新,ClickHouse 不是好选择。
坑 3:JOIN 性能一般
ClickHouse 的 JOIN 性能不如单表查询。大表 JOIN 大表可能会很慢。
解决方案:
- 尽量预聚合,避免运行时 JOIN
- 用小表 JOIN 大表(把小表放右边)
- 使用 Dictionary 功能把维表加载到内存
坑 4:分布式表需要手动维护
ClickHouse 的分布式表(Distributed Engine)需要手动配置分片规则,不像 Elasticsearch 那样自动分片。
ClickHouse vs Doris vs StarRocks:OLAP 三剑客怎么选?
| 维度 | ClickHouse | Apache Doris | StarRocks |
|---|---|---|---|
| 出身 | Yandex(俄罗斯) | 百度开源 | 鼎石开源 |
| 架构 | 存算一体 | 存算一体 | 存算一体 |
| SQL 兼容 | 较好 | 很好(MySQL 协议) | 很好(MySQL 协议) |
| JOIN 性能 | 一般 | 好 | 很好 |
| 实时写入 | 好 | 很好 | 很好 |
| 更新删除 | 弱(异步 Mutation) | 支持(Unique Key 模型) | 支持(Primary Key 模型) |
| 物化视图 | 支持 | 支持 | 支持 |
| 生态 | 较成熟 | 国内活跃 | 国内活跃 |
选型建议:
- 极致单表查询性能、日志分析→ClickHouse
- 需要复杂 JOIN、实时更新、统一 OLAP→Doris 或 StarRocks
- 国内生态、社区支持→Doris/StarRocks更友好
小结
今天咱们聊了 ClickHouse:
- 定位:列式数据库,专门为 OLAP 分析查询设计
- 快的秘诀:
- 列式存储(只读需要的列)
- 向量化执行(SIMD 批量计算)
- 高压缩率(类型相同,压缩效果好)
- MergeTree 引擎(分区+排序+稀疏索引)
- 并行处理(充分利用多核 CPU)
- 表引擎家族:MergeTree、Replacing、Summing、Aggregating 等
- 坑:不支持事务、不支持高频更新、JOIN 性能一般
- 与 Doris/StarRocks 对比:ClickHouse 单表查询最强,Doris/StarRocks JOIN 和实时更新更好
ClickHouse 的价值在于:它证明了为特定场景专门设计的系统,可以比通用系统快几个数量级。如果你的场景是"海量数据、分析查询、追加写为主",ClickHouse 几乎是无可替代的选择。
你们公司在用 ClickHouse 吗?主要是什么场景?查询速度提升有多大?欢迎聊聊~
