2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10

69.2. 多变量统计示例 #

69.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;

第 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, BUFFERS 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.00 loops=1)
   Filter: (a = 1)
   Rows Removed by Filter: 9900

规划器检查该条件,并将此子句的选择性确定为 1%。通过比较此估算值和实际行数,我们发现估算值非常准确(实际上是精确的,因为表非常小)。将 WHERE 条件更改为使用 b 列,会生成相同的计划。但是,如果我们通过 AND 将相同的条件应用于两个列,请观察会发生什么:

EXPLAIN (ANALYZE, TIMING OFF, BUFFERS 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.00 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

规划器分别估算每个条件的 selectivity,得出与上面相同的 1% 估算值。然后,它假定这些条件是独立的,因此将它们的 selectivity 相乘,得出最终的 selectivity 估算值仅为 0.01%。这是一个显著的低估,因为实际匹配这些条件的行数(100 行)比估算值高出两个数量级。

通过创建一个统计对象,指示 ANALYZE 在这两个列上计算函数依赖多变量统计信息,可以解决此问题。

CREATE STATISTICS stts (dependencies) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS 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.00 loops=1)
   Filter: ((a = 1) AND (b = 1))
   Rows Removed by Filter: 9900

69.2.2. 多变量 N-Distinct 计数 #

在估算多个列集合的基数时,也会出现类似的问题,例如 GROUP BY 子句会生成的组的数量。当 GROUP BY 只列出一个列时,n-distinct 估算值(在 HashAggregate 节点返回的估算行数中可见)非常准确。

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

但是,如果没有多变量统计信息,在具有两个列的 GROUP BY 查询中,组数的估算值(如下例所示)会相差一个数量级:

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

通过重新定义统计对象以包含这两个列的 n-distinct 计数,估算值得到了很大的改善。

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

69.2.3. MCV 列表 #

第 69.2.1 节中所述,函数依赖是一种非常便宜且高效的统计信息类型,但其主要限制是全局性(仅跟踪列级别的依赖关系,而不跟踪单个列值之间的依赖关系)。

本节将介绍多变量变体的MCV(most-common values) 列表,这是对 第 69.1 节中描述的每列统计信息的直接扩展。这些统计信息通过存储单个值来解决上述限制,但自然会更昂贵,无论是在 ANALYZE 中构建统计信息、存储还是规划时间方面。

让我们再次查看 第 69.2.1 节中的查询,但这次是在同一组列上创建了MCV列表(确保删除函数依赖,以确保规划器使用新创建的统计信息)。

DROP STATISTICS stts;
CREATE STATISTICS stts2 (mcv) ON a, b FROM t;
ANALYZE t;
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS 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.00 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% 的频率)。基本频率是从每列统计信息计算出的频率,就好像没有多列统计信息一样。如果其中任何一列包含 NULL 值,则会在 nulls 列中识别出来。

在估算 selectivity 时,规划器将所有条件应用于MCV列表中的项,然后将匹配项的频率相加。有关详细信息,请参阅 src/backend/statistics/mcv.c 中的 mcv_clauselist_selectivity

与函数依赖相比,MCV列表有两个主要优点。首先,列表存储实际值,使得可以确定哪些组合是兼容的。

EXPLAIN (ANALYZE, TIMING OFF, BUFFERS 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.00 loops=1)
   Filter: ((a = 1) AND (b = 10))
   Rows Removed by Filter: 10000

其次,MCV列表处理更广泛的子句类型,而不仅仅是像函数依赖那样的相等子句。例如,考虑同一表的以下范围查询:

EXPLAIN (ANALYZE, TIMING OFF, BUFFERS 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.00 loops=1)
   Filter: ((a <= 49) AND (b > 49))
   Rows Removed by Filter: 10000

提交更正

如果您在文档中发现任何不正确、与您在使用特定功能时的经验不符或需要进一步说明的内容,请使用 此表单来报告文档问题。