14.2. 计划者使用的统计信息

14 .2.1. 单列统计

正如我们在上一节中所看到的,查询计划者需要估计查询检索的行数,以便做出更好的查询计划选择。本部分提供了系统用于这些估计的统计信息的快速浏览。

统计信息的一个组成部分是每个表和索引中的条目总数,以及每个表和索引所占用的磁盘块数。此信息保存在表pg_classreltuplesrelpages列中。我们可以通过与此类似的查询来查看它:

SELECT relname, relkind, reltuples, relpages
FROM pg_class
WHERE relname LIKE 'tenk1%';

       relname        | relkind | reltuples | relpages
----------------------+---------+-----------+----------
 tenk1                | r       |     10000 |      358
 tenk1_hundred        | i       |     10000 |       30
 tenk1_thous_tenthous | i       |     10000 |       30
 tenk1_unique1        | i       |     10000 |       30
 tenk1_unique2        | i       |     10000 |       30
(5 rows)

在这里,我们可以看到tenk1包含 10000 行,以及它的索引,但是索引(毫不奇怪)比表小得多。

由于效率原因,reltuplesrelpages不会即时更新,因此它们通常包含一些过时的值。它们由VACUUMANALYZE和一些 DDL 命令(如CREATE INDEX)更新。不扫描整个表的VACUUMANALYZE操作(通常是这种情况)将根据它扫描的表的一部分来递增reltuples计数,从而得到一个近似值。无论如何,计划者都会缩放在pg_class中找到的值以匹配当前的物理表大小,从而获得更接近的近似值。

由于WHERE子句限制了要检查的行,因此大多数查询只检索表中一部分行。因此,计划者需要估算WHERE子句的“选择性”,即,与WHERE子句中的每个条件匹配的行的分数。用于此任务的信息存储在pg_statistic系统目录中。 pg_statistic中的条目由ANALYZEVACUUM ANALYZE命令更新,即使是新近更新的也总是近似的。

与其直接查看pg_statistic,不如手动查看统计信息时,最好查看其视图pg_statspg_stats旨在更易于阅读。此外,pg_stats所有人都可以读取,而pg_statistic仅超级用户可以读取。 (这可以防止非特权用户从统计信息中了解其他人的表的内容.pg_stats视图被限制为仅显示当前用户可以读取的表的行.)例如,我们可以这样做:

SELECT attname, inherited, n_distinct,
       array_to_string(most_common_vals, E'\n') as most_common_vals
FROM pg_stats
WHERE tablename = 'road';

 attname | inherited | n_distinct |          most_common_vals
---------+-----------+------------+------------------------------------
 name    | f         |  -0.363388 | I- 580                        Ramp+
         |           |            | I- 880                        Ramp+
         |           |            | Sp Railroad                       +
         |           |            | I- 580                            +
         |           |            | I- 680                        Ramp
 name    | t         |  -0.284859 | I- 880                        Ramp+
         |           |            | I- 580                        Ramp+
         |           |            | I- 680                        Ramp+
         |           |            | I- 580                            +
         |           |            | State Hwy 13                  Ramp
(2 rows)

请注意,同一列显示了两行,其中一行对应于从road表(inherited = t)开始的完整继承层次结构,另一行仅包含road表本身(inherited = f)。

可以使用ALTER TABLE SET STATISTICS命令逐列设置在pg_statistic中按ANALYZE存储在pg_statistic中的信息量,尤其是每列most_common_valshistogram_bounds数组中的最大条目数,也可以通过设置default_statistics_target配置全局设置变量。默认限制是当前 100 个条目。提高该限制可能允许进行更准确的计划程序估计,尤其是对于数据分布不规则的列,其代价是要消耗pg_statistic中的更多空间,并花费更多的时间来计算估计值。相反,对于具有简单数据分布的列,下限可能就足够了。

有关计划者使用统计信息的更多详细信息,请参见Chapter 70

14 .2.2. 扩展统计

通常会看到慢速查询运行错误的执行计划,因为查询子句中使用的多个列是相关的。计划者通常假设多个条件是相互独立的,当列值相关时,这种假设不成立。常规统计数据由于其基于每个列的性质而无法捕获有关跨列相关性的任何知识。但是,PostgreSQL 具有计算多元统计信息的能力,可以捕获此类信息。

由于可能的列组合数量非常多,因此自动计算多元统计信息是不切实际的。而是可以创建扩展的统计对象(通常更称为* statistics 对象*)来指示服务器在有趣的列集中获取统计信息。

使用CREATE STATISTICS命令创建统计对象。创建此类对象仅会创建一个表达对统计信息感兴趣的目录条目。实际数据收集由ANALYZE(手动命令或后台自动分析)执行。可以在pg_statistic_ext目录中检查收集的值。

