作为 OLAP 领域的"性能怪兽",ClickHouse 近年来在实时数仓、用户行为分析、日志分析等场景大放异彩。本文带你从原理到实践,搞懂 ClickHouse 的核心设计。
一、ClickHouse 是什么?
ClickHouse 是由俄罗斯 Yandex 开源的列式存储分析型数据库(OLAP),专为"对海量数据进行聚合分析"而生。它的典型场景是:
- 用户行为分析(埋点数据、漏斗分析)
- 实时数仓与报表
- 日志/监控数据分析
- BI 即席查询
一句话总结它的定位:读得极快、写吞吐极高,但不擅长频繁的更新和事务。
二、为什么这么快?核心原理拆解
1. 列式存储:只读需要的列
传统行式存储(如 MySQL)读取一行数据时会把整行所有字段都加载进内存;而 ClickHouse 按列存储:
ini
行式存储: [id, name, age, city][id, name, age, city][...]
列式存储: [id1, id2, id3...][name1, name2, name3...][age1, age2...]
分析查询通常只涉及少数几列,列式存储可以:
- 减少 IO:只读取查询需要的列
- 提升压缩率:同类型数据连续存储,压缩率比行存高 5-10 倍
2. 数据压缩:CPU 换 IO
ClickHouse 支持 LZ4、ZSTD 等压缩算法。由于分析场景 IO 是瓶颈,它选择"用 CPU 解压来减少磁盘读取",这是一种非常划算的取舍。
3. 向量化执行(Vectorized Execution)
ClickHouse 基于 CPU 的 SIMD 指令集,一次处理一批数据(block)而不是单行处理,大幅提升 CPU 缓存命中率和计算吞吐。
4. 稀疏索引:MergeTree 家族
ClickHouse 的核心引擎是 MergeTree:
- 数据按排序键写入,生成多个分区(part)
- 每个 part 默认每 8192 行生成一个稀疏索引(只记录每列块的聚合信息)
- 后台线程不断将小 part 合并(Merge)成大 part
此外针对特殊场景还有:
| 引擎 | 适用场景 |
|---|---|
| ReplacingMergeTree | 需要去重的数据(如订单状态) |
| AggregatingMergeTree | 预聚合 |
| CollapsingMergeTree | 状态折叠(记录最新状态) |
| ReplicatedMergeTree | 副本复制(高可用) |
| Distributed | 分布式表(集群路由) |
5. 并行计算最大化利用硬件
ClickHouse 会尽可能地把查询拆解到所有 CPU 核心上并行执行,单机就能吃满硬件资源。
三、快速上手:Docker 一条命令体验
bash
docker run -d --name clickhouse \
-p 8123:8123 -p 9000:9000 \
--ulimit nofile=262144:262144 \
clickhouse/clickhouse-server
连接方式:
- HTTP 接口 :
curl 'http://localhost:8123/' --data 'SELECT version()' - 客户端 :
clickhouse-client - JDBC/Go/Python SDK:官方均有支持
四、建表示例与核心概念
sql
CREATE TABLE user_events
(
event_time DateTime,
user_id UInt64,
event_type String,
page String,
duration UInt32
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time) -- 按月分区
ORDER BY (event_type, user_id, event_time) -- 排序键(稀疏索引依据)
TTL event_time + INTERVAL 90 DAY; -- 数据90天后自动过期
几个关键概念:
- 分区键(Partition Key) :数据物理隔离的粒度,常用时间字段。注意:分区数不要太多,上万分区是性能杀手。
- ORDER BY / 主键:MergeTree 的排序键与主键可以不同,主键用于稀疏索引,必须尽量把筛选过滤字段放到前面。
- TTL:自动清理过期数据,非常适合日志类场景。
五、一个性能直觉对比
假设 10 亿行埋点数据,查询"统计昨天各页面的平均停留时长":
sql
SELECT page, avg(duration)
FROM user_events
WHERE event_time >= yesterday() AND event_time < today()
GROUP BY page;
在合适的表结构上,ClickHouse 通常在几百毫秒到几秒内返回结果,这是 MySQL 等传统 OLTP 数据库难以企及的。
六、使用 ClickHouse 的常见坑
1. 不要把它当 OLTP 用 不支持事务、不擅长单行 UPDATE/DELETE(都是 mutation 操作,开销大)。频繁更新的场景应该用 Doris、StarRocks 或依赖 AggregatingMergeTree 做预聚合。
2. 小批量高频写入是大忌 ClickHouse 擅长批量写入,推荐攒批(几千到几万行)一次写入。高频小批量写入会产生大量小 part,拖垮合并线程。
3. JOIN 能力较弱 ClickHouse 的 JOIN 性能一般,尤其是大表 JOIN。实践中常见做法:
- 用 字典(Dictionary) 把维度表加载进内存
- 用大宽表替代 JOIN
- 合理设置
join_algorithm和max_memory_usage
4. 副本与分片设计 生产环境用 ReplicatedMergeTree 保证副本高可用,用 Distributed 表做分片路由。但需要注意 Distributed 表的写入是异步转发的,要监控 part 合并状态,避免写入放大。
七、选型建议
- 数据量大(亿级以上)、查询以聚合分析为主 → ClickHouse 非常合适
- 需要频繁的实时更新、或复杂的星型模型多表 JOIN → 可以对比 Doris / StarRocks
- 中小数据量 + 简单报表 → PostgreSQL + 物化视图可能就够了
写在最后
ClickHouse 的设计哲学非常清晰:为分析查询牺牲一切。它用列存、压缩、向量化、稀疏索引这套组合拳,把聚合查询性能推到了极致。理解它的"快从哪来",也就理解了它的"坑从哪来"------把更新当写入、把分区当索引,是所有新手最容易踩的坑。
如果你有海量数据分析的需求,不妨用 Docker 起一个实例,导入一亿行测试数据感受下什么叫"秒级响应"------那种感觉,一旦体验过就回不去了。
互动时间:你在生产中使用过 ClickHouse 吗?踩过哪些坑?欢迎在评论区交流 👇