資料庫的智慧之源-統計資訊之自定義多維統計

德哥發表於2017-09-11

標籤

PostgreSQL , 多列統計資訊 , 多列唯一值 , 多列依賴度 , 任意維度TOP N


背景

PostgreSQL和Oracle一樣,優化器是基於成本的估算。

成本估算中很重要的一個環節是估計每個執行節點返回的記錄數。

例如兩張表JOIN的時候,如果走HASH JOIN那麼需要選擇記錄數少的那個作為雜湊表。

又比如求多個欄位的group by,評估返回多少條記錄給上層節點。

對於基於單列統計的的柱狀圖,估算單個欄位條件的選擇性是很準確的,而估算多個欄位時,PostgreSQL預設使用獨立屬性,直接以多個欄位選擇性相乘的方法計算多個欄位條件的選擇性。不是很準確。

PostgreSQL 10引入了一個黑科技功能,允許使用者自定義多個欄位的統計資訊,目前支援多列相關性和多列唯一值兩種統計。

由於多列統計涉及到許多組合(N階乘種組合),因此預設不會對所有欄位進行任意組合的統計,使用者可以根據實際的業務需求,對需要and查詢,組合group by的欄位(例如 where a xx and b xx, group by a,b)。建立對應的自定義統計資訊。

例子講解

1、建表,11個欄位。

postgres=# create table tbl(id int, c1 int, c2 text, c3 int, c4 int, c5 int, c6 int, c7 int, c8 int, c9 int, c10 int);    
CREATE TABLE  

2、寫入測試資料,1000萬條。

postgres=# insert into tbl select     
postgres-# id,     
postgres-# random()*100, substring(md5(random()::text), 1, 4), random()*900, random()*10000, random()*10000000,     
postgres-# random()*100000, random()*100, random()*200000, random()*40000, random()*90000     
postgres-# from generate_series(1,10000000) t(id);    
INSERT 0 10000000  

3、分析表

postgres=# analyze tbl;  
ANALYZE  

得到reltuples為1e+07,後面要用於計算。

postgres=# select reltuples from pg_class where relname=`tbl`;  
-[ RECORD 1 ]----  
reltuples | 1e+07  

4、SQL舉例

4.1 單個欄位條件

postgres=# explain (analyze) select * from tbl where c1=1;  
                                                 QUERY PLAN                                                    
-------------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..218458.08 rows=93865 width=45) (actual time=0.018..755.833 rows=99527 loops=1)  
   Filter: (c1 = 1)  
   Rows Removed by Filter: 9900473  
 Planning time: 0.077 ms  
 Execution time: 763.151 ms  
(5 rows)  

可以推算得到c1=1的選擇性為: 93865/1e+07 。

postgres=# explain (analyze) select * from tbl where c2=`abc`;  
                                               QUERY PLAN                                                  
---------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..218458.08 rows=148 width=45) (actual time=874.473..874.473 rows=0 loops=1)  
   Filter: (c2 = `abc`::text)  
   Rows Removed by Filter: 10000000  
 Planning time: 0.080 ms  
 Execution time: 874.505 ms  
(5 rows)  

可以推算得到c2=`abc`的選擇性為: 148/1e+07 。

4.2 多個欄位條件

postgres=# explain (analyze) select * from tbl where c1=1 and c2=`abc`;  
                                              QUERY PLAN                                                 
-------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..243458.09 rows=1 width=45) (actual time=802.347..802.347 rows=0 loops=1)  
   Filter: ((c1 = 1) AND (c2 = `abc`::text))  
   Rows Removed by Filter: 10000000  
 Planning time: 0.116 ms  
 Execution time: 802.374 ms  
(5 rows)  

rows=1是怎麼得來的呢,在沒有自定義統計資訊時,是這麼算的,算這兩個條件完全不相干,所以選擇性直接相乘。

(93865/1e+07) * (148/1e+07) * 1e+07 = 1.389202 ~= 1  

4.3 單個欄位條件求唯一值

postgres=# explain (analyze) select c1,count(*) from tbl group by c1;  
                                                       QUERY PLAN                                                          
-------------------------------------------------------------------------------------------------------------------------  
 HashAggregate  (cost=243458.09..243459.10 rows=101 width=12) (actual time=3256.458..3256.473 rows=101 loops=1)  
   Group Key: c1  
   ->  Seq Scan on tbl  (cost=0.00..193458.06 rows=10000006 width=4) (actual time=0.013..1252.169 rows=10000000 loops=1)  
 Planning time: 0.061 ms  
 Execution time: 3256.518 ms  