ANALYZE根据用于计算常规单列统计信息的相同表行 samples 来计算扩展统计信息。由于通过增加表或其任何列的统计目标来增加 samples 大小(如上一节所述),因此较大的统计目标通常会导致更准确的扩展统计,以及更多的计算时间。

以下小节介绍了当前支持的扩展统计信息的种类。

14 .2.2.1. 功能依赖

最简单的扩展统计信息跟踪功能依赖关系,这是定义数据库标准格式的概念。如果说a的值足以确定b的值,也就是说没有两行具有相同的a的值但没有不同的b的值,则可以说列b在功能上取决于列a。在完全规范化的数据库中,功能依赖项仅应存在于主键和超键上。但是,实际上,由于种种原因,许多数据集并未完全标准化。出于性能原因的有意非正规化是一个常见的示例。即使在完全规范化的数据库中,某些列之间也可能存在部分相关性,这可以表示为部分功能依赖性。

功能依赖项的存在直接影响某些查询中估计的准确性。如果查询在独立列和从属列上都包含条件,则从属列上的条件不会进一步减小结果的大小;但是在不了解功能依赖性的情况下,查询计划者将假定条件是独立的,从而导致低估了结果的大小。

为了通知计划者功能依赖性,ANALYZE可以收集跨列依赖性的度量。评估所有列集之间的依赖性程度将非常昂贵,因此,数据收集仅限于用dependencies选项定义的统计对象中同时出现的那些列组。建议仅为高度相关的列组创建dependencies统计信息,以避免ANALYZE和以后的查询计划中不必要的开销。

这是收集功能依赖性统计信息的示例:

CREATE STATISTICS stts (dependencies) ON zip, city FROM zipcodes;

ANALYZE zipcodes;

SELECT stxname, stxkeys, stxdependencies
  FROM pg_statistic_ext
  WHERE stxname = 'stts';
 stxname | stxkeys |             stxdependencies               
---------+---------+------------------------------------------
 stts    | 1 5     | {"1 => 5": 1.000000, "5 => 1": 0.423130}
(1 row)

在这里可以看到,第 1 列(邮政编码)完全确定第 5 列(城市),因此系数为 1.0,而 city 仅在大约 42%的时间确定邮政编码,这意味着有许多城市(58%)由多个邮政编码表示。

在计算涉及功能相关列的查询的选择性时,计划人员使用相关系数调整按条件的选择性估计,以免产生低估。

14 .2.2.1.1. 功能依赖性的局限性

当前仅在考虑将列与常量值进行比较的简单相等条件时才应用功能依赖项。它们不用于改进比较两列或将一列与表达式进行比较的相等条件的估计,也不用于范围子句,LIKE或任何其他类型的条件。

在估计功能依赖性时,计划者假设所涉及列上的条件是兼容的,因此是多余的。如果它们不兼容,则正确的估计将是零行,但不会考虑这种可能性。例如,给定一个查询,例如

SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '94105';

规划人员将忽略city子句,因为它没有更改选择性,这是正确的。但是,它将对

SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '90210';

即使实际上有零行可以满足该查询。但是,功能依赖关系统计信息无法提供足够的信息来得出结论。

在许多实际情况下,通常可以满足此假设。例如,应用程序中可能会有一个 GUI,该 GUI 仅允许选择兼容的城市和邮政编码值以在查询中使用。但是,如果不是这种情况,功能依赖可能不是一个可行的选择。

14 .2.2.2. 多元 N 相异计数

单列统计信息存储每列中不同值的数量。当计划者仅具有单列统计数据,从而导致其选择错误的计划时,组合多于一列(例如,对于GROUP BY a, b)时,对不同值的数量的估算通常是错误的。

为了改善这种估算,ANALYZE可以收集列组的 n 个不同的统计信息。和以前一样,对每个可能的列分组都执行此操作是不切实际的,因此仅针对在使用ndistinct选项定义的统计对象中同时出现的那些列组收集数据。将从列出的列集中为两个或更多列的每种可能组合收集数据。

continue 前面的示例,邮政编码表中的 n 个不同计数可能如下所示:

CREATE STATISTICS stts2 (ndistinct) ON zip, state, city FROM zipcodes;

ANALYZE zipcodes;

SELECT stxkeys AS k, stxndistinct AS nd
  FROM pg_statistic_ext
  WHERE stxname = 'stts2';
-[ RECORD 1 ]--------------------------------------------------------
k  | 1 2 5
nd | {"1, 2": 33178, "1, 5": 33178, "2, 5": 27435, "1, 2, 5": 33178}
(1 row)

这表明存在具有 33178 个不同值的列的三种组合:邮政编码和 State;邮政编码和城市;以及邮递区号,城市和 State(鉴于此邮编在此表中是唯一的,因此它们都是相等的事实)。另一方面,城市和 State 的组合只有 27435 个不同的值。

建议仅在实际用于分组的列的组合上创建ndistinct统计对象,并且对它们的错误估计会导致不良的计划。否则,将浪费ANALYZE个周期。