引言
同一张表、同一条 SQL,在 Hive 上跑十分钟,在 ClickHouse 上可能只要三秒。差距不在于"谁更努力",而在于执行模型的根本不同:行式还是列式?逐行解释还是批量向量化?单机串行还是 MPP 并行?现代 OLAP 引擎的每一次性能跃迁,都对应着其中某个模型的升级。理解这些本质,你才能回答"该选哪个引擎、瓶颈在哪、怎么调"。
向量化查询引擎的性能公式:列存 × 向量化 × 并行 = 数量级的吞吐提升。三件事互为前提,缺一不可。
本文从存储布局讲起,逐层拆解列存、向量化执行、MPP 架构、优化器与下推机制,并横向对比 Doris、ClickHouse、StarRocks、Trino 四大引擎。
一、列式存储与行式存储
1.1 行存 vs 列存
| 维度 | 行式(Row) | 列式(Columnar) |
|---|---|---|
| 布局 | 一行数据连续存放 | 一列数据连续存放 |
| 读整行 | 快 | 慢(多列拼接) |
| 读少数列 | 慢(整行读入) | 快(只读目标列) |
| 压缩率 | 低 | 高(同列同类型) |
| 适用 | 点查、OLTP | 扫描、聚合、OLAP |
OLAP 查询几乎都只读少数列、扫大量行——天然适合列存。
1.2 列存压缩与编码
同列数据类型一致、取值重复度高,压缩率远高于行存。常用编码:
# 编码手段
# Dictionary: 低基数列 → 字典替换(性别/状态)
# Delta / Gorilla: 时间戳、计数器 → 存差值
# RLE(游程): 连续重复值 → (值, 重复次数)
# LZ4/ZSTD: 通用压缩 → 解压快
# 编码选择影响: 压缩率 vs 解压速度 的权衡
1.3 SIMD 友好的内存布局
列存让数据在内存中按列连续排列,这正是 CPU 向量指令(SIMD,如 AVX-512)最喜欢的形态:一次指令处理 16 个 double。逐行的结构体数组则无法向量化,还伴随缓存不命中。
# 为什么列存 + SIMD 快
# 连续内存 → 预取命中高
# 无分支预测抖动 → 数据并行
# 一条指令处理多元素 → IPC 提升
# 行存随机访问 → 缓存失效 + 标量执行
二、向量化执行引擎
2.1 火山模型 vs 向量化
| 模型 | 数据单位 | 调度开销 | 适合 |
|---|---|---|---|
| 火山模型(Volcano) | 一行 | 每行虚函数调用 | 通用但慢 |
| 向量化(Vectorized) | 一批(Batch) | 每批一次调度 | OLAP 吞吐 |
# 火山模型: next() 一行一行取, 每行都有虚调用开销
# 向量化: next() 一批(如 1024 行), 内部用循环 + SIMD
# 指令级优化: 编译期去掉分支, 热点算子特化
2.2 Batch 处理与算子向量化
算子(过滤、聚合、Join)都改为按批处理:一批数据进入算子,完成一次向量运算后输出下一批。过滤算子可以直接生成掩码向量,跳过不符合的行,无需逐行分支。
// 向量化过滤: 用掩码一次性过滤一批
void filter(const Batch& batch, double min_amount) {
// SIMD: 生成 mask, 压缩挑选命中的行
__m256d vals = _mm256_load_pd(batch.amounts);
__m256d cond = _mm256_cmp_pd(vals, _mm256_set1_pd(min_amount), _CMP_GE_OQ);
uint32_t mask = _mm256_movemask_pd(cond);
compact_keep(batch, mask);
}
2.3 从解释执行到编译执行
更进一步是编译执行:查询计划不是解释执行,而是生成一段特化的机器码(LLVM JIT),去除虚函数与分支。ClickHouse 的表达式编译、Trino 的字节码生成都是此思路。解释执行每行丢几十个周期,编译执行把周期压到接近零。
三、MPP 架构与分布式执行
3.1 MPP 是什么
MPP(Massively Parallel Processing)把查询拆成可并行的子任务,分发给多节点多核同时执行,最后汇聚结果。与 Hadoop 的差异在于查询级并行而非 MapReduce 两阶段:
# MPP 执行
# Coordinator: 解析/优化/生成分布式计划
# Worker: 执行局部算子(scan/join/agg)
# Exchange: 节点间数据传输(Shuffle)
# 结果: 汇聚到 Coordinator 或直接返回客户端
3.2 Shuffle 与数据本地性
Join/聚合需要把相同 key 的数据汇聚到同一节点,这就是 Shuffle(重分区)。Shuffle 是 MPP 最大的成本来源——网络传输 + 序列化。
# Shuffle 开销控制
# 减少 Shuffle: 广播小表(join)、预聚合(partial agg)
# 优化数据布局: 预先按 join key 分桶/分区
# 数据本地性: 计算尽量就近数据节点(存算耦合)
# 存算分离后: 网络成了瓶颈 → 靠列存压缩减小传输量
3.3 弹性与存算分离
新一代引擎强调存算分离:存储用对象存储/S3,计算按需弹性伸缩。代价是查询可能要从远端拉数据——所以列存压缩、向量化、下推(把过滤推给存储层)在存算分离架构里更重要。
四、四大引擎架构对比
4.1 Doris 与 StarRocks
两者都是国产 MPP + 向量化引擎,核心差异在模型演进:
| 特性 | Doris | StarRocks |
|---|---|---|
| 存储 | 明细 + 聚合 + 主键模型 | 主键模型(更新友好) |
| 向量化 | 2.x 起全面向量化 | 原生向量化 |
| 物化视图 | 异步物化视图 | 同步物化视图 |
| 定位 | 稳定大厂生态 | 更强实时分析 |
主键模型让 StarRocks 更适合"实时更新 + 高并发点查",Doris 则在大规模明细分析上生态成熟。
4.2 ClickHouse
ClickHouse 是列存 + 向量化 + 单机/分片的代表,把单节点压榨到极致:
# ClickHouse 特点
# MergeTree 存储引擎族: 分区 + 稀疏索引
# 局部性: 单机即可跑千亿行
# 分片 + 副本: 水平扩展, 但跨节点 Join 弱
# 大聚合/点查强, 高并发精确更新弱
4.3 Trino/Presto
Trino 是联邦查询引擎:不存数据,只做分布式 SQL 计算,通过 Connector 读 Hive、Iceberg、MySQL、S3 等任意源。
# Trino 特点
# 无存储: 纯计算层, 弹性伸缩
# 连接器生态: 一库查遍湖仓/异构源
# 联邦 Join: 跨源关联, 但性能依赖源下推能力
# 适合: 湖仓分析、ad-hoc 联邦查询
4.4 横向对比
| 引擎 | 存储 | 向量化 | 扩展方式 | 最佳场景 |
|---|---|---|---|---|
| Doris | 内置 | ✅ | MPP 集群 | 大规模明细分析 |
| StarRocks | 内置 | ✅ | MPP 集群 | 实时更新 + 高并发点查 |
| ClickHouse | MergeTree | ✅ | 分片/副本 | 单机大吞吐聚合 |
| Trino | 无(联邦) | ✅ | 无状态伸缩 | 湖仓 ad-hoc 查询 |
五、查询优化器
5.1 基于代价的优化(CBO)
优化器把 SQL 转成多个候选执行计划,用统计信息估算每个计划的代价(扫描行数、CPU、IO、网络),选代价最小的:
# CBO 工作流
# SQL → AST → 逻辑计划 → 物理计划(候选) → 代价估算 → 最优
# 关键输入: 表行数/列基数/数据分布/索引
# 输出: 扫描方式、Join 顺序、Shuffle 策略
5.2 统计信息与基数估算
代价估算准不准,取决于统计信息新鲜度:
| 统计量 | 作用 |
|---|---|
| 行数 / 字节数 | 扫描代价 |
| 列基数(distinct) | 聚合/Join 估算 |
| 列分布直方图 | 过滤选择性 |
| 分区统计 | 裁剪收益 |
统计过期会导致优化器选错 Join 顺序或错误地走广播——定期收集统计(ANALYZE)是优化器工作的前提。
5.3 物化视图改写
优化器可以把高频查询改写为命中物化视图,用"预计算"换查询时间:
-- StarRocks 创建物化视图
CREATE MATERIALIZED VIEW mv_daily_gmv AS
SELECT dt, sum(amount) AS gmv
FROM shop.orders
GROUP BY dt;
-- 查询自动改写命中 mv
SELECT dt, sum(amount) FROM shop.orders WHERE dt >= '2026-09-01' GROUP BY dt;
六、谓词下推与列剪枝
6.1 谓词下推的层次
谓词下推是把过滤条件尽量下放到靠近数据源的位置执行,减少上层处理量:
# 下推层次(越深越好)
# SQL 层: where → 存储层: 分区裁剪/索引
# 跨 Join: where 条件提前过滤输入
# 数据源: Iceberg/Parquet 的 min/max 与 bloom filter
# 目标: 少读数据 = 少解压 = 少传输
6.2 列剪枝
只读取查询需要的列,是列存引擎的标配优化:
-- 只读 order_id, amount, dt 三列
SELECT order_id, amount FROM shop.orders WHERE dt = '2026-09-27';
-- 其余列在扫描阶段就被剪掉
6.3 分区裁剪与 Bloom Filter
- 分区裁剪:
WHERE dt=...直接跳过无关分区文件。 - Bloom Filter:低基数过滤场景,存储层用布隆过滤器快速排除不含目标值的文件/块,大幅减少 IO。
# 下推组合拳
# where 谓词 → 分区裁剪 + 列剪枝 + min/max 跳过 + bloom filter
# 正确建模分区/排序键 = 给下推提供燃料
七、Join 与聚合执行策略
7.1 Hash Join vs Sort Merge Join
| Join | 原理 | 内存 | 适用 |
|---|---|---|---|
| Hash Join | 建 hash 表 + 探测 | 高 | 等值 Join 主流 |
| Sort Merge | 排序后归并 | 低 | 非等值/大数据倾斜 |
| Broadcast Join | 小表广播到各节点 | 小表内存 | 大表 × 小表 |
# 选择策略
# 一大一小 → Broadcast Join(免 Shuffle)
# 两大等值 → 分桶对齐 + Hash Join
# 倾斜 → 加盐拆分热点 key
7.2 Join 调优要点
- 右表建哈希:一般选小表建 hash 表。
- 分桶对齐:两表按相同 key 预先分桶,Join 时免重分区。
- 本地 Join:StarRocks 的 Colocate Join / ClickHouse 的分片内 Join 减少网络。
7.3 预聚合与聚合下推
- 部分聚合(Partial Agg):节点先本地聚合,只把聚合结果上抛,极大减少 Shuffle 量。
- 物化视图/OLAP 预聚合:把高成本聚合预先算好。
-- 部分聚合示例: 先按 (dt, region) 预聚合, 再按 region 汇总
SELECT region, sum(gmv) FROM (
SELECT dt, region, sum(amount) AS gmv
FROM shop.orders GROUP BY dt, region
) GROUP BY region;
八、选型与调优实践
8.1 场景选型
| 场景 | 推荐 | 理由 |
|---|---|---|
| 万亿级明细分析 | Doris | 生态成熟、扩展稳 |
| 实时更新 + 高并发点查 | StarRocks | 主键模型 + 同步物化视图 |
| 单机大吞吐聚合/日志分析 | ClickHouse | 局部性极佳 |
| 湖仓联邦/ad-hoc | Trino | 无存储、连接器全 |
| 湖仓底座 + OLAP 加速 | Trino + Doris | 存算分离、各司其职 |
8.2 通用调优清单
# [ ] 统计信息定期收集(ANALYZE)
# [ ] 排序键/分区键贴合常用 where
# [ ] 小表 join 走广播
# [ ] 大聚合拆两阶段(partial → final)
# [ ] 过滤尽量下推, 减少扫描量
# [ ] 物化视图覆盖高频报表
# [ ] 监控扫描字节数, 而不是只看耗时
8.3 监控与排障
- 看扫描量:扫描字节数陡增 = 谓词没下推 / 分区没裁剪。
- 看 Shuffle 量:网络传输大 = Join/聚合未对齐。
- 看内存:Hash Join 内存不足会 spill 到磁盘,性能骤降。
- 看慢查询:用引擎的 profile 定位瓶颈算子(scan/join/agg)。
总结
| 层 | 机制 | 性能贡献 |
|---|---|---|
| 存储 | 列式 + 压缩编码 | 少读、少解压 |
| 执行 | 向量化 + 编译 | 指令级并行 |
| 架构 | MPP + 本地性 | 水平并行 |
| 优化 | CBO + 统计 | 选对计划 |
| 下推 | 谓词/列/分区裁剪 | 少扫数据 |
| 数据布局 | 排序键/分区键 | 下推的燃料 |
向量化查询引擎的所有秘密,都可以归结为一句话:让 CPU 少做无用功。列存减少 IO,向量化减少指令开销,下推减少处理量,MPP 摊平到多核多机。选型时不要只看跑分,要看你工作负载的瓶颈在哪:扫描量、Shuffle、还是 Join。抓住瓶颈,调优就有了方向。
参考与延伸阅读
- ClickHouse 官方文档:MergeTree、数据编码与向量化执行
- Doris/StarRocks 官方文档:MPP 架构与物化视图
- Trino 官方文档:Connector 与查询下推
- 《Designing Data-Intensive Applications》(O’Reilly)——存储与执行模型
- Doris 与 StarRocks — MPP 实时分析实战
- ClickHouse 分析引擎 — 列存分析调优
- SQL 查询优化 — 通用 SQL 调优
- 湖仓一体架构 — 湖仓 + OLAP 加速
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。