文章目录
-
- [一、开篇:行存 vs 列存,一个例子看懂](#一、开篇:行存 vs 列存,一个例子看懂)
- [二、ClickHouse 列式存储的六大优点](#二、ClickHouse 列式存储的六大优点)
-
- [2.1 优点一:I/O 大幅降低](#2.1 优点一:I/O 大幅降低)
- [2.2 优点二:高压缩比,存储成本低](#2.2 优点二:高压缩比,存储成本低)
- [2.3 优点三:缓存命中率高](#2.3 优点三:缓存命中率高)
- [2.4 优点四:压缩算法灵活](#2.4 优点四:压缩算法灵活)
- [2.5 优点五:向量化执行](#2.5 优点五:向量化执行)
- [2.6 优点六:COUNT(*) 优化](#2.6 优点六:COUNT(*) 优化)
- [三、ClickHouse 列式存储的八大缺点](#三、ClickHouse 列式存储的八大缺点)
-
- [3.1 缺点一:不支持事务(最致命)](#3.1 缺点一:不支持事务(最致命))
- [3.2 缺点二:更新/删除成本极高](#3.2 缺点二:更新/删除成本极高)
- [3.3 缺点三:无二级索引(非主键查询慢)](#3.3 缺点三:无二级索引(非主键查询慢))
- [3.4 缺点四:点查性能弱,不适合高并发](#3.4 缺点四:点查性能弱,不适合高并发)
- [3.5 缺点五:Join 实现特殊,大表 Join 风险高](#3.5 缺点五:Join 实现特殊,大表 Join 风险高)
- [3.6 缺点六:窗口函数支持有限(已部分解决)](#3.6 缺点六:窗口函数支持有限(已部分解决))
- [3.7 缺点七:SQL 方言差异](#3.7 缺点七:SQL 方言差异)
- [3.8 缺点八:元数据管理需要人工干预](#3.8 缺点八:元数据管理需要人工干预)
- 四、优缺点对照总表
- [五、选型建议:什么时候选 ClickHouse?](#五、选型建议:什么时候选 ClickHouse?)
-
- [5.1 强烈推荐使用 ClickHouse](#5.1 强烈推荐使用 ClickHouse)
- [5.2 不建议使用 ClickHouse](#5.2 不建议使用 ClickHouse)
- 六、总结
在数据库的世界里,行式存储与列式存储之争已持续多年。ClickHouse 凭借列式存储,在 OLAP 领域独树一帜。但列式存储并非银弹------它在带来极致查询性能的同时,也伴随着事务缺失、更新困难等代价。本文将从原理到实战,系统梳理 ClickHouse 列式存储的优缺点,并给出清晰的选型建议。
一、开篇:行存 vs 列存,一个例子看懂
假设有一张用户行为表,有 100 列,1 亿行。你需要统计今天每个页面的访问量(PV)。
行式存储(如 MySQL):
- 数据按行连续存放:
[用户ID, 页面URL, 访问时间, 设备类型, IP, ...] - 执行
SELECT page_url, COUNT(*) ...时,必须读取出所有 100 列的数据 ,然后从中取出page_url - I/O 量 = 1 亿行 × 100 列 → 巨大
列式存储(如 ClickHouse):
- 数据按列分开存放:
page_url列单独存一块,访问时间列单独存一块 - 执行上述查询时,只读取
page_url这一列 - I/O 量 = 1 亿行 × 1 列 → 极小
结论 :列式存储的核心优势就是 "读多少列,取多少列"。
二、ClickHouse 列式存储的六大优点
2.1 优点一:I/O 大幅降低
| 场景 | 行存 I/O | 列存 I/O | 节省比例 |
|---|---|---|---|
| 100 列表,查 3 列 | 读 100 列 | 读 3 列 | 97% |
| 查所有列 | 读 100 列 | 读 100 列 | 0% |
COUNT(*) |
扫某列索引或全表 | 读最小列的元数据 | 99%+ |
实际案例 :某日志表 50 列,查询只取 event_time、user_id、event_type 3 列。行存每次扫描 50 列数据,列存只扫 3 列,I/O 减少 94%。
2.2 优点二:高压缩比,存储成本低
同一列的数据类型相同,相邻值往往相似,压缩效果极佳。
| 列类型 | 典型压缩算法 | 压缩比 | 说明 |
|---|---|---|---|
| 时间戳 | Delta + LZ4 | 10:1 ~ 50:1 | 前后差值极小 |
| 枚举/状态码 | 字典编码 | 100:1+ | 实际只存整数代号 |
| 数值序列 | Delta + ZSTD | 5:1 ~ 20:1 | 递增序列压缩极高 |
| 随机 UUID | 无法压缩 | 1:1 | 完全随机 |
实际案例 :某百亿级日志表,原始数据 100TB,ClickHouse 压缩后仅 12TB,压缩比超过 8:1。
2.3 优点三:缓存命中率高
压缩后的数据量小 → 同样大小的内存能缓存更多数据 → 重复查询命中内存,不落盘。
数据:
- 行存 :32GB 内存可缓存 32GB 原始数据(约 100%,因为行存基本不压缩)
- 列存 :32GB 内存可缓存压缩后数据,相当于 160GB~320GB 原始数据(压缩比 5:1~10:1)
2.4 优点四:压缩算法灵活
ClickHouse 允许为不同列选择不同的压缩算法:
sql
CREATE TABLE metrics (
event_time DateTime CODEC(Delta, ZSTD),
user_id UInt64 CODEC(ZSTD),
event_type LowCardinality(String) CODEC(ZSTD),
trace_id FixedString(32) CODEC(LZ4)
) ENGINE = MergeTree() ...
| 列类型 | 推荐算法 | 原因 |
|---|---|---|
| 时间序列 | Delta + ZSTD |
差值极小,压缩率极高 |
| 低基数字符串 | ZSTD |
字典自动压缩 |
| 随机值 | LZ4 |
速度快,压缩率有限 |
| 数值列 | T64 |
适合小整数范围 |
2.5 优点五:向量化执行
CPU 的 SIMD(单指令多数据流)指令可以一次性处理多个数据点。ClickHouse 的列式存储天然适配向量化:
cpp
// 伪代码:计算 sum(price)
// 行存:逐行累加(需要不断跳转)
for row in rows:
sum += row.price
// 列存:批量加载 256 个 price 到 CPU 寄存器,一次性累加
for i in 0..len(price_column)/256:
vector = load_256(price_column + i*256)
sum += sum_vector(vector)
效果 :列存下,SUM、AVG 等聚合函数的速度可比行存快 10-100 倍。
2.6 优点六:COUNT(*) 优化
ClickHouse 对于 COUNT(*) 可以只读最小列(如分区键)的元数据,完全不扫数据:
sql
-- 秒级返回,哪怕表有万亿行
SELECT COUNT(*) FROM giant_table;
三、ClickHouse 列式存储的八大缺点
3.1 缺点一:不支持事务(最致命)
ClickHouse 没有 ACID 事务,无法保证跨行、跨表的原子性操作。
不适用的场景:
- ❌ 银行转账(扣 A 账户 + 加 B 账户)
- ❌ 订单扣库存(防止超卖)
- ❌ 多表关联更新
替代方案:使用 PostgreSQL / MySQL 做事务主库,ClickHouse 做分析从库(通过 CDC 同步)。
3.2 缺点二:更新/删除成本极高
ClickHouse 不支持直接 UPDATE / DELETE,只能通过异步重写数据块实现:
sql
-- 这条 SQL 会重写所有包含 user_id=123 的数据块
ALTER TABLE table UPDATE status = 'inactive' WHERE user_id = 123;
影响:
- 大表更新可能耗时 数十秒甚至数分钟
- 不适合频繁更新场景
解决方案:
- 改用 追加 + 版本号 模式(查询时取最新版本)
- 使用 ReplacingMergeTree 自动去重
- 定期批量 ETL 而非实时更新
3.3 缺点三:无二级索引(非主键查询慢)
ClickHouse 只有稀疏主键索引(每 8192 行一个索引行),没有 B-Tree 或哈希索引。
| 查询类型 | 是否高效 | 原因 |
|---|---|---|
WHERE user_id = 123(user_id 是主键前缀) |
✅ 高效 | 主键二分查找 |
WHERE status = 'active'(status 不是主键) |
❌ 低效 | 全表扫描 |
WHERE page_url LIKE '%abc%' |
❌ 极低效 | 无倒排索引 |
解决方案:
- 将高频过滤列加入
ORDER BY(主键) - 使用跳数索引(minmax、bloom_filter)作为有限补偿
- 对低基数列,考虑使用
LowCardinality类型
3.4 缺点四:点查性能弱,不适合高并发
ClickHouse 的稀疏索引和列式存储设计,使其不适合 QPS > 5000 的点查场景。
| 场景 | ClickHouse | 推荐方案 |
|---|---|---|
SELECT * FROM table WHERE id = 123(QPS 5000+) |
⚠️ 勉强可用,但不如 MySQL | Redis / MySQL |
| 仪表盘实时刷新(每秒数十次) | ✅ 适合 | ClickHouse 可承载 |
| 用户请求级查询(每次请求都查) | ❌ 不适合 | 加缓存层(Redis) |
3.5 缺点五:Join 实现特殊,大表 Join 风险高
ClickHouse 默认使用 Hash Join:将右表完全加载到内存中,再扫描左表。
风险:
- 右表过大(> 内存限制) → OOM
- 多张大表 Join → 性能极差
解决方案:
- 右表使用
LowCardinality降低内存 - 将右表预先转化为字典(
dictGet) - 改用宽表代替 Join(数据冗余,查询快)
- 使用
GLOBAL JOIN配合分布式表
3.6 缺点六:窗口函数支持有限(已部分解决)
历史问题 :ClickHouse 早期版本不支持窗口函数。
现状:21.x 版本后已支持常用窗口函数。
sql
-- 支持的窗口函数
row_number() OVER (PARTITION BY user_id ORDER BY event_time)
rank() OVER (...)
lag() / lead() OVER (...)
sum() OVER (...)
仍有局限:
- 性能不如专用数仓(如 Snowflake)
- 某些复杂窗口计算仍需改写
3.7 缺点七:SQL 方言差异
ClickHouse 的 SQL 是标准 SQL 的超集,但也存在一些差异:
| 功能 | 标准 SQL | ClickHouse | 说明 |
|---|---|---|---|
LIKE 性能 |
一般 | 差 | 避免在大数据量下使用 %xxx% |
IN 子查询 |
支持 | 支持,但需 GLOBAL IN |
分布式场景需注意 |
JOIN 多个大表 |
慢 | 极慢 | 避免,改用宽表 |
UPDATE / DELETE |
行级 | 块级重写 | 完全不同 |
3.8 缺点八:元数据管理需要人工干预
某些场景下,ClickHouse 不会自动清理废弃数据:
- 删除表后,
/var/lib/clickhouse/data/目录可能残留 DETACHED分区不会自动清理- 副本失败后的残留文件需手动清理
建议 :建立监控脚本,定期清理 system.detached_parts。
四、优缺点对照总表
| 维度 | 优点 | 缺点 |
|---|---|---|
| I/O 效率 | 只读需要的列,I/O 节省 90%+ | 点查时需拼装多列,反而慢 |
| 压缩存储 | 压缩比 5~10 倍,成本低 | 写入时需解压/重压,CPU 开销大 |
| 查询性能 | 聚合查询快 10~100 倍 | 点查性能差,QPS 受限 |
| 事务 | --- | 不支持 ACID 事务 |
| 更新/删除 | --- | 成本极高,需重写数据块 |
| 索引 | 稀疏索引空间小 | 无二级索引,非主键过滤慢 |
| Join | --- | 大表 Join 内存风险高 |
| 窗口函数 | 已支持常用函数 | 性能不如专用数仓 |
| SQL 标准 | 标准 SQL 超集 | 存在方言差异(LIKE 等) |
| 运维 | --- | 元数据管理需人工干预 |
五、选型建议:什么时候选 ClickHouse?
5.1 强烈推荐使用 ClickHouse
| 特征 | 说明 |
|---|---|
| ✅ 海量数据聚合分析 | 每天 TB~PB 级数据写入 |
| ✅ 宽表 + 少量列查询 | 表有 50+ 列,查询只取 3-5 列 |
| ✅ 时间范围过滤 | 查询总是带时间条件(分区裁剪) |
| ✅ 高吞吐写入 | 每秒数十万行批量写入 |
| ✅ 弱事务/弱更新 | 数据几乎只追加,不修改 |
| ✅ 团队熟悉 SQL | 不需要额外培训 |
5.2 不建议使用 ClickHouse
| 特征 | 替代方案 |
|---|---|
| ❌ 需要 ACID 事务 | PostgreSQL / MySQL |
| ❌ 频繁单行更新/删除 | 改用追加 + 版本号,或换 MySQL |
| ❌ 高并发点查(QPS > 5000) | Redis / MySQL |
| ❌ 强一致性要求 | 需评估异步副本延迟 |
| ❌ 复杂多表 JOIN(多张大表) | 改用宽表或换 Trino/Presto |
| ❌ 全文搜索 | Elasticsearch |
六、总结
| 问题 | 答案 |
|---|---|
| 列式存储最大的好处是什么? | I/O 减少 + 压缩率高 → 查询快、存储省 |
| ClickHouse 最大的短板是什么? | 不支持事务、更新/删除成本高 |
| 什么时候绝对不要用 ClickHouse? | 金融交易、库存扣减、需要高并发点查 |
| 窗口函数能用吗? | ✅ 21.x 后已支持 |
| 二级索引有吗? | ❌ 没有,只能用主键 + 跳数索引替代 |
一句话记住
ClickHouse 列式存储:查询有多快,更新就有多难;压缩有多省,事务就有多弱。它是 OLAP 的利器,但别当 OLTP 来用。
如需深入了解 ClickHouse 的部署架构选型、分片与副本机制详解、分布式表原理剖析、无中心架构设计哲学、生产环境集群调优、多副本一致性实践、ClickHouse Keeper 核心原理等内容,请持续关注本专栏《ClickHouse 一站式从入门到实战》系列文章。
在数据库的世界里,行式存储与列式存储之争已持续多年。ClickHouse 凭借列式存储,在 OLAP 领域独树一帜。但列式存储并非银弹------它在带来极致查询性能的同时,也伴随着事务缺失、更新困难等代价。本文将从原理到实战,系统梳理 ClickHouse 列式存储的优缺点,并给出清晰的选型建议。