openGauss

开源数据库

openGauss社区官网

开源社区

shell
title: '单表代价估计初探'
date: '2024-03-19'
category: 'blog'
tags: ['代价估计']
archives: '2024-03-19'
author: 'zhoujing'
summary: '通过分析一个单表查询的执行计划,了解sequence scan, index scan, sort等代价估计逻辑'
times: '19:30'

在分析一个分区表索引扫描,顺序扫描及排序的代码的过程中,学习并分析了一下相关代码估计代码。做个记录及分享。

1.创建分区表

create table top1 (

pk_report_asset character(32) NOT NULL,

c1 int, c2 int,c3 int, is_deleted smallint,

org_code int NOT NULL)

PARTITION BY LIST (org_code)

(

PARTITION p1 VALUES (1) TABLESPACE pg_default,

PARTITION p2 VALUES (2) TABLESPACE pg_default,

PARTITION p3 VALUES (3) TABLESPACE pg_default,

PARTITION p4 VALUES (4) TABLESPACE pg_default,

PARTITION p5 VALUES (5) TABLESPACE pg_default,

PARTITION p6 VALUES (6) TABLESPACE pg_default,

PARTITION p7 VALUES (7) TABLESPACE pg_default,

PARTITION p8 VALUES (8) TABLESPACE pg_default,

PARTITION p9 VALUES (9) TABLESPACE pg_default,

PARTITION p10 VALUES (10) TABLESPACE pg_default

);

2.增加约束

ALTER TABLE top1

ADD CONSTRAINT pk_top1 PRIMARY KEY (pk_report_asset, org_code);

3.产生数据库

insert into top1 values

(generate_series(1,1000000),

generate_series(1,100),generate_series(1,1000),generate_series(1,10000),

0,

generate_series(1,10)

);

4.收集统计信息

ANALYZE;

5.比较如下SQL语句的执行代价:

explain (analyze,timing) select * from top1 order by PK_REPORT_ASSET desc;

explain(analyze,timing)

select /*+ indexscan(top1 pk_top1) */ * from top1 order by PK_REPORT_ASSET desc

可以看到seqscan的估算代价20310比index scan代价1230199少很多,而实际运行时间729和2067,相差没这么大。而排序代价对于两种计划差别不大,详见下表。

estimation costactual cost
seq scan20310729
seq sort1841904599
seq total2045005328
index scan12301992067
index sort1841915352
index total14143907419

所以下文主要分析一下,影响相关代码估计的因素,及其对应估算方法。

首先需要如下统计信息或配置信息进行收集:

cost estimation输入

统计信息解释value获取方式
Nindex,pageindex pages,analyze获得12551select relpages,reltuples from pg_class where relname = 'pk_top1';
Nindex,tupleindex tuples,analyze获得1000000
Ntable,pagestable pages,analyze获得10310select relpages,reltuples from pg_class where relname = 'top1';
Ntable,tupletable tuples,analyze获得1000000
H indexindex 高度1内存中结构获得
var correlationcolumn correlation,列值排序与存储之间的相关性, analyze获得0.0865771select correlation from pg_stats where tablename = 'top1' and attname = 'pk_report_asset';
index correlationindex correlation,列值排序与存储之间的相关性,用于估计index访问造成的table io cost0.064932825index column number>1时候,0.75*column correlation,否则=column correlation
cpu_operator_costpostgresql.conf中配置0.0025
cpu_index_tuple_costpostgresql.conf中配置0.005
cpu_tuple_costpostgresql.conf中配置0.01
random_page_costpostgresql.conf中配置4
seq_page_costpostgresql.conf中配置1
qual_op_costpostgresql.conf中配置00.0025,此处没有qualification,故为0
comparison_cost2*cpu_operator_cost0.005
doppostgresql.conf中配置1
sort_mempostgresql.conf中配置65536
Nsort,tuple计算获得1000000
limitsql语句中包含720000
selectivity根据统计信息计算所得,此例中没有过滤条件故为11

sequence access

顺序扫描,也叫表扫描,顺序读取表中所有记录。计算公式如下表。start_up cost为0。

