2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前版本 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2

69.1. 行估计示例 #

下面展示的示例使用了 PostgreSQL 回归测试数据库中的表。另请注意,由于 ANALYZE 在生成统计信息时使用随机抽样,因此在每次新的 ANALYZE 之后,结果会略有变化。

让我们从一个非常简单的查询开始

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

规划器如何确定 tenk1 的基数在 第 14.2 节 中有介绍,但为了完整性,这里再重复一遍。页数和行数从 pg_class 中查找

SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';

 relpages | reltuples
----------+-----------
      358 |     10000

这些数字是截至上次对表执行 VACUUMANALYZE 时的最新数据。然后,规划器获取表中实际的当前页数(这是一个廉价的操作,不需要扫描表)。如果这个值与 relpages 不同,那么 reltuples 将会相应地进行缩放,以得出一个当前的行数估计值。在上面的例子中,relpages 的值是最新的,所以行数估计值与 reltuples 相同。

接下来看一个 WHERE 子句中带有范围条件的示例

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000;

                                   QUERY PLAN
-------------------------------------------------------------------​-------------
 Bitmap Heap Scan on tenk1  (cost=24.06..394.64 rows=1007 width=244)
   Recheck Cond: (unique1 < 1000)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

规划器会检查 WHERE 子句的条件,并在 pg_operator 中查找运算符 < 的选择性函数。该函数存储在 oprrest 列中,在本例中是 scalarltselscalarltsel 函数从 pg_statistic 中检索 unique1 的直方图。对于手动查询,查看更简单的 pg_stats 视图会更方便

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='unique1';

                   histogram_bounds
------------------------------------------------------
 {0,993,1997,3050,4040,5036,5957,7057,8029,9016,9995}

接下来,计算出“< 1000”在直方图中所占的比例。这就是选择性。直方图将范围划分为等频桶,所以我们只需找到我们的值所在的桶,并计算它所占的部分以及它之前的所有桶。值 1000 显然在第二个桶 (993–1997) 中。假设每个桶内的值是线性分布的,我们可以这样计算选择性:

selectivity = (1 + (1000 - bucket[2].min)/(bucket[2].max - bucket[2].min))/num_buckets
            = (1 + (1000 - 993)/(1997 - 993))/10
            = 0.100697

也就是说,一个完整的桶加上第二个桶的线性部分,再除以桶的总数。现在,估计的行数可以通过选择性与 tenk1 的基数的乘积来计算

rows = rel_cardinality * selectivity
     = 10000 * 0.100697
     = 1007  (rounding off)

接下来,让我们考虑一个 WHERE 子句中带有相等条件的示例

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'CRAAAA';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=30 width=244)
   Filter: (stringu1 = 'CRAAAA'::name)

规划器再次检查 WHERE 子句的条件,并查找 = 的选择性函数,即 eqsel。对于相等性估计,直方图没有用;而是使用最常见值MCVMCV)列表来确定选择性。让我们看一下 MCV,以及一些后面会用到的额外列

SELECT null_frac, n_distinct, most_common_vals, most_common_freqs FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';

null_frac         | 0
n_distinct        | 676
most_common_vals  | {EJAAAA,BBAAAA,CRAAAA,FCAAAA,FEAAAA,GSAAAA,​JOAAAA,MCAAAA,NAAAAA,WGAAAA}
most_common_freqs | {0.00333333,0.003,0.003,0.003,0.003,0.003,​0.003,0.003,0.003,0.003}

由于 CRAAAA 出现在 MCV 列表中,所以选择性就是最常见频率(MCF)列表中对应的条目

selectivity = mcf[3]
            = 0.003

和之前一样,估计的行数就是这个选择性与 tenk1 的基数的乘积

rows = 10000 * 0.003
     = 30

现在考虑相同的查询,但常量不在 MCVMCV列表中

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'xxx';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=15 width=244)
   Filter: (stringu1 = 'xxx'::name)

