面试经常被问:「为什么 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':
- 读取整行(所有 5 列)→ 大量不必要的 I/O
- 解析每行,提取 status 和 amount
- 过滤 + 聚合
实际读取数据量:5 列 × 1 亿行 ≈ 2GB+
列式存储(ClickHouse/Doris)
磁盘上数据按列排列:
amount: [99.9 | 50.0 | 30.0 | ...] ← 只读这列
status: [paid | pending | paid | ...] ← 和这列
执行同样的查询:
- 只读取 amount 和 status 两列
- 列数据是连续存储的,一次扫描
- 用 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 倍的差距。
评论
评论已关闭。