PostgreSQL码农集散地

AI辅助 PolarDB内核学习 - 15 path(路径生成) 之 估算路径成本(costsize.c)代码

AI辅助 PolarDB内核学习 - 15 path(路径生成) 之 估算路径成本(costsize.c)代码

以下是针对 PolarDB for PostgreSQL 15 中 costsize.c 的详细解释,结合数据库优化器的核心逻辑和实际用例,并使用 Mermaid 图表辅助理解:

好的,我将尝试解读 src/backend/optimizer/path/costsize.c 文件,并提供尽可能详细和易于理解的解释。

文件概述:src/backend/optimizer/path/costsize.c

这个文件是 PostgreSQL 查询优化器中至关重要的一个组成部分。它的主要职责是:

  1. 估算查询执行路径的成本 (Cost Estimation):  为不同的查询执行计划(Paths)计算成本。  成本是优化器用来比较不同执行计划并选择最佳计划的关键指标。
  2. 估算查询结果的大小 (Size Estimation):  估算查询结果集的大小(行数和每行的大小)。  结果集大小对于成本估算至关重要,并且也影响后续操作的计划。
  3. 提供成本和大小相关的工具函数:  提供各种辅助函数,用于计算成本、调整成本、比较成本,以及处理与成本和大小相关的各种数据结构。

核心概念:

  • Path:  一个可能的查询执行计划。  例如,一个 Path 可以是使用索引扫描表,另一个 Path 可以是顺序扫描表。
  • Cost:  一个数值,表示执行一个 Path 的估计代价。  PostgreSQL 使用一个两部分组成的成本模型:
    • Startup Cost:  开始产生结果之前的成本(例如,打开表、初始化数据结构)。
    • Total Cost:  产生所有结果的总成本。
  • Rows:  估计的查询结果行数。
  • Width:  估计的每行结果的平均大小(以字节为单位)。
  • PlannerInfo:  一个包含查询优化器所需的所有信息的结构体,包括查询的语法树、关系信息、变量信息等等。
  • RelOptInfo:  一个表示关系(表、视图、子查询等)的结构体,包含关于关系的统计信息、访问路径等等。

主要数据结构:

  • Cost (typedef double):  成本值,通常是一个浮点数。
  • QualCost:包含启动成本和运行成本的结构体,用于表示 WHERE 子句的评估成本。
  • Path:抽象基类,表示一个可能的执行计划。  具体的 Path 类型包括:
    • SeqScanPath:顺序扫描 Path
    • IndexPath:索引扫描 Path
    • BitmapHeapPath:位图堆扫描 Path
    • NestLoopPath:嵌套循环连接 Path
    • MergeJoinPath:归并连接 Path
    • HashJoinPath:哈希连接 Path
    • 等等
  • RelOptInfo:表示一个关系(表、视图、子查询等)的结构体。  它包含关于关系的统计信息,以及可能的访问路径。

主要函数和功能:

以下是一些 costsize.c 中最重要的函数,以及它们的功能:

  • cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *rel):  估算顺序扫描一个关系的成本。  它使用关系(PG把表称为relation)的统计信息(例如,行数、页面数)来计算 I/O 成本和 CPU 成本。
  • cost_indexscan(Path *path, PlannerInfo *root, RelOptInfo *rel, ...):  估算使用索引扫描一个关系的成本。  它考虑了索引的类型、选择率、以及是否需要回表(访问堆表)等因素。
  • cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *rel, ...):  估算位图堆扫描的成本。
  • cost_nestloop(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, ...):  估算嵌套循环连接的成本。  它考虑了外层关系和内层关系的大小,以及内层关系是否需要重复扫描。
  • cost_mergejoin(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, ...):  估算归并连接的成本。  它考虑了排序成本和归并成本。
  • cost_hashjoin(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, ...):  估算哈希连接的成本。  它考虑了构建哈希表的成本和探测哈希表的成本。
  • set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel):  设置基本关系(表)的大小估计。  它使用统计信息来估计关系的行数和每行的大小。
  • set_rel_size_estimates(PlannerInfo *root, RelOptInfo *rel):  设置关系(包括连接结果)的大小估计。  它根据关系的类型和操作(例如,连接、过滤)来调整大小估计。
  • estimate_num_groups(PlannerInfo *root, RelOptInfo *rel, List *groupClauses): 估计分组操作产生的组数。
  • estimate_row_width(TupleDesc tupdesc, List *targetlist): 估计一行数据的宽度。
  • clauselist_selectivity(PlannerInfo *root, List *clauses, Oid relid, JoinType jointype, SpecialJoinInfo *sjinfo):  估计一个 WHERE 子句的选择率(即,满足条件的行数占总行数的比例)。  这是成本估算的关键部分。
  • restriction_selectivity(PlannerInfo *root, Expr *clause, Oid relid, ...):  估计一个简单表达式的选择率。
  • join_selectivity(PlannerInfo *root, Expr *clause, Oid relid1, Oid relid2, JoinType jointype, SpecialJoinInfo *sjinfo):  估计一个连接表达式的选择率。
  • adjust_scalar_function_cost(PlannerInfo *root, FuncExpr *funcexpr, Cost *cost): 调整标量函数的成本。
  • compare_path_costs(const Path *path1, const Path *path2, Cost *costdiff): 比较两个 Path 的成本。

