对于优化器,(主要是因为一直有很强的队友顶在前面)我自己并没有做过特别系统的学习,谈不上有太多一线经验,这里只整理一些我觉得有价值的资料和线索。

优化器基础

PostgreSQL 优化器的整体思路源自经典的 System R:基于统计信息做代价估算(CBO),在 join order 上,小规模场景主要依赖动态规划,而在 join 关系很多时则会回退到 GEQO。

对于 GP,则是在 PG 单机计划的基础上插入 motion(exchange)节点,把它改造成一个分布式并行计划。除了沿用 PG 的优化器之外,还可以选用著名的 ORCA 优化器:它基于更现代的 Volcano/Cascades 模型,采用自顶向下的规划方式。

优化器的整体架构和背景知识,可以参考 PolarDB 的两篇文章:<1><2>

深入代码

代码细节可以参考树杰的《PostgreSQL 技术内幕之查询优化》

★优化器代码执行的大致脉络如下:

standard_planner()      // 主入口
    subquery_planner()  // 逻辑重写
    ├─ 处理 CTE/MERGE/空FROM       
    ├─ 提升 SubLinks/Subqueries
    ├─ 统计 rtable 信息
    ├─ 预处理 RowMark
    ├─ 表达式预处理
    ├─ HAVING转移到WHERE
    ├─ OUTER JOIN简化
    ├─ 删除无用 RTE_RESULT
    └─ grouping_planner()   // plan主逻辑
        query_planner()     
        ├─ 处理 targetlist、jointree、placeholder
        ├─ 生成 joinlist
        ├─ 外连接和等价类处理
        ├─ 计算 pathkeys
        ├─ 处理 placeholder 依赖
        ├─ 移除无用 join
        ├─ 分发 placeholder
        ├─ 构建 lateral 信息
        ├─ 外键与 join 约束匹配
        ├─ OR 条件处理
        ├─ 扩展 appendrels
        └─ make_one_rel(root, joinlist) // join规划和路径生成 => RelOptInfo
    get_cheapest_fractional_path()  // 获取最优路径
    (gp独有步骤:调用cdbparallelize()将plan并行化)
    create_plan()   // 将path转换成plantree,封装成PlannedStmt返回给执行器

★函数栈调用示例(图片来源

image

★主要结构体

比如 QueryPathRelOptInfo 等,这里后续再单独补。

Discussion

在我目前的理解里,优化器最核心要解决两类问题:

  • 选择率估计:依赖统计信息。此前 GP 上很多客户 case,本质上都是统计信息不准导致的误判。
  • join order:PG 目前主要是动态规划 + GEQO,而更现代的做法可以参考 DPccp 论文

由于相关概念都比较抽象,想真正掌握优化器,必须结合具体 case 来看。这里有几篇 Doris 的优化经验文章,质量很高:<1><2>

Questions:

  1. 多表 join 的结果估计:例如两个表做 join,优化器如何估算结果行数?

    提示:必然要通过统计信息,具体需要那种?

  2. 代码练习:新增一个 plan 节点。

    可以考虑新增一个 SQL 语法 + 一个 plan 节点 + 一个执行器节点,完整走一遍 query 处理流程。