ClickHouse 列式存储与海量数据实时 OLAP 实战

ClickHouse 列式存储与海量数据实时 OLAP 实战
神经蛙MySQL 扛住了交易订单的实时写入,却在” yesterday 全站 GMV 按省份聚合”这种分析查询里反复超时。ClickHouse 就是为这种”海量数据、复杂聚合、秒级返回”场景而生的列式 OLAP 数据库。本文把 ClickHouse 最容易被问、最容易踩坑的原理与设计讲清楚。
一、为什么需要 ClickHouse:OLTP 与 OLAP 的冲突
MySQL / PostgreSQL 擅长的是 OLTP(联机事务处理),行存、强事务、点查写入快。BI / 监控 / 埋点分析需要的是 OLAP(联机分析处理),列存、高吞吐扫描、聚合快。两者设计目标不同,硬用 MySQL 做分析会遇到三大瓶颈:
| 维度 | OLTP(MySQL) | OLAP(ClickHouse) |
|---|---|---|
| 数据组织 | 行存储,一次读一整行 | 列存储,只读需要的列 |
| 写入模型 | 面向单行增删改,事务强一致 | 面向批量追加,最终一致 |
| 典型查询 | SELECT * WHERE id = ? |
SELECT province, sum(amount) FROM ... GROUP BY province |
| 索引结构 | B+Tree,适合点查 | 稀疏索引 + 排序键,适合范围/聚合 |
| 扩展方式 | 主从读写分离 | 分片 + 副本,水平扩展 |
二、核心概念速览
ClickHouse 的名词和 MySQL 不一样,先把概念对齐:
| 概念 | 含义 | 类比 |
|---|---|---|
| MergeTree | 最常用表引擎,支持排序、分区、副本 | InnoDB 之于 MySQL |
| 列族 / Column | 数据按列独立压缩存储 | CSV 的每一列 |
| 排序键(Order Key) | 定义数据在磁盘上的物理顺序 | 聚簇索引 |
| 主键(Primary Key) | 通常与排序键一致,用于稀疏索引 | 逻辑主键 |
| 分区键(Partition Key) | 按时间/维度把数据切到不同目录 | MySQL 分区表 |
| Granule | 最小读取单元,默认 8192 行 | 页 / Page |
| Part | 一次写入产生一个不可变数据块 | LSM-Tree 的 SSTable |
| Merge | 后台把多个 Part 合并成更大 Part | LSM 的 Compaction |
注意:ClickHouse 的
PRIMARY KEY不保证唯一,它只是声明排序键并构建稀疏索引。真正的去重要用ReplacingMergeTree或在写入前完成。
三、列式存储:为什么聚合查询这么快
列式存储是 ClickHouse 快的根本原因。假设一条订单记录有 50 个字段,分析查询通常只关心 order_amount、province、dt 三个字段:
1 | 行存读取:把整行 50 个字段全部读到内存,再丢弃 47 个 |
| 优势 | 列式存储的表现 |
|---|---|
| I/O 裁剪 | 只读查询涉及的列,磁盘 I/O 与字段数成正比 |
| 压缩比高 | 同一列数据类型相同、重复值多,字典 / RLE 压缩效果显著 |
| 向量化执行 | 整批数据在 CPU SIMD 指令里流水线处理,减少分支跳转 |
| 数据局部性 | 同列数据物理连续,CPU Cache 命中率高 |
四、MergeTree 表引擎:数据的物理组织
MergeTree 是 ClickHouse 的默认与核心引擎。建表示例:
1 | CREATE TABLE order_events |
关键设计:
- PARTITION BY:按月份把数据拆到不同目录,删除旧分区时只需删目录,极快。
- ORDER BY:决定 Part 内部物理顺序,是稀疏索引的依据,也是查询剪枝的关键。
- 不可变 Part:每次批量写入产生一个 Part,后续由后台线程合并,写入性能极高。
- 稀疏索引:每 8192 行(一个 Granule)记录一次边界值,索引体积小,适合顺序扫描。
排序键一旦确定就很难修改。设计原则是:把最常出现在 WHERE、JOIN、GROUP BY 中的列放在前面,让稀疏索引和压缩都能发挥最大作用。
五、分区与排序键:查询剪枝的艺术
同样的数据,不同的排序键,查询性能可能差几十倍。优秀的设计遵循:
| 设计原则 | 说明 |
|---|---|
| 高频过滤列放排序键前面 | 如 WHERE province = 'Zhejiang', province 应靠前 |
| 时间列放分区键 | toYYYYMMDD(dt) 或 toYYYYMM(dt),方便 TTL 和按天删除 |
| 基数低的列用 LowCardinality | 如 province、channel,显著降低内存和磁盘占用 |
| 大基数列只做最后排序 | 如 user_id、order_id,用于保证 Part 内有序即可 |
1 | -- 常见埋点日志表设计 |
这样 WHERE app_id = 'xxx' AND event_type = 'pay' AND dt >= '2026-10-01' 能同时命中分区剪枝和索引剪枝。
六、向量化执行与 SQL 解析
ClickHouse 的执行引擎是向量化(Vectorized)的:数据以块(Block)为单位处理,每列用紧凑数组存储,函数调用批量作用在数组上。相比传统的”一次处理一行”的火山模型,向量化大幅减少虚函数调用和缓存失效。
1 | SELECT |
对于这条查询,ClickHouse 只会读取 dt、province、amount 三列,按 Granule 批量过滤、分组、聚合,最后在节点间做二次聚合。
七、物化视图与投影:空间换时间
物化视图(Materialized View)是 ClickHouse 里的”预聚合”利器。你可以为原始明细表建立一张汇总表,写入明细时自动把聚合结果同步过去:
1 | -- 1. 创建目标预聚合表 |
查询时直接查 order_events_by_province 就能得到 province 维度的聚合结果,避免扫描明细。
八、MergeTree 家族与典型引擎
MergeTree 不是单一引擎,而是一个家族。根据业务场景选引擎,能少走很多弯路:
| 引擎 | 特点 | 典型场景 |
|---|---|---|
| MergeTree | 基础引擎,支持排序、分区、副本 | 绝大多数分析表 |
| ReplacingMergeTree | 按排序键去重,保留版本最新 | 需要幂等写入的表 |
| SummingMergeTree | 对数值列自动求和合并 | 预聚合指标表 |
| AggregatingMergeTree | 对聚合函数状态合并 | 复杂预聚合(UV、分位数) |
| CollapsingMergeTree | 用 Sign 字段做”行级撤销” | CDC / 状态变更场景 |
| VersionedCollapsingMergeTree | 带版本号的 Collapsing | 需要版本控制的 CDC |
| Log 引擎 | 轻量、无索引、追加写入 | 临时日志、测试 |
九、ClickHouse 与 Elasticsearch / Doris / StarRocks 怎么选
| 维度 | ClickHouse | Elasticsearch | Doris / StarRocks |
|---|---|---|---|
| 定位 | 列式 OLAP | 搜索引擎 | 实时数仓 / 湖仓 |
| 写入模式 | 批量追加最优 | 近实时索引 | 近实时导入 |
| 查询强项 | 宽表聚合、Ad-hoc | 全文检索、日志搜索 | 多表关联、复杂数仓 |
| 多表 Join | 弱(推荐大宽表) | 不支持复杂 Join | 强,支持分布式 Join |
| 更新删除 | 弱(异步 Merge) | 实时 | 支持 Upsert |
| 生态 | 监控 / 埋点领域成熟 | ELK 栈 | 国内实时数仓首选 |
一句话:日志检索选 Elasticsearch,复杂数仓建模选 Doris/StarRocks,宽表聚合和高吞吐分析选 ClickHouse。
十、生产避坑清单
| 坑 | 现象 | 正确姿势 |
|---|---|---|
| 单表不分区 | 删历史数据慢、查询扫描全表 | 按 toYYYYMMDD(dt) 分区 |
| 排序键乱写 | 查询命中不了索引,全表扫描 | 高频过滤列放前面 |
| 频繁小批量写 | Part 爆炸,Merge 跟不上 | 攒批写入,每秒至少几千条 |
| 用行存思维更新 | ALTER UPDATE 极慢 |
用 ReplacingMergeTree / 重分区写新表 |
| 不做 TTL | 磁盘无限增长 | TTL dt + INTERVAL 90 DAY |
| 超大 Join | 内存溢出或极慢 | 提前打成大宽表,或换 StarRocks |
| 单节点 | 磁盘/CPU 瓶颈 | 用分片 + 副本搭建集群 |
| 不监控 Merge | 后台负载不可见 | 盯 system.parts、system.merges |
总结
ClickHouse 的设计哲学很清晰:用列式存储减少 I/O,用稀疏索引和分区做剪枝,用向量化执行压榨 CPU,用不可变 Part 换取极致写入吞吐。它不是万能数据库,但在”大宽表 + 海量数据 + 复杂聚合”的分析场景里几乎没有对手。掌握好排序键、分区键、MergeTree 家族与物化视图这四条主线,再结合生产避坑清单,就能把 ClickHouse 稳定地跑在监控、埋点、BI 数仓的第一线。
下一篇可以沿着”ClickHouse 集群分片与副本部署”或”ClickHouse 接入 Kafka/Flink 实时数仓”继续深入——把”算”和”存”真正打通,实时分析才算落地。

















