选择 打开 改范围 完整检索页
受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10
当前 PostgreSQL 版本不在支持生命周期内。
您可以参阅当前版本的对应页面,或其他在上面列出的活跃大版本。

71.2. 多元统计信息示例 #

71.2.1. 函数依赖 #

多元相关性可以通过一个非常简单的数据集来演示 — 一个包含两列的表,两列都含有相同的值:

CREATE TABLE t (a INT, b INT);
INSERT INTO t SELECT i % 100, i % 100 FROM generate_series(1, 10000) s(i);
ANALYZE t;

Section 14.2所述,规划器可以利用从pg_class得到的页数和行数来确定t的基数:

SELECT relpages, reltuples FROM pg_class WHERE relname = 't';

 relpages | reltuples
----------+-----------
       45 |     10000

数据分布非常简单;每一列中都只有 100 个不同的值,并且均匀分布。

下例展示了对一个WHERE条件进行估计的结果,该条件作用于a列:

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1;
                                 QUERY PLAN                                  
-------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..170.00 rows=100 width=8) (actual rows=100 loops=1)
   Filter: (a = 1)
   Rows Removed by Filter: 9900

规划器检查这个条件,并确定该子句的选择率为 1%。比较估计值与实际行数,可以看到估计非常准确(实际上完全准确,因为表很小)。如果修改WHERE条件,使其使用b列,会生成完全相同的计划。再看将同一条件应用于两个列的情况,这两个条件之间使用AND

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1;
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=1 width=8) (actual rows=100 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

规划器分别估计每个条件的选择率,得到与上面相同的 1%。然后假定两个条件独立,将其选择率相乘,得到最终选择率估计值仅为 0.01%。这显著低估了结果,因为实际满足条件的行数(100)比估计值高两个数量级。

可以通过创建一个统计信息对象来解决此问题,使ANALYZE计算这两个列的函数依赖多变量统计信息:

CREATE STATISTICS stts (dependencies) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=100 width=8) (actual rows=100 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

71.2.2. 多元可区分值计数 #

估计由多个列组成的集合的基数时,也会出现类似问题,例如估计GROUP BY子句产生的分组数。当GROUP BY只列出一个列时,非重复值数量估计(显示为 HashAggregate 节点返回的估计行数)非常准确:

EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a;
                                       QUERY PLAN                                        
-----------------------------------------------------------------------------------------
 HashAggregate  (cost=195.00..196.00 rows=100 width=12) (actual rows=100 loops=1)
   Group Key: a
   ->  Seq Scan on t  (cost=0.00..145.00 rows=10000 width=4) (actual rows=10000 loops=1)

但如果没有多变量统计信息,查询中GROUP BY涉及两个列时,对分组数的估计会相差一个数量级,如下例所示:

EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b;
                                       QUERY PLAN                                        
--------------------------------------------------------------------------------------------
 HashAggregate  (cost=220.00..230.00 rows=1000 width=16) (actual rows=100 loops=1)
   Group Key: a, b
   ->  Seq Scan on t  (cost=0.00..145.00 rows=10000 width=8) (actual rows=10000 loops=1)

重新定义统计信息对象,使其包含这两个列的非重复值计数后,估计会有很大改善:

DROP STATISTICS stts;
CREATE STATISTICS stts (dependencies, ndistinct) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF) SELECT COUNT(*) FROM t GROUP BY a, b;
                                       QUERY PLAN                                        
--------------------------------------------------------------------------------------------
 HashAggregate  (cost=220.00..221.00 rows=100 width=16) (actual rows=100 loops=1)
   Group Key: a, b
   ->  Seq Scan on t  (cost=0.00..145.00 rows=10000 width=8) (actual rows=10000 loops=1)

71.2.3. MCV 列表 #

Section 71.2.1中所述,函数依赖是一种非常廉价且高效的统计信息类型,但其主要局限在于它具有全局性(只在列层面跟踪依赖关系,而不是跟踪各个列值之间的依赖关系)。

本节介绍MCV(高频值)列表的多元变体,它是Section 71.1中按列统计信息的直接扩展。这类统计信息通过存储单个值来解决上述限制,但无论是在ANALYZE中构建统计信息,还是在存储和规划时间方面,它的代价自然都更高。

再来看Section 71.2.1中的查询,但这次在同一组列上创建MCV列表(务必删除函数依赖统计信息,确保规划器使用新创建的统计信息)。

DROP STATISTICS stts;
CREATE STATISTICS stts2 (mcv) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 1;
                                   QUERY PLAN
-------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=100 width=8) (actual rows=100 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

估计结果与使用函数依赖时一样准确,这主要是因为表相当小,且分布简单、非重复值数量较少。在查看函数依赖处理得不太好的第二个查询之前,先检查一下MCV列表。

要检查MCV列表,可以使用返回集函数pg_mcv_list_items

SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
                pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts2';
 index |  values  | nulls | frequency | base_frequency
-------+----------+-------+-----------+----------------
     0 | {0, 0}   | {f,f} |      0.01 |         0.0001
     1 | {1, 1}   | {f,f} |      0.01 |         0.0001
   ...
    49 | {49, 49} | {f,f} |      0.01 |         0.0001
    50 | {50, 50} | {f,f} |      0.01 |         0.0001
   ...
    97 | {97, 97} | {f,f} |      0.01 |         0.0001
    98 | {98, 98} | {f,f} |      0.01 |         0.0001
    99 | {99, 99} | {f,f} |      0.01 |         0.0001
(100 rows)

这证实这两列中有 100 个不同的组合,而且它们都大致同样常见(每个组合的频率都是 1%)。基础频率是根据按列统计信息计算出的频率,相当于假设不存在多列统计信息。如果任一列中存在空值,它会在nulls列中标识出来。

在估计选择率时,规划器会将所有条件应用到MCV列表中的各个项上,然后把匹配项的频率加总起来。 详情请参阅mcv_clauselist_selectivity(位于src/backend/statistics/mcv.c中)。

与函数依赖相比,MCV列表有两个主要优势。首先,列表存储实际值,因此能够判断哪些组合是相容的。

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a = 1 AND b = 10;
                                 QUERY PLAN
---------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=1 width=8) (actual rows=0 loops=1)
   Filter: ((a = 1) AND (b = 10))
   Rows Removed by Filter: 10000

其次,MCV列表可以处理更多类型的子句,而不像函数依赖那样只支持等值子句。例如,考虑对同一个表进行如下范围查询:

EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a <= 49 AND b > 49;
                                QUERY PLAN
---------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..195.00 rows=1 width=8) (actual rows=0 loops=1)
   Filter: ((a <= 49) AND (b > 49))
   Rows Removed by Filter: 10000