(5 rows)  

rows=101來自pg_stats.n_distinct , tbl.c1列的統計。

n_distinct             | 101  

4.4 多個欄位條件求唯一值

postgres=# explain (analyze) select c1,c2,count(*) from tbl group by c1,c2;  
                                                          QUERY PLAN                                                             
-------------------------------------------------------------------------------------------------------------------------------  
 GroupAggregate  (cost=1561215.43..1671215.50 rows=1000001 width=17) (actual time=11414.144..16549.549 rows=5147139 loops=1)  
   Group Key: c1, c2  
   ->  Sort  (cost=1561215.43..1586215.45 rows=10000006 width=9) (actual time=11414.132..13905.616 rows=10000000 loops=1)  
         Sort Key: c1, c2  
         Sort Method: external merge  Disk: 185984kB  
         ->  Seq Scan on tbl  (cost=0.00..193458.06 rows=10000006 width=9) (actual time=0.008..1567.909 rows=10000000 loops=1)  
 Planning time: 0.082 ms  
 Execution time: 16952.301 ms  
(8 rows)  

5、自定義統計資訊語法講解

Command:     CREATE STATISTICS  
Description: define extended statistics  
Syntax:  
CREATE STATISTICS [ IF NOT EXISTS ] statistics_name  
    [ ( statistic_type [, ... ] ) ]  
    ON column_name, column_name [, ...]  
    FROM table_name  

建立自定義統計資訊,指定需要自定義統計的欄位名,需要統計依賴性、唯一性(不指定則都統計)。

6、建立自定義統計資訊

我們建立c1 c2 c3這三個欄位的自定義統計資訊。

postgres=# create statistics s1 on c1,c2,c3 from tbl;  
CREATE STATISTICS  

自定義統計資訊建立好之後,需要分析表,才會生成。

postgres=# analyze tbl;  
ANALYZE  

7、自定義統計資訊解讀

postgres=# select * from pg_statistic_ext where stxname=`s1`;  
-[ RECORD 1 ]---+--------------------------------------------------------------------------------------------------------------  
stxrelid        | 16384   -- 表  
stxname         | s1  
stxnamespace    | 2200  
stxowner        | 10  
stxkeys         | 2 3 4  -- 表示第2,3,4列建立自定義統計資訊。  
stxkind         | {d,f}   -- 統計 欄位之間的依賴度(相關性)、唯一值個數。  
stxndistinct    | {"2, 3": 3747653, "2, 4": 87662, "3, 4": 9001205, "2, 3, 4": 10000006}   -- 組合唯一值個數  
stxdependencies | {"3 => 2": 0.642100, "3 => 4": 0.639567, "2, 3 => 4": 0.995000, "2, 4 => 3": 0.712033, "3, 4 => 2": 0.999667}    
-- 欄位之間的依賴性,當使用多個欄位AND條件時,用於代替多個孤立條件的選擇性相乘。選擇性乘以依賴度,選出最後計算結果最低的,作為最終選擇性。  

stxndistinct,很好理解,就是欄位組合後的唯一值個數。

stxdependencies,當一個欄位確定後,另一個欄位是唯一值的比例有多少?例如a=1, b={1,2,3,4,5,….}; a=2,b=1,這裡只有後面這條算b依賴a。 依賴條數除以總數即a => b的依賴值。可以用於評估兩個欄位都是等值條件時的選擇性。生成stxdependencies的演算法很簡單,a => b 等於 count(distinct a)/count(distinct a,b);b=>a 等於 count(distinct b)/count(distinct a,b);

postgres=# d t  
                 Table "public.t"  
 Column |  Type   | Collation | Nullable | Default   
--------+---------+-----------+----------+---------  
 c1     | integer |           |          |   
 c2     | integer |           |          |   
Statistics objects:  
    "public"."s1" (ndistinct, dependencies) ON c1, c2 FROM t  
  
postgres=# select * from pg_statistic_ext where stxname=`s2`;  
 stxrelid | stxname | stxnamespace | stxowner | stxkeys | stxkind |   stxndistinct   |             stxdependencies                
