PostgreSQL码农集散地

AI辅助 PolarDB内核学习 - 24 优化器(plan模块) 入口代码 planner.c

AI辅助 PolarDB内核学习 - 24 优化器(plan模块) 入口代码 planner.c

以下从多维度对PostgreSQL查询优化器核心模块planner.c进行深度解析:

一、架构师视角 - 模块架构设计

Image


二、内核开发者视角 - 关键代码结构

2.1 核心数据结构

// 分组集数据(处理GROUPING SETS)  
typedefstruct {
    List       *rollups;        // 多层分组集合  
double      dNumHashGroups; // 哈希分组估算数  
    Bitmapset  *unhashable_refs;// 不可哈希列  
} grouping_sets_data;  

// 窗口函数处理上下文  
typedefstruct {
    WindowClause *wc;           // 窗口子句  
    List       *uniqueOrder;    // 唯一排序键  
} WindowClauseSortData;  

2.2 主流程(grouping_planner函数)

Image


三、DBA视角 - 优化器行为特征

3.1 LIMIT优化示例

EXPLAINSELECT * FROM orders ORDERBY order_date LIMIT100;  

优化器行为:

  1. preprocess_limit计算有效元组比例
  2. 优先选择带排序的索引扫描
  3. 当order_date有索引时,使用IndexScan+Limit
  4. 无索引时采用Sort+SeqScan

3.2 分组优化策略

SELECT customer_id, SUM(amount)   
FROM orders   
GROUPBY customer_id   
HAVINGSUM(amount) > 1000;  

优化器决策点:

  1. 使用哈希聚合还是排序聚合
  2. HAVING条件下推时机
  3. 分组列基数估算(dNumHashGroups)

四、关键算法深度解析

4.1 分组路径生成(create_ordinary_grouping_paths)

Image


4.2 窗口函数优化(create_one_window_path)

// 窗口函数处理核心逻辑  
staticvoidcreate_one_window_path(...){  
// 步骤1:确定窗口排序键  
    get_windowclause_sort_order(...);  

// 步骤2:生成排序路径  
if (window_has_unique_sort)  
        pathkeys = NIL; // 无需额外排序  
else
        add_sort_node(...);  

// 步骤3:添加窗口聚合节点  
    create_windowagg_path(...);  
}  

典型优化场景:

  • 当窗口函数使用UNIQUE排序键时,跳过显式排序
  • 多个窗口函数共享相同PARTITION BY时合并计算

五、并行查询处理

5.1 并行度决策

intplan_create_index_workers(Oid tableOid, Oid indexOid){  
// 基于表大小和配置参数计算  
    workers = Min(max_parallel_maintenance_workers,   
                 log2(rel_pages / 1000));  
return workers;  
}  

5.2 并行聚合优化

-- 原始执行计划  
Finalize GroupAggregate  
-> Gather Merge
   -> Partial HashAggregate  

-- 优化条件:  
1. can_partial_agg()返回true
2. 聚合函数支持并行(标记PARALLEL_SAFE)  
3. 无ORDERBY或GROUPINGSETS

六、扩展机制

6.1 优化器钩子

// 插件可注入的钩子函数  
typedef PlannedStmt *(*planner_hook_type) (Query *parse,...);  
extern PGDLLIMPORT planner_hook_type planner_hook;  

// 示例:修改查询树  
static PlannedStmt *my_planner(...){  
if (parse->commandType == CMD_SELECT)  
        add_dummy_where_clause(parse); // 注入自定义条件  
return standard_planner(...);  
}  

6.2 自定义扫描提供程序

通过planner.c中的preprocess_expression实现:

Node *preprocess_expression(...){  
if (is_custom_scan_expr(expr))  
        rewrite_custom_scan_expr(...);  
// 标准处理流程  
return expr;  
}  

七、诊断与调优

7.1 常见问题模式

现象
可能原因
检查点
未使用预期索引
成本估算偏差
检查pg_stats统计信息
并行度不足
配置参数限制
max_parallel_workers_per_gather
内存溢出
work_mem不足
观察EXPLAIN ANALYZE的峰值内存

7.2 性能调优参数

-- 关键配置示例  
SET random_page_cost = 1.1;     -- SSD优化  
SET work_mem = '64MB';          -- 提升排序/哈希性能  
SET parallel_setup_cost = 10;   -- 降低并行启动阈值  
SET enable_sort = off;          -- 强制使用哈希聚合  

通过深入理解planner.c的实现机制,开发者可定制优化策略,DBA能精准诊断执行计划问题,架构师可设计更高效的数据库应用模式。建议结合EXPLAIN (ANALYZE, VERBOSE)输出与源码实现进行对比分析。