列存与向量化查询引擎:Doris、ClickHouse、StarRocks 与 Trino 的性能本质

深入解析现代 OLAP 查询引擎的性能本质:行式与列式存储的取舍、列存压缩与编码、向量化执行引擎与火山模型的差异、Batch 处理与 SIMD 指令级并行、MPP 架构与 Shuffle、Doris/ClickHouse/StarRocks/Trino 四大引擎的架构对比、基于代价的查询优化器与基数估算、谓词下推与列剪枝、Join/聚合执行策略,以及场景化选型与调优实践。

引言

同一张表、同一条 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 + 向量化引擎,核心差异在模型演进:

特性DorisStarRocks
存储明细 + 聚合 + 主键模型主键模型(更新友好)
向量化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 集群实时更新 + 高并发点查
ClickHouseMergeTree✅分片/副本单机大吞吐聚合
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-hocTrino无存储、连接器全
湖仓底座 + 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 加速

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「data-engineering」更多文章

  1. 流批一体:从 Lambda/Kappa 架构到统一计算层
  2. 数据平台成本与 FinOps:存储、计算、弹性与降本实践
  3. 数据网格 Data Mesh:领域数据产品、自助平台与联邦治理