这是一个完全不同的问题:当值不在 MCVMCV列表中时,如何估计选择性。方法是利用该值不在列表中的事实,并结合所有 MCVMCVs

selectivity = (1 - sum(mcv_freqs))/(num_distinct - num_mcv)
            = (1 - (0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003 +
                    0.003 + 0.003 + 0.003 + 0.003))/(676 - 10)
            = 0.0014559

的频率知识。具体做法是,将所有 MCVMCV的频率加起来,用 1 减去这个和,然后除以其他不同值的数量。这相当于假设,不属于任何 MCV 的那部分列值在所有其他不同值之间是均匀分布的。请注意,这里没有空值,所以我们不必担心它们(否则我们也要从分子中减去空值比例)。然后像往常一样计算估计的行数

rows = 10000 * 0.0014559
     = 15  (rounding off)

前面 unique1 < 1000 的例子是对 scalarltsel 实际工作的过度简化;现在我们已经看到了使用 MCV 的例子,可以补充一些细节。那个例子就其本身而言是正确的,因为 unique1 是一个唯一列,所以它没有 MCV(显然,没有哪个值比其他任何值更常见)。对于一个非唯一列,通常既有直方图,也有 MCV 列表,并且直方图不包含由 MCV 代表的那部分列总体。我们这样做是因为它能提供更精确的估计。在这种情况下,scalarltsel 会直接将条件(例如,“< 1000”)应用于 MCV 列表中的每个值,并累加那些满足条件的 MCV 的频率。这为表中属于 MCV 的那部分提供了一个精确的选择性估计。然后,直方图会像上面那样用于估计不属于 MCV 的那部分表中的选择性,最后将这两个数字结合起来,估算出总的选择性。例如,考虑

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 < 'IAAAAA';

                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=3077 width=244)
   Filter: (stringu1 < 'IAAAAA'::name)

我们已经看到了 stringu1 的 MCV 信息,下面是它的直方图

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';

                                histogram_bounds
-------------------------------------------------------------------​-------------
 {AAAAAA,CQAAAA,FRAAAA,IBAAAA,KRAAAA,NFAAAA,PSAAAA,SGAAAA,VAAAAA,​XLAAAA,ZZAAAA}

检查 MCV 列表,我们发现条件 stringu1 < 'IAAAAA' 被前六个条目满足,而不被后四个满足,因此在总体中 MCV 部分的选择性是

selectivity = sum(relevant mvfs)
            = 0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003
            = 0.01833333

将所有 MCF 相加也告诉我们,由 MCV 代表的总人口比例是 0.03033333,因此由直方图代表的比例是 0.96966667(同样,没有空值,否则我们也必须在这里排除它们)。我们可以看到,值 IAAAAA 几乎落在第三个直方图桶的末尾。通过一些相当粗略的关于不同字符频率的假设,规划器得出的估计是,直方图总体中小于 IAAAAA 的部分为 0.298387。然后我们结合 MCV 和非 MCV 总体的估计值

selectivity = mcv_selectivity + histogram_selectivity * histogram_fraction
            = 0.01833333 + 0.298387 * 0.96966667
            = 0.307669

rows        = 10000 * 0.307669
            = 3077  (rounding off)

在这个特定的例子中,来自 MCV 列表的修正相当小,因为列的分布实际上非常平坦(统计数据显示这些特定值比其他值更常见,主要是由于抽样误差)。在一个更典型的情况下,即某些值明显比其他值更常见时,这个复杂的过程会显著提高准确性,因为最常见值的选择性是被精确计算出来的。

现在让我们考虑一个 WHERE 子句中有多个条件的情况

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000 AND stringu1 = 'xxx';

                                   QUERY PLAN
-------------------------------------------------------------------​-------------
 Bitmap Heap Scan on tenk1  (cost=23.80..396.91 rows=1 width=244)
   Recheck Cond: (unique1 < 1000)
   Filter: (stringu1 = 'xxx'::name)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

规划器假设这两个条件是独立的,因此各个子句的选择性可以相乘

