DuckDB 源码分析- Logical Plan 逻辑计划
版本
- v1.3-ossivalis
背景
DuckDB 作为开源的嵌入式分析型数据库, 整体代码框架比较清晰,适合作为学习 AP 型数据库的项目。截止 (2025-05) 最新版是 v1.3-ossivalis, 我们编译一个 Debug 版本结合 GDB 调试工具来进行代码的阅读和分析。
1 | git clone -b v1.3-ossivalis https://github.com/duckdb/duckdb |
SQL Parser
DuckDB 使用 C++ 编写, 在 SQL 的解析优化执行等阶段都使用了模块化, SQL Parse 阶段调用了 Postgres 的 Parser 来进行 SQL 的解析, 完成后使用 Transformer 将 PostgreSQL 的解析树转换为 DuckDB 的语法树:
1 | Parser parser(db->con->context->GetParserOptions()); |
Binder
DuckDB 选择将 Binder 操作和逻辑计划的生成进行耦合, 在 class Planner(逻辑计划生成模块)中完成 binder 操作.
Binder 操作会针对 SQL 语句进行检查, 例如 SELECT 中查询的字段是否存在, WHERE 条件中是否合法等. Binder 操作对 QueryNode 进行绑定后会生成 BoundQueryNode, 再进行逻辑计划的生成:
1 | BoundStatement Binder::Bind(QueryNode &node) { |
Logical Plan 逻辑计划
我们创建一个测试表,并写入数据, 根据不同的 SQL 语句来动态调试来梳理 Logical Plan 的生成过程.
1 | D CREATE TABLE test_table ( |
逻辑计划是由一系列 LogicalOperator 组成的算子树, LogicalOperator 的结构如下:
1 | class LogicalOperator { |
LogicalOperator 的 children
存放的是其子逻辑运算符, expressions
是当前逻辑运算符的表达式, 以上述表结构为例, 执行以下 SQL:
1 | D PRAGMA explain_output = 'all'; |
上述 SQL 语句有 3 个 LogicalOperator, 分别是 duckdb::LogicalOperatorType::LOGICAL_PROJECTION
和 duckdb::LogicalOperatorType::LOGICAL_FILTER
, duckdb::LogicalOperatorType::LOGICAL_GET
.
LOGICAL_PROJECTION: 投影算子.
LOGICAL_FILTER: 过滤算子.
LOGICAL_GET: 属于 Data sources 算子, 从数据表中扫描数据.
其他的 LogicalOperator 定义在 LogicalOperator 定义
Logical Plan 生成
查询语句的逻辑计划生成是在Binder::CreatePlan(BoundSelectNode &statement)
中实现:
1 | unique_ptr<LogicalOperator> Binder::CreatePlan(BoundSelectNode &statement) { |
Logical Plan Optimizer 逻辑计划优化
逻辑计划完成生成后, 我们需要对逻辑计划展开优化:
1 |
|
DuckDB 支持用户自定义优化规则, 自定义的优化规则会被执行两次.
1 | unique_ptr<LogicalOperator> Optimizer::Optimize(unique_ptr<LogicalOperator> plan_p) { |
DuckDB 内置了很多启发式规则, 其中内置的优化规则包括:
- EXPRESSION_REWRITER: 表达式重写.
表达式重写是
Optimizer
初始化时构造函数里就填好的规则, 包括常量折叠, 分配律优化, CASE 语句简化等等.
- FILTER_PULLUP: 谓词上拉.
我们以下面这个 SQL 为例, FILTER_PULLUP 优化会将
vals1.i=5
提至FILTER
算子下方, 方便后续的谓词下推:
1 | D explain SELECT * FROM (SELECT * FROM vals1, vals2 WHERE vals1.i=5) as tbl1, |
FILTER_PULLUP 优化后的逻辑计划:
1 | ┌─────────────────────────────┐ |
FILTER_PUSHDOWN,
EMPTY_RESULT_PULLUP,
CTE_FILTER_PUSHER,
REGEX_RANGE,
IN_CLAUSE,
JOIN_ORDER,
DELIMINATOR,
UNNEST_REWRITER,
UNUSED_COLUMNS,
STATISTICS_PROPAGATION,
COMMON_SUBEXPRESSIONS,
COMMON_AGGREGATE,
COLUMN_LIFETIME,
BUILD_SIDE_PROBE_SIDE,
LIMIT_PUSHDOWN,
TOP_N,
COMPRESSED_MATERIALIZATION,
DUPLICATE_GROUPS,
REORDER_FILTER,
SAMPLING_PUSHDOWN,
JOIN_FILTER_PUSHDOWN,
EXTENSION,
MATERIALIZED_CTE,
SUM_REWRITER: SUM() 聚合表达式的重写.
官方注释里标明了具体的优化规则即: SUM(x + C) -> SUM(x) + C * COUNT(x), x 为某列字段, C 为常量:
SUM(x + C) 需要对每列的数据先多一次加法, 每列数据额外引入了一条加法指令.
SUM(x) 对一大列连续内存做加法, 可以使用 SIMD,比如 AVX 指令一轮处理8或16个元素, 然后使用一条乘法指令 C * count(X) 即可.
- LATE_MATERIALIZATION