若打开并行,即dop>1, 则相关代价会除以并行数,此处为默认值1.

相关代码见costsize.cpp::cost_seqscan()

seqscan cost计算公式估计结果
cpu_run_cost(cpu_tuple_cost+qual_op_cost)*Ntable,tuple10000
disk_run_costseq_page_cost*Ntable, page10310
seqscan total costcpu_run_cost+disk_run_cost20310

index scan

index cost包括startup cost, index io+cpu cost,以及对应表的cpu+io cost几部分。

一般B树索引,是通过btcostestimate_internal这个函数计算indexTotalCost的,包含index cpu cost, index page cost及start up cost。该计划中indexTotalCost为55204.3

如果index scan可以访问到所有的列,也就不需要估计table的cpu 及io cost了,但本例中,index只包含两列,需要再访问表获取所有列的信息的。计算table io cost,需要根据index correlation来分析最好及最坏情况下table io的访问数目。再根据公式计算最终table io cost。该例子中min_io_cost为correlation为1时,table io为所有表页面顺序访问代价。max_io_cost为所有页面均为随机访问的代价,根据公式:max_io_cost+indexCorrelation* indexCorrelation*(min_io_cost-max_io_cost)

计算最终table io cost, 为1164994。已经远大于sequence scan代价。

index scan cost计算公式估计结果
start-up cost{ceil(log2(Nindex,tuple))+(Hindex+1)*50}*cpu_operator_cost0.3
index cpu costselectivity * Nindex,tuple *(cpu_index_tuple_cost+qual_op_cost)5000
table cpu costselectivity* Ntable,tuple *cpu_tuple_cost10000
index io costceil(Selectivity* Nindex,page) *random_page_cost50204
table io costmax_io_cost+indexCorrelation* indexCorrelation*(min_io_cost-max_io_cost)1164994.934
Npages,(临时计算),考虑buffer cache的情况,在uncorrelated case下计算table pagesindex_pages_fetched()apply the Mackert and Lohman formula 线性计算table io pages292471
max_io_cost(临时计算),最差情况下所有table页都是随机访问max_io_cost=Npages*random_page_cost1169884
min_io_cost(临时计算), 最好情况下所有table页按照顺序访问min_io_cost=1* random_page_cost+(ceil(selectivity* Npage)-1)*seq_page_cost10313
indexTotalCost(临时计算)indexTotalCost(临时计算)=index cpu cost+index io cost+ start-up cost55204.3
total_cost1230199.234

sort cost

再看下sort cost。 本例子中,每个partition如果使用index访问,可以保证每个parition按照index key也就是PK_REPORT_ASSET 做了排序。最终结果集按照PK_REPORT_ASSET 排序后是否可以省略sort的代价呢?

这涉及到排序的实现方法。

在排序记录能全部放在sort memory中时,会使用快排序。在不能放在sort memory中时,会使用归并排序。本例中查询及排序的记录数较多,不能全部在sort memory中进行,故使用了归并排序。而归并排序的时间复杂度是O(Nlog2N)。即使所有partition已经按照PK_REPORT_ASSET排好序,最终结果需要将所有partition按照PK_REPORT_ASSET重新排序。时间复杂度仍为O(Nlog2N) 。

下面来看下具体sort cost的计算

sort cost计算公式估算代价
input_sortsort之前的花费20310
start-up costcomparison_cost* Nsort*log2Nsort202000.8428
run costcpu_operator_cost*Nsort2500
io trafficif(output_bytes>sort_mem_bytes) ,tape stype merge algorithm2* relsize * ceil(logM(p/(2*sort_mem)))82033
total cost204500.8428

而无论sequence scan还是index scan的estimation cost及actual cost相差并不大。

小结

从上述分析我们看到,sort cost针对sequence scan与index scan并没有降低。而使用index scan会造成Npages较Ntable,pages增大很多,并且默认情况下index scan在correlation不是1的情况下产生更多的random io。而默认情况下 random_page_cost: seq_page_cost = 4:1 这个参数对SSD磁盘可能造成index代价估计过高。在调整random_page_cost=1后,估算代价与实际代价相近。但仍然会选择sequence scan。