selectivity = selectivity(unique1 < 1000) * selectivity(stringu1 = 'xxx')
            = 0.100697 * 0.0014559
            = 0.0001466

rows        = 10000 * 0.0001466
            = 1  (rounding off)

请注意,估计从位图索引扫描返回的行数仅反映了与索引一起使用的条件;这很重要,因为它会影响后续堆获取的成本估计。

最后,我们将研究一个涉及连接的查询

EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 50 AND t1.unique2 = t2.unique2;

                                      QUERY PLAN
-------------------------------------------------------------------​-------------------
 Nested Loop  (cost=4.64..456.23 rows=50 width=488)
   ->  Bitmap Heap Scan on tenk1 t1  (cost=4.64..142.17 rows=50 width=244)
         Recheck Cond: (unique1 < 50)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.63 rows=50 width=0)
               Index Cond: (unique1 < 50)
   ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.00..6.27 rows=1 width=244)
         Index Cond: (unique2 = t1.unique2)

tenk1 的限制条件 unique1 < 50 在嵌套循环连接之前进行评估。这与前面的范围示例类似。这一次,值 50 落在了 unique1 直方图的第一个桶中

selectivity = (0 + (50 - bucket[1].min)/(bucket[1].max - bucket[1].min))/num_buckets
            = (0 + (50 - 0)/(993 - 0))/10
            = 0.005035

rows        = 10000 * 0.005035
            = 50  (rounding off)

连接的限制条件是 t2.unique2 = t1.unique2。操作符是我们熟悉的 =,但是选择性函数是从 pg_operatoroprjoin 列中获取的,即 eqjoinseleqjoinsel 会查找 tenk2tenk1 的统计信息

SELECT tablename, null_frac,n_distinct, most_common_vals FROM pg_stats
WHERE tablename IN ('tenk1', 'tenk2') AND attname='unique2';

tablename  | null_frac | n_distinct | most_common_vals
-----------+-----------+------------+------------------
 tenk1     |         0 |         -1 |
 tenk2     |         0 |         -1 |

在这种情况下,unique2 没有 MCVMCV信息,并且所有值似乎都是唯一的(n_distinct = -1),所以我们使用一个依赖于两个关系行数估计值(num_rows,未显示,但为“tenk”)以及列的空值比例(两者都为零)的算法

selectivity = (1 - null_frac1) * (1 - null_frac2) / max(num_rows1, num_rows2)
            = (1 - 0) * (1 - 0) / max(10000, 10000)
            = 0.0001

即,从每个关系的 1 中减去空值比例,然后除以较大关系的行数(在非唯一情况下,该值会被缩放)。连接可能产生的行数计算为两个输入的笛卡尔积的基数乘以选择性

rows = (outer_cardinality * inner_cardinality) * selectivity
     = (50 * 10000) * 0.0001
     = 50

如果这两列有 MCV 列表,eqjoinsel 将会使用直接比较 MCV 列表的方式来确定由 MCV 代表的那部分列总体中的连接选择性。对于总体中其余部分的估计则遵循这里展示的相同方法。

注意,我们展示的 inner_cardinality 是 10000,也就是 tenk2 未经修改的大小。从 EXPLAIN 的输出中看,连接行的估计值似乎来自于 50 * 1,即外部行的数量乘以每次对 tenk2 进行内部索引扫描所获得的估计行数。但事实并非如此:连接关系的大小是在考虑任何特定连接计划之前估算的。如果一切正常,那么这两种估算连接大小的方法会产生大致相同的答案,但由于舍入误差和其他因素,它们有时会显著不同。

对于对更多细节感兴趣的人,表大小的估计(在任何 WHERE 子句之前)是在 src/backend/optimizer/util/plancat.c 中完成的。子句选择性的通用逻辑在 src/backend/optimizer/path/clausesel.c 中。特定于操作符的选择性函数主要在 src/backend/utils/adt/selfuncs.c 中找到。

提交更正

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