多元相关性可以通过一个非常简单的 dataset 来演示 - 一个表,其中包含两列,这两列都包含相同的值。
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-distinct 估计值(在 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-distinct 计数,估计值得到了很大改进。
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列表。
检查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列表中的所有项目应用所有条件,然后对匹配的项目的频率求和。有关详细信息,请参阅 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
如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表格 报告文档问题。