pg 统计信息及索引cost 简介

pg 统计信息及索引cost 简介

pg 统计信息及索引cost 简介

单列的统计信息

单列的统计信息,通过do_analyze_rel 函数进行生成,主要分为两个部分:

  1. 数据采样
  2. 数据统计

数据采样

随机的采取表中的一部分数据进行分析,如果没有指定采样数(一般都没有),使用default_statistics_target (默认100)进行计算,采样容量为 300 * default_statistics_target (默认30000)。

根据 SIGMOD98 中的论文 Random sampling fo r histogram construction: how much is enough 可以得出这个这个已足够,当当表很大是,仍需调大default_statistics_target 。

采样算法采样两阶段算法,第一阶段采样使用S算法对表中的页面进行随机采样,第二个阶段使用 Z(Vitter)算法,它在第一阶段采样出来的页面的基础上对元组进行采样。

数据采样的两个阶段采用不同的算法是因为当对一个表进行统计分析的时候,它的页面数(块数)是可以准确获得到的,也就是说页面采样是在己知总体容量的基础上进行的。而第二阶段的 Z(Vitter)算法是一种蓄水池算法,它主要解决的是在不知道总体容量的情况下如何进

对于外部表:
  • pg: 通过AnalyzeForeignTable 函数获取采样外部表的函数acquirefunc(没有则warning,同时也获取relpages),然后调用函数获取采样的行,然后对数据进行统计
  • opengauss :通过AnalyzeForeignTable 函数获取采样外部表的函数acquirefunc(没有则warning,同时也获取relpages),但gauss 不使用acquirefunc 获取行, 而是通过AcquireSampleRows采样行(只支持obs,hdfs, obs_cvs, oracle_fdw,pg_fdw,mot,RELKIND_STREAM?支持其他的需要修改代码)

数据统计

分析各个列的统计信息, 对于表达式索引,也会计算表达式值并统计信息。

统计数据会存在pg_statistic 表中,

pages/tuples 会计入对应的表和索引中(Update pages/tuples stats in pg_class, 默认表和索引的行数相同,除非是partial index - 带where条件的索引 )

索引不会使用采样到的pages ,因为这个pages 是表的pages. 而是使用GetOneRelNBlocks 获取的blocks, 作为pages. 对于外表就是0

索引cost 计算

cpu 和 io , 选择率用来确定行数或索引项数

对于seqscan, 选择率用来确定 返回的行数

对于索引扫描,会调用索引的amcostestimate 函数计算索引cost和索引项数目,索引条件的选择率(其实就是列的选择率)用来确定扫描到的索引项数,会基于这个索引项数确定对堆表的io 和cpu cost (pg 索引不是聚簇索引,需要回表查询,除非indexonly)

c 复制代码
    amcostestimate:
    
    /*
     * @param loop_count 索引作为内表时,需要循环的次数
     * @[out]indexStartupCost 索引扫描自身的启动代价
     * @[out]indexTotalCost 索引扫描自身的整体代价
     * @[out]indexSelectivity 索引扫描的选择率
     * @[out]indexCorrelation 索引的相关系数
     * @[out]indexPages 索引page 数,opengauss 没有
     * 
     */
    void (*amcostestimate)(PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost,
                               Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation,double *indexPages)
    

在btree 中,启动代价为约束中的表达式代价,即计算所有的表达式值的代价(因为在每次扫描前需要计算表达式的值,右值)。

总代价为 io代价和 cpu代价的和。

  • io代价: 索引扫描的执行次数取决于外表的元组数和是否有ScalarArrayOpExpr(有那么num_sa_scans也是扫描次数),所以num_scans = num_sa_scans * num_outer_scans;因为多次扫描可能导致数据被缓存,所以通过如下方式计算IO cost:
c 复制代码
        /*   
        对于外表:
        在pg 中,index->pages 为0
        在 opengauss 中,在 build_simple_rel 中会调用 set_local_rel_size,最终调用clamp_row_est 会把index->pages 转换为1, 也即外表上的索引 index->pages 为1
        */
        
          if (index->pages > 1 && index->tuples > 1)
              numIndexPages = ceil(numIndexTuples * index->pages / index->tuples);
          else
              numIndexPages = 1.0;   // numIndexPages 如果小于1 会被设置为1,对于外表即为1
                
        
        
                double		pages_fetched;
        
        		/* total page fetches ignoring cache effects */
        		pages_fetched = numIndexPages * num_scans;
        
        		/* use Mackert and Lohman formula to adjust for cache effects */
        		pages_fetched = index_pages_fetched(pages_fetched,
        											index->pages,
        											(double) index->pages,
        											root);
        
        		/*
        		 * Now compute the total disk access cost, and then report a pro-rated
        		 * share for each outer scan.  (Don't pro-rate for ScalarArrayOpExpr,
        		 * since that's internal to the indexscan.)
        		 */
        		indexTotalCost = (pages_fetched * spc_random_page_cost)
        			/ num_outer_scans;

