为什么 ClickHouse 比 MySQL 快 100 倍?列式存储原理

面试经常被问:「为什么 OLAP 数据库用列存,而不是行存?」

直接用数据说话。假设有一张表:

CREATE TABLE orders (
  id INT, user_id INT, amount DECIMAL,
  status VARCHAR, created_at DATETIME
);
-- 1 亿行数据

查询:SELECT SUM(amount) FROM orders WHERE status = 'paid'

行式存储(MySQL/InnoDB)

磁盘上数据按行排列:

[1|101|99.9|paid|2026-01-01][2|102|50.0|pending|2026-01-02]...

执行 SUM(amount) WHERE status = 'paid'

  1. 读取整行(所有 5 列)→ 大量不必要的 I/O
  2. 解析每行,提取 status 和 amount
  3. 过滤 + 聚合

实际读取数据量:5 列 × 1 亿行 ≈ 2GB+

列式存储(ClickHouse/Doris)

磁盘上数据按列排列:

amount:  [99.9 | 50.0 | 30.0 | ...]   ← 只读这列
status:  [paid | pending | paid | ...] ← 和这列

执行同样的查询:

  1. 只读取 amount 和 status 两列
  2. 列数据是连续存储的,一次扫描
  3. 用 SIMD 向量化比较:一次比较 256 个值

实际读取数据量:2 列 × 1 亿行 ≈ 800MB,不到行存的 40%。

压缩优势

列式存储的同列数据类型相同,压缩比极高:

压缩算法 行存压缩比 列存压缩比
LZ4 2-3x 5-10x
ZSTD 3-5x 10-20x

因为一列里的 status 只有几个值(paid/pending/cancelled),字典编码后几乎可以压缩到原来的 1%。

向量化执行

这是列存最大的性能来源:

// 传统逐行处理
for (int i = 0; i < n; i++) {
    if (status[i] == PAID)
        sum += amount[i];
}

// 向量化:一次处理 256 行
__m256i status_vec = _mm256_load_si256(&status[i]);
__m256i mask = _mm256_cmpeq_epi32(status_vec, paid_vec);
sum += _mm256_mask_add(mask, amount_vec);

CPU 的 SIMD 指令一次处理 8 个 32 位整数(AVX2)或 16 个(AVX-512)。列式存储在内存里是连续的,天然适合向量化。

什么时候该用列存?

场景 推荐
SELECT * 返回全行 行存
聚合查询 (SUM/COUNT/AVG) 列存
全表扫描 + 过滤 列存
点查询 (WHERE id = 1) 行存
频繁更新 行存

实际性能对比

在 16 核、64GB 的机器上测试 1 亿行 TPC-H lineitem 表:

查询 MySQL 8.0 ClickHouse 倍数
SUM(quantity) 4.2s 0.18s 23x
GROUP BY returnflag 12.8s 0.35s 36x
WHERE + GROUP BY + ORDER BY 45.6s 0.42s 108x

列式存储的快,不是靠更快的硬件,而是更少的数据读取 + 更好的压缩 + 向量化执行。三个因素叠加,才有了 100 倍的差距。

关于 Zihao Zhang

后端开发工程师。关注 Java/Spring Boot/Redis/MySQL 技术栈,分布式系统,OLAP 数据库,AI Agent 开发与应用。

评论

评论已关闭。