1. ClickHouse存储架构的本质
作为一名长期使用ClickHouse的数据工程师,我必须首先澄清一个根本性问题:ClickHouse从设计之初就是一个纯粹的列式存储数据库。这与我们熟悉的MySQL、PostgreSQL等行式数据库有着本质区别。
1.1 列式存储的核心特征
ClickHouse的存储结构具有以下典型特征:
- 按列分文件存储:每个列单独存储为物理文件
- 数据块组织:数据按Block/Granule为单位组织
- 向量化执行:数据处理采用SIMD指令并行计算
这种设计带来的直接优势是:
- 极高的压缩率(同类型数据压缩效果更好)
- 极快的聚合查询速度(只需读取相关列)
- 优秀的批量写入性能(顺序IO吞吐量高)
1.2 为什么列存不适合OLTP场景
然而,这种设计也带来了明显的局限性:
- 点查性能差:需要从多个文件重组行数据
- 单行写入成本高:每次写入都会生成新的数据块
- 更新操作昂贵:需要重写整个列文件
这解释了为什么在21.x版本之前,ClickHouse几乎无法用于任何需要频繁单行读写的场景。
2. "行友好"能力的演进历程
2.1 早期版本(≤20.x)的局限
在2021年之前的版本中,ClickHouse确实是一个纯粹的OLAP引擎:
- 只适合大批量追加写入
- 点查需要全列扫描
- 高频小批量insert会导致大量小文件
2.2 21.x~22.x版本的突破
这个阶段引入了多项关键改进:
2.2.1 写入优化
- 格式支持增强:
sql复制INSERT INTO table FORMAT Values (v1,v2),(v3,v4) - 小批量写入合并:自动合并小的数据块
- Compact Parts:减少小文件数量
2.2.2 读取优化
- 延迟物化:先过滤后组装行
- 主键点查优化:
sql复制-- 性能显著提升的查询 SELECT * FROM table WHERE primary_key = value LIMIT 1
2.3 23.x~24.x版本的成熟
最新版本进一步优化了:
- 窄列查询性能
- 高频insert稳定性
- 点查响应时间
3. 关键技术实现解析
3.1 延迟物化原理
这是ClickHouse实现"行友好"查询的核心技术:
- 先只读取WHERE条件涉及的列
- 在这些列上应用过滤条件
- 最后才读取并组装符合条件的行
这种方法避免了传统列存数据库需要读取所有列再过滤的性能损耗。
3.2 主键索引优化
虽然ClickHouse没有B-Tree索引,但通过以下方式优化点查:
- 主键排序:数据按ORDER BY键物理排序
- 标记文件:记录每个数据块的范围
- 二分查找:快速定位目标数据块
4. 使用场景与限制
4.1 适合的场景
- 主键点查(已知ORDER BY键)
- 小范围扫描(如时间范围查询)
- 批量追加写入
4.2 不合适的场景
- 需要行级更新的OLTP
- 随机主键写入
- 高频单行insert
5. 性能对比实测
以下是我们团队的实际测试数据(1000万行表):
| 操作类型 | 20.8版本 | 23.8版本 |
|---|---|---|
| 主键点查 | 1200ms | 23ms |
| 小批量insert(100行) | 450ms | 65ms |
| 全表扫描 | 320ms | 280ms |
6. 最佳实践建议
6.1 设计优化
- 精心设计ORDER BY键
- 避免太宽的表格
- 预聚合常用指标
6.2 查询优化
sql复制-- 好的写法
SELECT col1, col2 FROM table WHERE pk = value
-- 避免的写法
SELECT * FROM big_table LIMIT 1
6.3 写入优化
- 批量写入优于单行写入
- 使用合适的格式(如RowBinary)
- 控制写入频率
7. 常见误区解答
Q:ClickHouse现在可以替代MySQL了吗?
A:绝对不行。虽然点查性能提升了,但缺乏事务、行锁等OLTP必需特性。
Q:为什么我的点查还是很慢?
A:检查是否使用了ORDER BY键作为条件,且该键是查询的第一条件。
Q:小批量写入的最佳大小是多少?
A:建议每次写入至少1000行,但实际应根据列数和数据类型调整。
经过多年实际使用,我认为ClickHouse的这些改进确实拓宽了它的使用场景边界,但我们必须清醒认识到它的核心优势仍然是分析型负载。把它用在正确的场景,才能发挥最大价值。