非多次扫描:indexTotalCost = numIndexPages * spc_random_page_cost;

  • CPU cost:
c 复制代码
        qual_op_cost = cpu_operator_cost *(list_length(indexQuals) + list_length(indexOrderBys));
        
        indexTotalCost += qual_arg_cost;  //  加上start_cost,qual_arg_cost即start cost
        indexTotalCost += numIndexTuples * num_sa_scans * (cpu_index_tuple_cost + qual_op_cost);  // 加上每个索引项的cpu cost
        
        
        opengauss 没有下面的cost:
        b tree查找非叶子节点的cost:
        	if (index->tuples > 1)		/* avoid computing log(0) */
        	{
        		descentCost = ceil(log(index->tuples) / log(2.0)) * cpu_operator_cost;
        		costs.indexStartupCost += descentCost;
        		costs.indexTotalCost += costs.num_sa_scans * descentCost;
        	}
         
         
         b tree对节点的page 进行二分查找的cost:
         	descentCost = (index->tree_height + 1) * 50.0 * cpu_operator_cost;
        	costs.indexStartupCost += descentCost;
        	costs.indexTotalCost += costs.num_sa_scans * descentCost;
            
         
  • 相关系数:单列的通过pg_statistic 获取

行数确定

通过调用get_relation_info (add_base_rels_to_query)获取表及表上索引的page 和行数, 对于表调用estimate_rel_size 获取,对于非部分索引,page 通过RelationGetNumberOfBlocks 获取, tuples 即为表的tuples。 部分索引通过estimate_rel_size 获取,但如果获取的行数大于表的行数,则tuples 设置为表行数。

estimate_rel_size 会去获取关系的blocks ,然后计算pages 和tuples, 之后在make_one_rel 中会调用set_base_rel_sizes 调整rows, 宽度

  • 外表:
c 复制代码
        static void
        set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
        {
        	/* Mark rel with estimated output rows, width, etc */
        	set_foreign_size_estimates(root, rel);
        
        	/* Let FDW adjust the size estimates, if it can */
        	rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);
        
        	/* ... but do not let it set the rows estimate to zero */
        	rel->rows = clamp_row_est(rel->rows);
        
        	/* also, make sure rel->tuples is not insane relative to rel->rows */
        	rel->tuples = Max(rel->tuples, rel->rows);
        }
  • 普通表:
c 复制代码
        void
        set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
        {
        	double		nrows;
        
        	/* Should only be applied to base relations */
        	Assert(rel->relid > 0);
        
        	nrows = rel->tuples *
        		clauselist_selectivity(root,
        							   rel->baserestrictinfo,
        							   0,
        							   JOIN_INNER,
        							   NULL);
        
        	rel->rows = clamp_row_est(nrows);
        
        	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
        
        	set_rel_width(root, rel);
        }
相关推荐
kejijianwen1 小时前
JdbcTemplate常用方法一览AG网页参数绑定与数据寻址实操
服务器·数据库·oracle
编程零零七1 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
高兴就好(石4 小时前
DB-GPT部署和试用
数据库·gpt
这孩子叫逆5 小时前
6. 什么是MySQL的事务?如何在Java中使用Connection接口管理事务?
数据库·mysql
Karoku0665 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
码农郁郁久居人下5 小时前
Redis的配置与优化
数据库·redis·缓存
MuseLss6 小时前
Mycat搭建分库分表
数据库·mycat
Hsu_kk7 小时前
Redis 主从复制配置教程
数据库·redis·缓存
DieSnowK7 小时前
[Redis][环境配置]详细讲解
数据库·redis·分布式·缓存·环境配置·新手向·详细讲解
程序猿小D7 小时前
第二百三十五节 JPA教程 - JPA Lob列示例
java·数据库·windows·oracle·jdk·jpa