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 查询优化器中至关重要的一个组成部分。它的主要职责是:
估算查询执行路径的成本 (Cost Estimation): 为不同的查询执行计划(Paths)计算成本。 成本是优化器用来比较不同执行计划并选择最佳计划的关键指标。 估算查询结果的大小 (Size Estimation): 估算查询结果集的大小(行数和每行的大小)。 结果集大小对于成本估算至关重要,并且也影响后续操作的计划。 提供成本和大小相关的工具函数: 提供各种辅助函数,用于计算成本、调整成本、比较成本,以及处理与成本和大小相关的各种数据结构。
核心概念:
Path: 一个可能的查询执行计划。 例如,一个 Path 可以是使用索引扫描表,另一个 Path 可以是顺序扫描表。 Cost: 一个数值,表示执行一个 Path 的估计代价。 PostgreSQL 使用一个两部分组成的成本模型: Startup Cost: 开始产生结果之前的成本(例如,打开表、初始化数据结构)。 Total Cost: 产生所有结果的总成本。 Rows: 估计的查询结果行数。 Width: 估计的每行结果的平均大小(以字节为单位)。 PlannerInfo: 一个包含查询优化器所需的所有信息的结构体,包括查询的语法树、关系信息、变量信息等等。 RelOptInfo: 一个表示关系(表、视图、子查询等)的结构体,包含关于关系的统计信息、访问路径等等。
主要数据结构:
Cost(typedef double): 成本值,通常是一个浮点数。QualCost:包含启动成本和运行成本的结构体,用于表示 WHERE 子句的评估成本。Path:抽象基类,表示一个可能的执行计划。 具体的 Path 类型包括:SeqScanPath:顺序扫描 PathIndexPath:索引扫描 PathBitmapHeapPath:位图堆扫描 PathNestLoopPath:嵌套循环连接 PathMergeJoinPath:归并连接 PathHashJoinPath:哈希连接 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 的成本。
成本估算的步骤:
统计信息收集: PostgreSQL 使用 ANALYZE命令收集关于表的统计信息,例如行数、页面数、唯一值数量、直方图等等。 这些统计信息存储在pg_statistic系统表中。大小估算: 优化器首先估算查询中涉及的每个关系的大小(行数和每行的大小)。 这通常基于统计信息。 选择率估算: 优化器估算 WHERE 子句的选择率。 选择率表示满足条件的行数占总行数的比例。 这通常基于统计信息和一些启发式规则。 成本计算: 对于每个可能的执行计划(Path),优化器使用大小估计和选择率来计算成本。 成本包括 I/O 成本、CPU 成本和通信成本(对于并行查询)。 路径比较: 优化器比较所有可能的执行计划的成本,并选择成本最低的计划。
代码解读示例: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;
}
代码解读:
函数签名:
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 生成的内容请自行辨别正确性, 当然也多了些许踩坑的乐趣, 毕竟冒险是每个男人的天性.