多元相关性可以用一个非常简单的数据集来演示 — 一个有两列的表,它们都包含相同的值:
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)
如 第 70.2.1 节中所述,函数依赖是非常廉价和高效的统计类型,但它们的主要限制是其全局特性(仅跟踪列级别的依赖项,而不是在单个列值之间)。
本节介绍MCV(最常见值)列表的多变量变体, 第 70.1 节 中描述的每列统计数据的简单扩展。
这些统计数据通过存储单独的值来解决这个限制,但是就构建ANALYZE
中的统计数据、存储和规划时间而言,它的成本自然更高。
让我们再看看来自第 70.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
The estimate is as accurate as with the functional dependencies, mostly thanks to the table being fairly small and having a simple distribution with a low number of distinct values. Before looking at the second query, which was not handled by functional dependencies particularly well, let's inspect the MCV list a bit. 估计值与函数依赖一样准确,这主要是由于表相当小而且具有少量不同值的简单分布。 在查看第二个查询之前,这个函数依赖处理得不是很好,让我们先检查一下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