CBO(Cost-Based Optimizer)在 第
3 篇 的规则链之后做 数值决策:选哪种
join 算法、哪种 join 顺序、是否 broadcast 小表。决策输入是
基数估计(cardinality) 与
代价常量;输出是 estimated rows /
cost 列在 EXPLAIN 里。
本文拆解 OLAP 引擎(Trino 476+
主线)的统计来源与代价组成,并对照 postgresql-kernel
第 10 篇 的 pg_statistic。文末用
DuckDB 1.5.4 本机 plan 中的 Cardinality
说明估算如何进入计划;Trino 有/无 ANALYZE
的对比实验需 Docker 栈,本机未跑时不伪造 Trino
数字。
环境:WSL2、i9-12900K;DuckDB 1.5.4 实测;无 Trino JVM。Trino 机制引用官方 Cost-based optimizations(476+)。
一、CBO 在优化链中的位置
flowchart TB
RULES["Logical / physical rules"]
STATS["Statistics snapshot"]
COST["CostCalculator"]
WIN["Pick lowest cost plan"]
RULES --> COST
STATS --> COST
COST --> WIN
| 无统计时 | 典型后果 |
|---|---|
| 默认 row count 常数 | join 顺序随机或固定启发式 |
| NDV 未知 | hash table 内存预估偏差 |
| 分区 min/max 缺失 | 分区裁剪失败 → 全表扫 |
第 5 篇 的 broadcast vs shuffle 阈值直接依赖 build 侧 estimated size。
二、统计对象:表与列
| 统计量 | 符号/字段 | 用途 |
|---|---|---|
| Row count | \(N_R\) | 扫描基数起点 |
| NDV | \(D_c\) | 等值谓词选择率 \(\approx 1/D_c\) |
| Null fraction | \(f_{null}\) | IS NULL / join 语义 |
| Min / Max | \(min_c, max_c\) | 范围谓词、分区裁剪 |
| Histogram | 等深/等宽桶 | 非均匀分布 |
| Average width | 字节宽 | 内存与 network 估算 |
2.1 选择率合成
多谓词 AND 常假设 ** independence**(与 PG
clauselist_selectivity 类似):
\[ sel(p_1 \land p_2) \approx sel(p_1) \times sel(p_2) \]
相关列(city + zipcode)会
严重低估或高估;PG 用
CREATE STATISTICS;Trino/Iceberg 侧部分依赖
Puffin 扩展统计(Iceberg spec,A 级)。
2.2 Join 基数
等值 join 常用:
\[ |R \bowtie S| \approx \frac{|R| \cdot |S|}{\max(D_{R.k}, D_{S.k})} \]
NDV 估错时 中间结果数量级错误,导致 错误 join 顺序(先放大再过滤 vs 先过滤再 join)。
三、统计来源
| 来源 | 机制 | 典型引擎 |
|---|---|---|
| ANALYZE | 采样写 catalog | PG、Trino Hive connector |
| Connector layout | split 时文件 row count | Iceberg manifest |
| Table property | 外部维护 | 手工 CALL system.register_table |
| Runtime | 动态反馈 | Spark AQE(B 级对照) |
3.1 Trino ANALYZE
Trino 对支持 analyze properties 的
connector 提供 ANALYZE(文档
ANALYZE,476+)。写入 column
statistics 到 metastore 或 connector 元数据,供下次
planning 读取。
本机限制:reproduce/docker-compose.yml
未在本写作环境执行;不编造 「ANALYZE 前后
estimated rows 从 X 变 Y」的 Trino 片段。复现步骤见
reproduce/run_trino_experiments.sh 与 lakehouse reproduce。
3.2 Iceberg 与湖上统计
| 层级 | 内容 | 与 lakehouse/18 关系 |
|---|---|---|
| Manifest | per-file record count、column bounds | file pruning |
| Puffin | 外部 stats blob | NDV、histogram 可选 |
| Partition spec | transform 列 | partition pruning |
引擎 不扫描全表 即可从 manifest 汇总 分区级 row count;这是湖仓相对裸 Parquet 的关键优势(lakehouse/08)。
四、代价模型组成
Trino CostCalculator(概念,476+ 文档
Cost-based
optimizations)将计划节点代价分解为:
| 分量 | 含义 | 分布式特有 |
|---|---|---|
| CPU | 表达式、hash、比较 | 各节点加总 |
| IO | 扫描字节/页 | connector 延迟 |
| Network | Exchange shuffle 数据量 | MPP 核心 |
\[ Cost(plan) = w_{cpu} \cdot CPU + w_{io} \cdot IO + w_{net} \cdot Network \]
权重 \(w_*\) 可通过 session property 或 config 调整(具体 property 名以 476+ 文档为准)。
4.1 与 PostgreSQL 对照
| PG GUC / 概念 | Trino 类比 |
|---|---|
seq_page_cost /
random_page_cost |
scan CPU + IO 权重 |
cpu_tuple_cost |
表达式 CPU |
effective_cache_size |
connector 侧较少显式建模 |
| — | network cost(PG 无 shuffle) |
PG 第 10 篇 的 rows=1 灾难 在 Trino 体现为 estimated 1 row → nested loop + 实际百万行 shuffle。
五、DuckDB 1.5.4:计划中的 Cardinality
本机 run_duckdb_experiments.py(100k
orders,5000 customer):
| 查询 | 计划要点 | 估算基数 |
|---|---|---|
q_filter:o_custkey = 3 |
SEQ_SCAN + filter |
~14286(\(\approx 100000/7\)) |
q_join:日期 filter + join + agg |
HASH_JOIN,scan 带
o_orderdate>=1996-06-01 |
filter 后 ~20000 |
DuckDB 在 优化阶段
用表统计(generate_series
合成数据亦有内置估计)写入 plan Cardinality
字段。这与 Trino EXPLAIN 的
Estimates: rows=...
角色相同;数值
不可跨引擎对比(算法与假设不同)。
实测 wall
time(q_join,EXPLAIN ANALYZE 3
次中位数):0.0028 s——说明
估计与执行在小数据上一致,不代表 Trino 在
TB 级 Iceberg 上的行为。
六、统计过期与估算误差
flowchart LR
STALE["Stats stale"] --> BAD["Bad cardinality"]
BAD --> WRONG["Wrong join order"]
WRONG --> SCAN["Full table scan"]
WRONG --> BOOM["Huge intermediate"]
WRONG --> BC["Bad broadcast"]
| 症状 | 可能统计原因 | 运维动作 |
|---|---|---|
| Iceberg 全文件 scan | 分区 transform 谓词不可推导 | 检查 predicate on partition column |
| Broadcast 巨大表 OOM | build 侧 row 低估 | 更新 ANALYZE / 调 threshold |
| Shuffle 远大于预期 | join NDV 低估 | 对比 EXPLAIN ANALYZE actual rows |
| 计划频繁变 | 无 stats 默认常量 | 启用 analyze 或 manifest stats |
Trino EXPLAIN ANALYZE 对比 estimated
vs actual 是生产第一排查手段(第 17
篇)。
七、Histogram 与 NDV 精度
等深直方图(equi-depth):每桶行数相近,桶边界记录值域;范围查询用 桶重叠比例 估选择率。
NDV:HyperLogLog 或采样估计;小样本下 distinct 低估 会导致 join 结果 高估(保守)或 低估(激进),取决于引擎公式。
Iceberg Puffin 可存储 sketches;connector 是否读取取决于 Trino Iceberg 版本实现(第 15 篇)。
八、代价 vs 真实延迟
| 指标 | 反映什么 | 不反映什么 |
|---|---|---|
EXPLAIN cost |
优化器内部比较 | 用户 wall time |
EXPLAIN ANALYZE |
实际 CPU/row | 排队、metastore 冷启动 |
| DuckDB 0.0028s | 本地内存 scan | 跨 AZ network |
优化器选「最低 cost plan」是在模型内最优,不是全局 SLA 最优;resource group、并发 spill 会改变实际表现(第 16 篇)。
九、诊断清单
SHOW STATS FOR/ information_schema(connector 支持时)。EXPLAIN看 Estimates 是否全为默认值(如-1或固定常数)。EXPLAIN ANALYZE看 Input rows vs Planned rows 比率。- 湖表:对照 lakehouse/18 看 layout constraint 是否出现。
- 必要时
ANALYZE或重写 SQL(谓词写在 partition 列上)。
十、小结
统计信息把 「这张表有多大、这列有多散」 变成数字,代价模型把 算法与 shuffle 体积 变成可比较的 cost。湖格式把 file-level stats 推到 planning 入口,减少全表采样依赖;但 ** independence 假设与过期 stats** 仍是 join 计划崩溃的主因。
下一篇进入 Join 重排与物理算子选择:left-deep vs bushy、broadcast vs partitioned join、DPP。
参考资料
- Trino Documentation, Cost-based optimizations / ANALYZE(476+)。
- Apache Iceberg Specification, Metrics / Puffin。
- postgresql-kernel 第 10 篇(pg_statistic)。
- DuckDB
1.5.4,
reproduce/run_duckdb_experiments.py(Cardinality 与 timing)。 - 第 3 篇(CBO 位置)。
- lakehouse 第 18 章(manifest stats 与 pruning)。
返回 系列目录 | 上一篇:Calcite 与优化框架 | 下一篇:Join 规划
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【分布式 OLAP 查询引擎】OLAP 查询引擎全景:从单进程到 MPP
从 OLTP/OLAP/HTAP 边界、嵌入式 DuckDB 与分布式 Trino/Spark 分工、批式扫描与交互式查询延迟口径出发,闭合 lakehouse 与 stream-processing 之间的查询层缺口,并给出本系列 18 篇地图。
【分布式 OLAP 查询引擎】Calcite 与规则/代价优化框架
RelNode、Convention、Trait 与 Rule-based 改写链;VolcanoPlanner/Cascades memo 搜索;Trino 476+ 自研 planner 与 Calcite 借鉴边界,对照 Spark Catalyst 优化阶段。
【分布式 OLAP 查询引擎】Trino 查询路径:从 SqlQueryExecution 到 Page 流
主线拆解 Trino Coordinator 上 SqlQueryExecution 的生命周期:analyze、plan、fragment、Stage 调度到 Worker Task;Iceberg connector 如何从 snapshot/manifest 过滤生成 Split;Page 在 Operator 链上流动;EXPLAIN 与 EXPLAIN ANALYZE 字段判读。
【分布式 OLAP 查询引擎】Spark SQL 与 Catalyst:逻辑/物理计划与 AQE
拆解 Spark 3.5+ Catalyst 的 Analyzed / Optimized / Physical 计划链、whole-stage codegen 与 shuffle 边界、AQE 的动态 coalesce/skew join/broadcast;并与 Trino 476 及 Iceberg V2 reader 下推能力对照。