支持的版本: 当前 (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 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

11.8. 部分索引 #

一个 部分索引 是建立在表子集上的索引;该子集由条件表达式定义(称为部分索引的 谓词)。该索引仅包含满足谓词的那些表行的条目。部分索引是一种专门的功能,但在某些情况下很有用。

使用部分索引的一个主要原因是避免索引常用值。由于搜索常用值(占所有表行百分之几以上)的查询无论如何都不会使用索引,因此根本没有必要将这些行保留在索引中。这会减小索引的大小,从而加快使用索引的那些查询的速度。它还将加快许多表更新操作的速度,因为并非在所有情况下都需要更新索引。示例 11.1 显示了此想法的可能应用。

示例 11.1. 设置部分索引以排除常用值

假设您正在数据库中存储 Web 服务器访问日志。大多数访问都来自您组织的 IP 地址范围,但有些来自其他地方(例如,拨号连接的员工)。如果您的 IP 搜索主要针对外部访问,您可能不需要索引与您组织的子网对应的 IP 范围。

假设一个如下所示的表

CREATE TABLE access_log (
    url varchar,
    client_ip inet,
    ...
);

要创建适合我们示例的部分索引,请使用如下命令

CREATE INDEX access_log_client_ip_ix ON access_log (client_ip)
WHERE NOT (client_ip > inet '192.168.100.0' AND
           client_ip < inet '192.168.100.255');

可以使用此索引的典型查询将是

SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';

这里,查询的 IP 地址由部分索引覆盖。以下查询无法使用部分索引,因为它使用了索引中排除的 IP 地址

SELECT *
FROM access_log
WHERE url = '/index.html' AND client_ip = inet '192.168.100.23';

请注意,这种部分索引要求预先确定常用值,因此这种部分索引最适合于数据分布不发生变化的情况。可以偶尔重新创建此类索引以适应新的数据分布,但这会增加维护工作。


部分索引的另一种可能用途是从索引中排除典型查询工作负载不感兴趣的值;如 示例 11.2 中所示。这会产生与上面列出的相同的优点,但它会阻止通过该索引访问 不感兴趣 的值,即使在这种情况下索引扫描可能是有利的。显然,为这种情况设置部分索引需要大量的谨慎和实验。

示例 11.2. 设置部分索引以排除不感兴趣的值

如果您有一个同时包含已开单和未开单订单的表,其中未开单订单占总表的一小部分,但这些是访问最多的行,则可以通过仅在未开单行上创建索引来提高性能。创建索引的命令如下所示

CREATE INDEX orders_unbilled_index ON orders (order_nr)
    WHERE billed is not true;

可以使用此索引的可能查询将是

SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;

但是,该索引也可以用于根本不涉及 order_nr 的查询,例如

SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;

这不如 amount 列上的部分索引有效,因为系统必须扫描整个索引。但是,如果未开单订单相对较少,则仅使用此部分索引查找未开单订单可能是一个胜利。

请注意,此查询无法使用此索引

SELECT * FROM orders WHERE order_nr = 3501;

订单 3501 可能在已开单或未开单订单中。


示例 11.2 还说明了索引列和谓词中使用的列不需要匹配。PostgreSQL 支持具有任意谓词的部分索引,只要只涉及被索引表的列即可。但是,请记住,谓词必须与旨在从索引中受益的查询中使用的条件匹配。准确地说,只有当系统能够识别出查询的 WHERE 条件在数学上隐含索引的谓词时,才能在查询中使用部分索引。PostgreSQL 没有复杂的定理证明器,可以识别以不同形式编写的数学等价表达式。(创建一个通用的定理证明器不仅极其困难,而且可能太慢而无法真正使用。)系统可以识别简单的不等式含义,例如 x < 1 意味着 x < 2;否则,谓词条件必须与查询的 WHERE 条件的一部分完全匹配,否则索引将不会被识别为可使用。匹配发生在查询规划时,而不是运行时。因此,参数化查询子句不适用于部分索引。例如,带有参数的预准备查询可能会指定 x < ?,对于参数的所有可能值,这永远不会意味着 x < 2

部分索引的第三种可能用途不需要在查询中使用索引。这里的想法是在表的子集上创建唯一索引,如 示例 11.3 中所示。这强制执行满足索引谓词的行之间的唯一性,而不约束不满足谓词的行。

示例 11.3. 设置部分唯一索引

假设我们有一个描述测试结果的表。我们希望确保对于给定的主题和目标组合只有一个 成功 条目,但可能存在任意数量的 不成功 条目。以下是一种实现方法

CREATE TABLE tests (
    subject text,
    target text,
    success boolean,
    ...
);

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;

当成功测试很少而不成功测试很多时,这是一种特别有效的方法。也可以通过使用 IS NULL 限制创建唯一的部分索引来允许一列中仅有一个空值。


最后,部分索引也可以用于覆盖系统的查询计划选择。此外,具有特殊分布的数据集可能会导致系统在不应该使用索引时使用索引。在这种情况下,可以设置索引,使其对有问题的查询不可用。通常,PostgreSQL 会对索引使用做出合理的选择(例如,在检索常用值时会避免使用索引,因此前面的示例实际上只节省了索引大小,而没有必要避免使用索引),并且计划选择严重错误是提交错误报告的原因。

请记住,设置部分索引表明您至少知道查询规划器所知道的那么多,特别是当您知道何时索引可能有利时。形成这种知识需要经验并了解 PostgreSQL 中索引的工作原理。在大多数情况下,部分索引相对于常规索引的优势将是最小的。在某些情况下,它们会适得其反,如 示例 11.4 中所示。

示例 11.4. 不要将部分索引用作分区的替代

您可能很想创建一组大型的非重叠部分索引,例如

CREATE INDEX mytable_cat_1 ON mytable (data) WHERE category = 1;
CREATE INDEX mytable_cat_2 ON mytable (data) WHERE category = 2;
CREATE INDEX mytable_cat_3 ON mytable (data) WHERE category = 3;
...
CREATE INDEX mytable_cat_N ON mytable (data) WHERE category = N;

这是一个坏主意!几乎可以肯定,使用像下面这样声明的单个非部分索引会更好

CREATE INDEX mytable_cat_data ON mytable (category, data);

(出于 第 11.3 节 中描述的原因,将类别列放在第一位。)虽然在这个较大的索引中进行搜索可能必须下降几个树级别,但几乎肯定比选择适当的部分索引所需的规划器工作要便宜。问题的核心是系统不了解部分索引之间的关系,并且会费力地测试每个索引以查看它是否适用于当前查询。

如果您的表足够大,以至于单个索引确实不是一个好主意,那么您应该考虑使用分区(请参阅 第 5.12 节)。使用该机制,系统确实理解表和索引是非重叠的,因此可以实现更好的性能。


有关部分索引的更多信息,请参阅 [ston89b][olson93][seshadri95]

提交更正

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