理解 DuckDB 的逻辑计划(Logical Plan)、优化器(Optimizer)和物理执行计划模块的工作流程

✅ 一、用于调试的 SQL 语句建议

✅ 1. 创建 orders 表的 SQL

CREATE TABLE orders (
    order_id     INTEGER,
    customer_id  INTEGER,
    order_date   DATE,
    amount       DOUBLE
);

✅ 2. 插入测试数据(模拟多个客户在不同时间下单)

INSERT INTO orders VALUES
-- 客户1:7个订单,部分在2022年之后
(1, 101, '2022-01-02', 120.0),
(2, 101, '2022-02-15', 80.0),
(3, 101, '2022-03-01', 90.0),
(4, 101, '2022-04-10', 60.0),
(5, 101, '2022-06-12', 70.0),
(6, 101, '2022-07-20', 30.0),
(7, 101, '2022-08-05', 40.0),

-- 客户2:4个订单,不满足 HAVING 条件
(8, 102, '2022-02-01', 200.0),
(9, 102, '2022-03-01', 180.0),
(10, 102, '2022-03-15', 150.0),
(11, 102, '2022-03-20', 220.0),

-- 客户3:6个订单,满足 HAVING 条件
(12, 103, '2022-01-05', 95.0),
(13, 103, '2022-01-15', 85.0),
(14, 103, '2022-02-10', 90.0),
(15, 103, '2022-02-20', 88.0),
(16, 103, '2022-03-01', 77.0),
(17, 103, '2022-03-10', 66.0),

-- 客户4:5个订单,刚好不满足 COUNT(*) > 5
(18, 104, '2022-04-01', 50.0),
(19, 104, '2022-04-05', 45.0),
(20, 104, '2022-04-10', 60.0),
(21, 104, '2022-04-15', 70.0),
(22, 104, '2022-04-20', 80.0);

✅ 3. 执行你的分析查询 SQL(统计订单数大于 5 的客户)

我们需要选择一个能覆盖典型逻辑运算的 SQL 语句,例如选择、投影、连接、聚合等。
sql

-- 推荐用于逻辑计划和优化器调试的 SQL

SELECT customer_id, COUNT(*) AS order_count

FROM orders

WHERE order_date >=  DATE  '2022-01-01'

GROUP  BY customer_id

HAVING  COUNT(*) >  5

ORDER  BY order_count DESC

LIMIT 10;

这个 SQL 涵盖以下关键节点:

  • Filter(WHERE)

  • Group By / Aggregation

  • Having

  • Projection

  • Order By + Limit

✅ 二、推荐的 GDB 调试设置流程

(gdb) b Parser::ParseQuery
Breakpoint 1 at 0x589200
(gdb) b Binder::Bind
Breakpoint 2 at 0xa8ff40 (40 locations)
(gdb) b Planner::CreatePlan
Breakpoint 3 at 0xb1da90 (2 locations)
(gdb) b Optimizer::Optimize
Breakpoint 4 at 0xa2ae90

在上面的4处设置断点。

✅ 三、查看逻辑计划过程

主要的过程就是参考https://github.com/duckdb/duckdb/blob/main/src/main/client_context.cpp文件里面的353行的ClientContext::CreatePreparedStatementInternal函数,里面记录了执行的过程,从1️⃣ Create Logical Plan->2️⃣ 优化逻辑计划->3️⃣ 构建物理计划->4️⃣ 初始化执行器->5️⃣ 开始执行。
逻辑计划的执行入口在/src/planner/planner.cpp的32行的Planner::CreatePlan函数内,首先查看duckdb逻辑计划构建阶段的入口。

Planner::CreatePlan函数

首先进入到同名的Planner::CreatePlan上面选择对应的statement->type类型,比如我这里选择的就是select类型。
Planner::CreatePlan函数地址:https://github.com/duckdb/duckdb/tree/main/src/planner 32行
在了解这个函数之前,先去了解逻辑计划的基类一下class LogicalOperator,简单图示:

class LogicalOperator
    ├── LogicalOperatorType type         ← 节点类型(如 JOIN、GET、FILTER)
    ├── vector<LogicalOperator*> children← 子节点(树结构)
    ├── vector<Expression*> expressions  ← 节点内表达式(如 WHERE 条件、SELECT 列)
    ├── vector<LogicalType> types        ← 返回列类型
    ├── idx_t estimated_cardinality      ← 行数估计
    ├── virtual ResolveTypes() = 0       ← 推断返回类型(由子类实现)
    ├── ToString()/Print()               ← 输出逻辑计划树
    └── Cast<T>()                        ← 类型安全的子类转换
  • LogicalOperatorType type
    枚举类型,表示逻辑节点的类别:这个字段可以让优化器、渲染器、执行器知道当前是哪个操作。
  • ② vector<unique_ptr> children
    当前逻辑节点的子节点,构成逻辑计划树。
    比如:一个 LogicalFilter 会有一个子节点是 LogicalGet。
  • ③ vector<unique_ptr> expressions
    表达式节点,比如:
    WHERE x > 10 → x > 10 是一个表达式
    SELECT x + y → x + y 是一个表达式
    对于某些逻辑节点(如 LogicalProjection),表达式就是它的输出列表达式。
  • ④ vector types
    当前逻辑节点最终会输出哪些列的类型。
    是在 ResolveOperatorTypes() 或 ResolveTypes() 中由具体逻辑算子设置。
  • ⑤ estimated_cardinality 和 has_estimated_cardinality
    用于估算输出行数,供优化器做选择(如连接顺序)
  • ⑥ ResolveOperatorTypes() + ResolveTypes()
    比如:LogicalProjection 会根据表达式类型推导出列类型
    LogicalAggregate 会根据聚合函数决定返回类型
  • ⑦ ToString() 和 Print()
    用于调试或 explain 时打印逻辑计划结构
  • ⑧ Copy(), Serialize(), Cast() 等
    提供计划节点的复制、序列化能力
    Cast() 用于安全地将 LogicalOperator 转为具体类型(如 LogicalJoin)
🧠 总结一句话

LogicalOperatorDuckDB 中逻辑执行计划的树节点基类,所有 SELECT、JOIN、FILTER 等操作都继承它,通过组合形成一棵逻辑计划树,被优化器和物理计划器使用。

现在再看duckdb的逻辑计划:

🧩 [1] 获取查询性能分析器
cpp
auto &profiler = QueryProfiler::Get(context);

从当前客户端上下文 context 中获取查询性能分析器,用于记录各阶段耗时(比如绑定、优化等)。


🧩 [2] 获取命名

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haven-852

你的鼓励是对我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值