成本估算的步骤:

  1. 统计信息收集:  PostgreSQL 使用 ANALYZE 命令收集关于表的统计信息,例如行数、页面数、唯一值数量、直方图等等。  这些统计信息存储在 pg_statistic 系统表中。
  2. 大小估算:  优化器首先估算查询中涉及的每个关系的大小(行数和每行的大小)。  这通常基于统计信息。
  3. 选择率估算:  优化器估算 WHERE 子句的选择率。  选择率表示满足条件的行数占总行数的比例。  这通常基于统计信息和一些启发式规则。
  4. 成本计算:  对于每个可能的执行计划(Path),优化器使用大小估计和选择率来计算成本。  成本包括 I/O 成本、CPU 成本和通信成本(对于并行查询)。
  5. 路径比较:  优化器比较所有可能的执行计划的成本,并选择成本最低的计划。

代码解读示例:cost_seqscan 函数

/*  
 * cost_seqscan  
 *      Determine the cost of a sequential scan.  
 */

void
cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *rel)
{  
    Cost        startup_cost = 0;  
    Cost        total_cost;  
double      dpages;  

/*  
     * We estimate cost as follows:  
     *  
     * startup_cost: just the fixed costs of starting up the executor.  
     *  
     * per-tuple CPU: cpu_tuple_cost  
     *  
     * per-page I/O: seq_page_cost  
     *  
     * total_cost: startup_cost + cpu_tuple_cost * #tuples +  
     *              seq_page_cost * #pages  
     *  
     * We use the relation's simple-relation-size estimates for the number of  
     * tuples and pages to be retrieved.  Note in particular that the number of  
     * pages is the physical size of the relation, even if we expect to scan  
     * only a fraction of the tuples in it.  
     */

/*  
     * If the relation is small enough, we can perform a parallel seqscan.  
     * Otherwise, we don't consider parallel seqscan.  
     */

if (rel->rows < root->parallel_tuple_cost)  
        path->parallel_aware = true;  

/*  
     * Estimate I/O cost.  (See comments in cost_indexscan().)  
     */

    dpages = rel->pages;  

    total_cost = startup_cost +  
        cost_cpu(root, cpu_tuple_cost, rel->rows) +  
        dpages * root->config->seq_page_cost;  

/*  
     * We charge a small amount of startup cost.  We assume that the relation is  
     * already open, so this is just the cost of initializing the scan.  
     */

    path->startup_cost = startup_cost;  
    path->total_cost = total_cost;  
}  

代码解读:

  1. 函数签名:

  • void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *rel)
  • path: 指向 Path 结构的指针,表示顺序扫描的 Path。
  • root: 指向 PlannerInfo 结构的指针,包含查询优化器的全局信息。
  • rel: 指向 RelOptInfo 结构的指针,表示要扫描的关系(表)。
  • 成本估算逻辑:

    • Startup Cost:  顺序扫描的启动成本通常很低,因为它只需要初始化扫描。  这里设置为 0。
    • I/O Cost:  I/O 成本是顺序扫描的主要成本。  它等于 seq_page_cost(顺序页面读取的成本)乘以关系的页面数 (rel->pages)。
    • CPU Cost:  CPU 成本是处理每个元组的成本。  它等于 cpu_tuple_cost(处理一个元组的成本)乘以关系的行数 (rel->rows)。
    • Total Cost:  总成本是启动成本、I/O 成本和 CPU 成本的总和。
  • 并行扫描:

    • 如果关系的行数小于 root->parallel_tuple_cost,则认为该关系足够小,可以进行并行顺序扫描。  path->parallel_aware 设置为 true。
  • 设置 Path 的成本:

    • path->startup_cost 设置为启动成本。
    • path->total_cost 设置为总成本。

    总结:

    costsize.c 文件是 PostgreSQL 查询优化器的核心组件,负责估算查询执行计划的成本和大小。  它使用统计信息、选择率估算和成本模型来比较不同的执行计划,并选择最佳计划。  理解这个文件对于理解 PostgreSQL 查询优化器的工作原理至关重要。

    进一步学习:

    • 阅读源代码:  仔细阅读 src/backend/optimizer/path/costsize.c 文件,并尝试理解每个函数的实现细节。
    • 阅读 PostgreSQL 文档:  PostgreSQL 文档中关于查询优化器的部分提供了关于成本估算的更多信息。
    • 调试查询优化器:  使用 EXPLAIN ANALYZE 命令来查看查询优化器选择的执行计划,并分析成本估算是否合理。
    • 阅读相关论文:  有很多关于查询优化和成本估算的学术论文,可以帮助你更深入地理解这个领域。

    希望这个解读对你有所帮助!  如果你有任何其他问题,请随时提出。

    提示

    解读 src/backend/optimizer/path/costsize.c    

    PolarDB & PostgreSQL 15 差异

    git diff -u 50d3d22baba63613d1f1406b2ed460dc9b03c3fc f5e7493819e370d30ac2047c68c21c9fb03ce4a0 -- src/backend/bootstrap/costsize.c    

    差异分析待补充.

     以上内容基于DeepSeek、QwQ及诸多AI生成, 轻微人工调整, 感谢杭州深度求索人工智能、阿里云等公司. 

     AI 生成的内容请自行辨别正确性, 当然也多了些许踩坑的乐趣, 毕竟冒险是每个男人的天性.