70.2. 多元统计信息示例

70.2.1. 函数依赖
70.2.2. 多元可区分值计数

70.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个不同的值,是一种均匀分布。

下面的例子展示了估算a列上的一个WHERE条件的结果:

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

70.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)