PostgreSQL查询代价估算(三)

本文介绍PostgreSQL中索引扫描与Tid扫描的成本估算方法,详细解析了cost_index与cost_tidscan函数的工作原理,包括索引扫描的启动成本、运行成本计算及Tid扫描的具体流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.1.1.1    索引扫描的代价:cost_index

函数功能:

某个索引获取元组的花费。不同类型的索引,有着不同的基本属性值的(如选择率、索引相关度等)花费估算函数(在pg_am.hamcostestimate列上定义)。索引扫描代价,在PostgreSQL中分为普通索引的扫描代价、只读索引扫描的代价。只读索引扫描因为不需要访问数据文件,所以可以减少花费。不管是哪种方式,基本原理都遵从代价估算模型“总代价 = I/O代价 + CPU代价”;局部差别,只是根据实际的扫描过程所做的工作,对每个主要步骤做花费计算。索引扫描有别于顺序扫描,是增加了对索引文件的访问,这项花费需要计算。

 

代码分析:

void

cost_index(IndexPath *path, PlannerInfo *root, double loop_count)

{......

       if (!enable_indexscan)

              startup_cost += disable_cost;

 

    //每一类索引都有自己的花费基本值估算函数,如btree索引的花费估算函数是btcostestimate函数。对于不同的表达式,

       OidFunctionCall7(index->amcostestimate,

                                    PointerGetDatum(root),

                                    PointerGetDatum(path),

                                    Float8GetDatum(loop_count),

                                    PointerGetDatum(&indexStartupCost), //索引启动花费

                                    PointerGetDatum(&indexTotalCost), //索引总花费

                                    PointerGetDatum(&indexSelectivity),//索引选择率

                                    PointerGetDatum(&indexCorrelation)); //索引相关度

......

       startup_cost += indexStartupCost;

       run_cost += indexTotalCost - indexStartupCost;

 

       //估算表元组数:基于索引的选择率与元组个数的乘积是能取到的元组个数

       tuples_fetched = clamp_row_est(indexSelectivity * baserel->tuples);

 

       //从表空间取数据页的IO花费:随机读取的花费、顺序读取的花费

       get_tablespace_page_costs(baserel->reltablespace,

                                                   &spc_random_page_cost,

                                                   &spc_seq_page_cost);

 

       //根据索引和索引使用次数,在区分“索引扫描”和“只索引扫描”方式下,计算索引页的IO花费

       if (loop_count > 1)

       {

              //最大IO计算方式:

              //计算依据:元组数、循环次数、基表页面数、索引页面数

              pages_fetched = index_pages_fetched(tuples_fetched * loop_count,

                                          baserel->pages, (double) index->pages, root);

              //如果是“只索引扫描”,则重新计算页面获取数(因为不需要做表的扫描,只扫描索引的页面即可)

              if (indexonly)

                     pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac));

              max_IO_cost = (pages_fetched * spc_random_page_cost) / loop_count;

 

              //最小IO计算方式:

              //考虑了索引存在选择率的问题

              //计算依据:索引选择率、元组数、循环次数、基表页面数、索引页面数

              pages_fetched = ceil(indexSelectivity * (double) baserel->pages);

              pages_fetched = index_pages_fetched(pages_fetched * loop_count,

                                          baserel->pages, (double) index->pages, root);

//如果是“只索引扫描”,则重新计算页面获取数

              if (indexonly)

                     pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac));

              min_IO_cost = (pages_fetched * spc_random_page_cost) / loop_count;

       }

       else //道理同上,只是不需要考虑循环次数

       {……}

 

       csquared = indexCorrelation * indexCorrelation;

       run_cost += max_IO_cost + csquared * (min_IO_cost - max_IO_cost);

 

       //估算每个元组的CPU花费赋值给qpqual_cost

       cost_qual_eval(&qpqual_cost, list_difference_ptr(allclauses, path->indexquals), root);

                              

    //计算每个元组的CPU花费

       startup_cost += qpqual_cost.startup;

       cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;

 

       run_cost += cpu_per_tuple * tuples_fetched;//计算获取所有元组的CPU花费

 

       path->path.startup_cost = startup_cost;

       path->path.total_cost = startup_cost + run_cost;

       //最后的总的花费是:索引和基表限制条件的启动花费+总的IO花费(主要是索引页的IO花费)+总的CPU花费(元组)

}

 

调用关系图解:

PostgreSQL查询代价估算(三) - 那海蓝蓝 - 那海蓝蓝的博客
 

1.         cost_index函数被create_index_path函数调用,完成索引扫描的花费估算;

2.         cost_index函数被reparameterize_path函数调用,完成参数化路径的索引扫描的花费估算。

 

1.1.1.2     Tid扫描的代价估算:cost_tidscan

函数功能:

计算一个关系使用Tid扫描方式的花费。

 

代码分析:

Void cost_tidscan(Path *path, PlannerInfo *root, RelOptInfo *baserel, List *tidquals)

{

……

       //计算期望得到的元组个数(ntuples),分三种情况处理,tid分别是ScalarArrayOpExprCurrentOfExprCTID = something这三种格式

……

       //计算Tid条件的花费(值计算一次,其他的限制条件的花费需要为每个元组都计算一次)

       cost_qual_eval(&tid_qual_cost, tidquals, root);

//从表空间取数据页的IO花费:随机读取的花费

get_tablespace_page_costs(baserel->reltablespace, &spc_random_page_cost, NULL);

       //计算元组获取的IO花费

       run_cost += spc_random_page_cost * ntuples;

 

       //计算元组获取的CPU花费

       startup_cost += baserel->baserestrictcost.startup +

              tid_qual_cost.per_tuple;

       cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple -

              tid_qual_cost.per_tuple;

       run_cost += cpu_per_tuple * ntuples;

 

       path->startup_cost = startup_cost;

       path->total_cost = startup_cost + run_cost;

       //最后的总的花费是:基表限制条件的启动花费(包括tid条件花费)+总的IO花费+总的CPU花费

}

 

调用关系图解:

PostgreSQL查询代价估算(三) - 那海蓝蓝 - 那海蓝蓝的博客

1.         cost_tidscan函数被create_tidscan_path函数调用,直至被set_plain_rel_pathlist函数调用,用以完成单表tid扫描的花费估算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值