PostgreSQL 优化器行评估算法

3 minute read

背景

了解本文需要先了解一下PostgreSQL的统计信息表pg_class,pg_stats。可以查看我前面写的

PostgreSQL Statistics and Query Explain Introduction

接下来先看一个PostgreSQL EXPLAIN语句的输出:

https://www.postgresql.org/docs/10/static/planner-stats-details.html

pic

注意看图中的rows=114688,PostgreSQL是怎么来算这个值的,这里简单的写一下我对PostgreSQL行评估的理解,有兴趣的朋友可以线下交流,同时这对了解PostgreSQL的代码也是有帮助的。

创建测试表,写入测试记录:

create table tbl_test1 (id serial8 primary key,name varchar(32));  
  
create table tbl_test2 (id serial8 primary key,name varchar(32));  

插入测试记录

insert into tbl_test1 (name) values ('a'),('b'),('b'),('c'),('c'),('c'),('d'),('d'),('d'),('d'),('e'),('e'),('e'),('e'),('e'),('f'),('f'),('f'),('f'),('f'),('f'),('g'),('g'),('g'),('g'),('g'),('g'),('g');  
  
insert into tbl_test1(name) select name from tbl_test1;  
  
…………….  
bj_dsm_0=> insert into tbl_test2(name) select name from tbl_test1;  
  
INSERT 0 14336  
  
bj_dsm_0=> insert into tbl_test2(name) select name from tbl_test1;  
  
INSERT 0 14336  
  
bj_dsm_0=> select count(*) from tbl_test1;  
  
count  
  
——-  
  
14336  
  
(1 row)  
  
    
bj_dsm_0=> select count(*) from tbl_test2;  
  
count  
  
——-  
  
28672  
  
(1 row)  

再插入几条稀有记录

bj_dsm_0=> insert into tbl_test1 (name) values ('digoal');  
  
INSERT 0 1  
  
bj_dsm_0=> insert into tbl_test2 (name) values ('digoal');  
  
INSERT 0 1  

查看测试记录

bj_dsm_0=> select * from tbl_Test1 limit 29;  
id | name  
—-+——  
1 | a  
2 | b  
3 | b  
4 | c  
5 | c  
6 | c  
7 | d  
8 | d  
9 | d  
10 | d  
11 | e  
12 | e  
13 | e  
14 | e  
15 | e  
16 | f  
17 | f  
18 | f  
19 | f  
20 | f  
21 | f  
22 | g  
23 | g  
24 | g  
25 | g  
26 | g  
27 | g  
28 | g  
29 | a  
(29 rows)  

更新统计信息

analyze tbl_test1;  
  
analyze tbl_test2;  

例一:

bj_dsm_0=> explain select * from tbl_test1;  
  
QUERY PLAN  
  
—————————————————————-  
  
Seq Scan on tbl_test1  (cost=0.00..221.36 rows=14336 width=10)  
  
(1 row)  

PostgreSQL要得出rows=14336,首先得到真实的pages,然后去比较pg_class中的relpages,如果一致的话直接取出pg_class中的reltuples.如果不一致,PostgreSQL会根据真实的pages值自动计算出reltuples。得到的rows=reltuples。

我们来通过查看pg_class验证一下:

bj_dsm_0=> select relname,relpages,reltuples from pg_class where relname='tbl_test1';  
  
relname  | relpages | reltuples  
  
———–+———-+———–  
  
tbl_test1 |       78 |     14336  

完全正确。

例二:

bj_dsm_0=> explain select * from tbl_test1 where id < 1000;  
  
QUERY PLAN  
  
————————————————————————————-  
  
Index Scan using tbl_test1_pkey on tbl_test1  (cost=0.00..43.52 rows=1044 width=10)  
  
Index Cond: (id < 1000)  
  
(2 rows)  

我们查看一下pg_stats的统计信息

bj_dsm_0=> select attname,histogram_bounds from pg_stats where tablename = 'tbl_test1' and attname='id';  
  
attname |                      histogram_bounds  
  
———+————————————————————  
  
id      | {17,1367,2838,4328,5704,7162,8621,10028,11467,12903,14332}  
  
(1 row)  

ID总共被划分为10个区段,id<1000落在第一个区段.

rows=  
  
round(14336.0*(((1000.0-17.0)/(1367.0-17.0))/10.0))  
  
=1044  

验证正确。

例三:

bj_dsm_0=> explain select * from tbl_test1 where name='a';  
  
QUERY PLAN  
  
————————————————————–  
  
Seq Scan on tbl_test1  (cost=0.00..257.20 rows=487 width=10)  
  
Filter: ((name)::text = 'a'::text)  
  
(2 rows)  

查看一下统计信息:

bj_dsm_0=> select attname,most_common_vals,most_common_freqs from pg_stats where tablename = 'tbl_test1' and attname='name';  
  
attname | most_common_vals |                    most_common_freqs  
  
———+——————+———————————————————-  
  