----------+---------+--------------+----------+---------+---------+------------------+------------------------------------------  
    16394 | s2      |         2200 |       10 | 1 2     | {d,f}   | {"1, 2": 105358} | {"1 => 2": 0.083733, "2 => 1": 0.916200}  
(1 row)  
  
postgres=# select count(distinct c1) from t;  
 count   
-------  
 10000  
(1 row)  
  
postgres=# select count(distinct c2) from t;  
 count    
--------  
 100001  
(1 row)  
  
postgres=# select count(distinct (c1,c2)) from t;  
s count    
--------  
 109999  
(1 row)  
  
postgres=# select 10000/109999.0;  
        ?column?          
------------------------  
 0.09090991736288511714  
(1 row)  
  
postgres=# select 100001/109999.0;  
        ?column?          
------------------------  
 0.90910826462058745989  
(1 row)  
  
postgres=# select 0.083733+0.916200;  
 ?column?   
----------  
 0.999933  
(1 row)  

評估例子

a = ? and b = ? 的選擇性   
=   
min( "選擇性(a) * (a=>b)" , "選擇性(b) * (b=>a)" )  

8、SQL舉例

8.1 多個欄位條件

postgres=# explain (analyze) select * from tbl where c1=1 and c2=`abc`;  
                                               QUERY PLAN                                                 
--------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..243458.09 rows=96 width=45) (actual time=802.182..802.182 rows=0 loops=1)  
   Filter: ((c1 = 1) AND (c2 = `abc`::text))  
   Rows Removed by Filter: 10000000  
 Planning time: 0.098 ms  
 Execution time: 802.203 ms  
(5 rows)  

建立了多欄位統計資訊後,這兩個條件在統計資訊之列,所以可以用他們的依賴度來算組合AND條件的選擇性。

演算法:選擇性最低的條件的選擇性 * 與另一個欄位的依賴度,得到組合選擇性。

0.642100 * (148/1e+07) * 1e+07 = 95.0308 (這裡反推的選擇性有一點失真,大概原理就是這樣)  

8.2 多個欄位條件求唯一值

postgres=# explain (analyze) select c1,c2,count(*) from tbl group by c1,c2;  
                                                          QUERY PLAN                                                             
-------------------------------------------------------------------------------------------------------------------------------  
 GroupAggregate  (cost=1561215.43..1698692.02 rows=3747653 width=17) (actual time=11632.613..16843.873 rows=5147139 loops=1)  
   Group Key: c1, c2  
   ->  Sort  (cost=1561215.43..1586215.45 rows=10000006 width=9) (actual time=11632.597..14202.457 rows=10000000 loops=1)  
         Sort Key: c1, c2  
         Sort Method: external merge  Disk: 185984kB  
         ->  Seq Scan on tbl  (cost=0.00..193458.06 rows=10000006 width=9) (actual time=0.008..1567.024 rows=10000000 loops=1)  
 Planning time: 0.215 ms  
 Execution time: 17246.889 ms  
(8 rows)  

直接使用了多列統計資訊中的唯一值統計資訊3747653。

"2, 3": 3747653  

9、如何利用自定義統計資訊統計多個欄位 唯一值、多列依賴性。

9.1 PostgreSQL已有了單列唯一值的統計,我們可以通過pg_stats.n_distinct以及pg_class.reltuples查詢到。

通過create statistic,資料庫會自動收集多列值的統計資訊,我們查詢pg_statistic_ext.stxndistinct,可以得到多列唯一值的估計值。

9.2 多列依賴性指的是列與列之間值的依賴強度,是一個小於等於1的係數。1表示強依賴,

小結

1、PostgreSQL 10支援自定義多列統計資訊,目前支援 多列組合唯一值、列與列的相關性。

2、多列唯一值可用於評估group by, count(distinct)等。

3、列與列相關性可用於估算多個列AND條件的選擇性。演算法

a = ? and b = ? 的選擇性   
=   
min( "選擇性(a) * (a=>b)" , "選擇性(b) * (b=>a)" )  

4、由於多列統計資訊的組合很多,因此資料庫預設只統計單列的柱狀圖。當使用者意識到某些列會作為組合查詢列時,再建立自定義多列統計資訊即可。

參考

https://www.postgresql.org/docs/10/static/multivariate-statistics-examples.html

https://www.postgresql.org/docs/10/static/sql-createstatistics.html

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


相關文章