可以使用一个非常简单的数据集(一个包含两列的表,两列包含相同值)展示多元相关性
CREATE TABLE t (a INT, b INT); INSERT INTO t SELECT i % 100, i % 100 FROM generate_series(1, 10000) s(i); ANALYZE t;
如 第 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
对多列集合的基数进行估计时也会出现类似的问题,例如,GROUP BY
子句将生成的分组数。当 GROUP BY
列出单列时,n 不同估计(可见为 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)
通过重新定义统计信息对象以包括两列的 n 不同计数,估计值得到了很大改善
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)
如 第 68.2.1 节 中所述,函数依赖是一种非常廉价且高效的统计信息类型,但其主要限制在于其全局性(仅跟踪列级别的依赖性,而不是单个列值之间的依赖性)。
本节将介绍MCV(最常见的值)列表是对 第 68.1 节 中描述的按列统计信息的一个直接扩展。这些统计信息通过存储各个值来解决限制问题,但当然在构建 ANALYZE
统计信息、存储和计划时间方面开销更大。
让我们再次查看 第 68.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列表。
可以利用 pg_mcv_list_items
返回结果的函数检查MCV列表
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列表中的项,然后对匹配项进行汇总。有关详细信息,请参见 src/backend/statistics/mcv.c
中的 mcv_clauselist_selectivity
。
与函数依赖项相比,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