name    | {g,f,e,d,c,b,a}  | {0.239667,0.229,0.174333,0.145333,0.103,0.0746667,0.034}  
  
(1 row)  

刚好’a’落在了MCV里面,通过统计信息可以看出频率为0.034

rows=14336*0.034=487  

验证正确。

bj_dsm_0=> select count(*) from tbl_test1 where name='a';  
  
count  
  
——-  
  
512  
  
(1 row)  

与真实记录差别不大

例四:

bj_dsm_0=> explain select * from tbl_test1 where name='digoal';  
  
QUERY PLAN  
  
————————————————————  
  
Seq Scan on tbl_test1  (cost=0.00..257.20 rows=1 width=10)  
  
Filter: ((name)::text = 'digoal'::text)  
  
(2 rows)  

查看统计信息

bj_dsm_0=> select n_distinct,most_common_vals,most_common_freqs,histogram_bounds from pg_stats where tablename='tbl_test1' and attname='name';  
  
n_distinct | most_common_vals |                      most_common_freqs                       | histogram_bounds  
  
————+——————+————————————————————–+——————  
  
7 | {g,f,e,d,c,b,a}  | {0.243,0.225667,0.184333,0.147,0.103333,0.0633333,0.0333333} |  
  
(1 row)  
  
    
bj_dsm_0=> select 0.243+0.225667+0.184333+0.147+0.103333+0.0633333+0.0333333;  
  
?column?  
  
———–  
  
0.9999996  
  
(1 row)  
  
    
bj_dsm_0=> select (1-0.9999996)*14336;  
  
?column?  
  
———–  
  
0.0057344  
  
(1 row)  

显然round(0.0057344)=0,但是PostgreSQL取了rows=1。

所以估计rows返回应该都是>=1

例五:

bj_dsm_0=> explain select * from tbl_test1 where name='a' and id<1000;  
  
QUERY PLAN  
  
———————————————————————————–  
  
Index Scan using tbl_test1_pkey on tbl_test1  (cost=0.00..45.85 rows=34 width=10)  
  
Index Cond: (id < 1000)  
  
Filter: ((name)::text = 'a'::text)  
  
(3 rows)  

查看统计信息

bj_dsm_0=> select n_distinct,most_common_vals,most_common_freqs,histogram_bounds from pg_stats where tablename='tbl_test1';  
  
n_distinct | most_common_vals |                      most_common_freqs                       |                     histogram_bounds  
  
————+——————+————————————————————–+———————————————————-  
  
-1 |                  |                                                              | {2,1391,2882,4271,5690,7170,8664,9989,11426,12920,14335}  
  
7 | {g,f,e,d,c,b,a}  | {0.243,0.225667,0.184333,0.147,0.103333,0.0633333,0.0333333} |  
  
(2 rows)  

概率应该是两个相乘的结果

bj_dsm_0=> select 14336*(((1000.0-2.0)/(1391.0-2.0))/10.0)*0.0333333;  
  
?column?  
  
——————————–  
  
34.334806077926565874258692096  
  
(1 row)  

验证正确。

例六:

bj_dsm_0=> explain select * from tbl_test1 t1,tbl_test2 t2 where t1.id<1000 and t1.id=t2.id;  
  
QUERY PLAN  
  
————————————————————————————————  
  
Merge Join  (cost=0.00..545.78 rows=1052 width=20)  
  
Merge Cond: (t1.id = t2.id)  
  
->  Index Scan using tbl_test1_pkey on tbl_test1 t1  (cost=0.00..43.66 rows=1052 width=10)  
  
Index Cond: (id < 1000)  
  
->  Index Scan using tbl_test2_pkey on tbl_test2 t2  (cost=0.00..912.36 rows=28674 width=10)  
  
(5 rows)  

查看统计信息

bj_dsm_0=> select tablename,n_distinct,most_common_vals,most_common_freqs,histogram_bounds from pg_stats where tablename ~ 'tbl_test';  
  
tablename | n_distinct | most_common_vals |                    most_common_freqs                    |                       histogram_bounds  
  
———–+————+——————+———————————————————+————————————————————–  
  
tbl_test1 |         -1 |                  |                                                         | {2,1362,2855,4237,5700,7116,8494,9989,11405,12815,14336}  
  
tbl_test1 |          7 | {g,f,e,d,c,b,a}  | {0.244,0.215,0.183,0.144,0.109667,0.0626667,0.0416667}  |  
  
tbl_test2 |         -1 |                  |                                                         | {5,2831,5541,8355,11671,14421,17172,20137,22954,25755,28668}  
  
tbl_test2 |          7 | {g,f,e,d,c,b,a}  | {0.243667,0.217333,0.18,0.142333,0.105,0.0736667,0.038} |  
  
(4 rows)  

Merge两个PK取min(rows)

验证正确。

先说到这里,后面还有例子继续补充。

Flag Counter

digoal’s 大量PostgreSQL文章入口