这是一个理解 ClickHouse 性能本质的核心问题。如果只用一句话概括:
ClickHouse 的向量化执行,本质是:
不是"一行一行算",而是"一批一批算"。
下面我分 直觉 → 内部机制 → 和行式执行对比 → 为什么快 → 常见误解 五个层次给你讲清楚。
一、先用直觉理解(非常重要)
❌ 传统行式执行(Row-at-a-time)
text
for each row:
取一行
计算表达式
判断条件
输出
- 每行一次函数调用
- 分支判断频繁
- CPU cache 利用率低
✅ ClickHouse 向量化执行(Vectorized)
text
for each block (1024 ~ 8192 rows):
取一整列(数组)
在数组上做计算
一次性输出结果列
- 一次处理一批数据
- 函数作用于数组(vector)
- 大量减少 CPU 指令 & 分支
👉 这是 ClickHouse 快的根本原因之一
二、ClickHouse 内部是怎么做的
1️⃣ Block(数据块)
ClickHouse 的最小执行单元不是 row,而是 Block:
- 一个 Block ≈ 一批行
- 每一列是一个数组(
Column) - 默认 block size:
max_block_size(通常 8192)
示意:
text
Block:
user_id → [u1, u2, u3, ...]
price → [10, 20, 30, ...]
cnt → [1, 2, 3, ...]
2️⃣ Expression 在列上执行
sql
SELECT price * cnt
FROM t
WHERE price > 10
执行逻辑:
-
price > 10→ 对
price列数组一次性比较→ 生成 Bool mask
-
price * cnt→ 两个数组做逐元素乘法
👉 没有"单行循环"
三、和行式执行的对比(关键)
| 维度 | 行式执行 | CK 向量化 |
|---|---|---|
| 处理单位 | 行 | Block(批) |
| 函数调用 | 每行一次 | 每列一次 |
| CPU 分支 | 多 | 极少 |
| Cache 命中 | 低 | 高 |
| SIMD | 几乎不可用 | 可大量使用 |
四、为什么向量化执行这么快
1️⃣ CPU Cache 友好
- 连续内存访问
- 顺序扫描
- 几乎没有 random access
2️⃣ SIMD 自动加速
现代 CPU 可以:
- 一条指令处理多个值(AVX2 / AVX-512)
例如:
text
price > 10
→ 一条指令比较 8~16 个值
3️⃣ 函数调用 / 分支极少
- 行式:一行一个 if
- 向量化:一次判断一整列
五、向量化 ≠ 列存(容易混淆)
| 概念 | 含义 |
|---|---|
| 列存 | 数据存储方式 |
| 向量化执行 | 数据处理方式 |
- 列存 ≠ 必然向量化
- 行存 ≠ 不能向量化(但很难)
👉 ClickHouse 是:
列存 + 向量化执行 + 顺序扫描
三者叠加,才有极致性能。
六、ClickHouse 中"哪些地方用到了向量化"
几乎全部核心算子:
WHERE过滤- 表达式计算
GROUP BY- 聚合函数
- 排序(部分阶段)
- JOIN(build/probe 阶段)
七、什么时候向量化会"失效"
⚠️ 以下场景会降低向量化优势:
1️⃣ 复杂 UDF / lambda
2️⃣ 每行逻辑分支非常复杂
3️⃣ 极端小数据量(几十行)
4️⃣ 大量 LIKE '%xxx%' / 正则
👉 这时 CPU 成本会上来,但仍比行式好。
八、一个非常重要的工程认知
ClickHouse 的性能不是"索引查得快",
而是"即使扫,也扫得极快"。
向量化执行让"全表扫描"变成了一个可接受、甚至高效的操作。
九、一句话总结(面试级)
ClickHouse 的向量化执行是:
以 Block 为单位,把列当数组,用 SIMD 和顺序内存访问对整批数据做计算,从而极大降低 CPU 指令数和 cache miss。
一、典型场景
-
Flink 实时写入 CK(明细 / 底池 / 选品结果)
-
CK 上:
- 圈品(等值 + 模糊条件)
- 统计分析(group by、count、sum)
- 偶尔
LIKE、IN、范围过滤
-
数据量:百万~十亿级
-
很关心:
- 为啥没索引也能跑
- 为啥 LIKE 不一定慢
- 为啥扫全表还能接受
👉 这些 全部依赖向量化执行。
二、什么 SQL「最能吃满」向量化(你应该多写)
✅ 1️⃣ 等值 / IN / 范围过滤(无索引也不怕)
sql
SELECT count(*)
FROM sku_pool
WHERE pool_type = 3
AND status = 1
AND price BETWEEN 100 AND 300;
为什么快?
pool_type = 3→ 对一整列做批量比较status = 1→ 布尔 mask- 没有逐行 if
- 扫得快 + 算得快
👉 这是 CK 最擅长的形态
✅ 2️⃣ 统计(典型 OLAP)
sql
SELECT
category_id,
countDistinct(sku_id)
FROM sku_event
WHERE event_date = '2026-01-12'
GROUP BY category_id;
向量化体现在哪?
countDistinct在 block 上批量处理- GROUP BY 用哈希表 + 批量 probe
- cache 命中率极高
👉 这是 CK 的绝对舒适区
✅ 3️⃣ 模糊过滤里的"可控模糊"
sql
WHERE title LIKE '耐克%'
比你想象中快的原因:
- 扫描是列式顺序 IO
- 前缀匹配在 vector 上跑
- 比 MySQL 一行一行判断快得多
⚠️ 但注意:
LIKE '%耐克%' 就开始吃 CPU 了(后面说)
三、哪些 SQL 会"拖垮"向量化(你要警惕)
❌ 1️⃣ 大字段 + %LIKE%
sql
WHERE json_str LIKE '%"brand":"NIKE"%'
问题在哪?
- 每行都要做子串扫描
- 字符串很长
- SIMD 优势基本丢失
👉 不是 I/O 慢,是 CPU 被吃光
❌ 2️⃣ 复杂 UDF / Lambda
sql
WHERE my_custom_func(col1, col2) = 1
- 每个 block 都要走复杂逻辑
- 很难 vectorize
- 退化成"伪行式"
❌ 3️⃣ 极小结果集 + 频繁点查
sql
SELECT *
FROM t
WHERE id = 'xxx'
LIMIT 1;
- CK 能做
- 但这不是它的强项
- QPS 高时不如行存数据库
👉 向量化是"批处理友好",不是"点查友好"
四、把「向量化」用在圈品里的正确姿势(很关键)
❌ 错误姿势(行式思维)
"我要一条条 SKU 判断是否符合条件"
✅ 正确姿势(向量化思维)
"我要让每个条件都能在整列上一次性判断"
例如:
sql
SELECT sku_id
FROM sku_pool
WHERE
category_id IN (101,102,103)
AND price BETWEEN 80 AND 150
AND brand IN ('NIKE','ADIDAS')
AND score > 60;
- 每个条件 → 一次列级判断
- 最终 mask 合并
- 输出命中行
👉 这是 CK 天生擅长的圈品方式
五、为什么你会感觉「没索引也不慢」
可以用一句话解释:
CK 的优势不在"跳过多少数据",而在"即使全扫,也用向量化把 CPU 成本压到极低"
六、把这套理解映射回你之前的问题
1️⃣ 为啥 CK 不强调行存?
→ 行存会破坏向量化执行
2️⃣ 为啥 LIKE 和 = 没索引时差别没你想象大?
→ 都在扫,差在 CPU 指令数量
3️⃣ 为啥 CK 适合圈品、分析、不适合 OLTP?
→ 向量化 = 批处理友好
七、一个"工程级"的总结(你可以直接在设计里用)
ClickHouse 的 SQL 设计目标,不是"少扫数据",
而是"让每一次扫描都尽可能线性、连续、向量化"。
当你写 SQL 时,应该问自己一句:
这个条件,是不是能在"列级别"一次性算完?
如果答案是 是,那这条 SQL 基本就稳了。