↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 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 / 7.1 / 7.0

REINDEX

REINDEX — 重建索引

大纲

REINDEX [ ( option [, ...] ) ] { INDEX | TABLE | SCHEMA } [ CONCURRENTLY ] name
REINDEX [ ( option [, ...] ) ] { DATABASE | SYSTEM } [ CONCURRENTLY ] [ name ]

其中 option 可以是以下之一:

    CONCURRENTLY [ boolean ]
    TABLESPACE new_tablespace
    VERBOSE [ boolean ]

描述

REINDEX 使用索引所属表中存储的数据重建索引,并替换索引的旧副本。以下几种场景适合使用 REINDEX:

  • 一个索引已经损坏,不再包含有效数据。尽管理论上这不该发生,但在实践中索引可能因软件缺陷或硬件故障而损坏。REINDEX 提供了一种恢复方法。

  • 一个索引已经“膨胀”,也就是说其中包含许多空页或几乎为空的页。在 PostgreSQL 中,B-树索引在某些不常见的访问模式下可能出现这种情况。REINDEX 可通过写入一个不含死页的新版本索引来减少索引的空间消耗。详见第 25.2 节。

  • 你修改了某个索引的存储参数(例如 fillfactor),并希望确保该更改已经完全生效。

  • 如果使用 CONCURRENTLY 选项构建索引失败,该索引会被保留为“无效”。这类索引没有用处,但用 REINDEX 重建它们可能很方便。注意,只有 REINDEX INDEX 才能在无效索引上执行并发构建。

参数

INDEX #

重新创建指定的索引。如果用于分区索引,则这种形式的 REINDEX 不能在事务块内执行。

TABLE #

重新创建指定表的所有索引。如果该表有一个辅助“TOAST”表,也会对其重新索引。如果用于分区表,则这种形式的 REINDEX 不能在事务块内执行。

SCHEMA #

重新创建指定模式中的所有索引。如果该模式中的某个表有一个辅助“TOAST”表,也会对其重新索引。共享系统目录上的索引也会被处理。这种形式的 REINDEX 不能在事务块内执行。

DATABASE #

重新创建当前数据库中除系统目录外的所有索引。系统目录上的索引不会被处理。这种形式的 REINDEX 不能在事务块内执行。

SYSTEM #

重新创建当前数据库内系统目录上的所有索引。共享系统目录上的索引也包含在内。用户表上的索引不会被处理。这种形式的 REINDEX 不能在事务块内执行。

name #

要重新索引的特定索引、表或数据库的名称。索引名和表名可以带模式限定。目前,REINDEX DATABASE 和 REINDEX SYSTEM 只能对当前数据库重新索引。它们的参数是可选的,但如果给出,就必须与当前数据库名匹配。

CONCURRENTLY #

使用此选项时,PostgreSQL 会在不获取任何会阻止表上并发插入、更新或删除的锁的情况下重建索引;而标准索引重建会阻止表上的写入(但不阻止读取),直到完成。使用此选项时有若干注意事项,见下文 Rebuilding Indexes Concurrently。

对于临时表,REINDEX 始终以非并发方式执行,因为没有其他会话可以访问它们,而且非并发重新索引开销更小。

TABLESPACE #

指定索引将在新的表空间中重建。

VERBOSE #

在每个索引被重建时打印进度报告。

boolean #

指定所选选项是否开启。可以写 TRUE、ON 或 1 来启用选项,写 FALSE、OFF 或 0 来禁用它。也可以省略 boolean 值,此时假定为 TRUE。

new_tablespace #

用于重建索引的表空间。

注解

如果怀疑某个用户表上的索引已经损坏,可以使用 REINDEX INDEX 或 REINDEX TABLE 直接重建该索引,或者重建该表上的所有索引。

如果需要从系统表上的索引损坏中恢复,情况就更复杂了。在这种情况下,重要的是系统本身没有使用任何可疑索引。(事实上,在这种场景下,你可能会发现服务器进程在启动时立即崩溃,因为它依赖损坏的索引。)要安全恢复,必须用 -P 选项启动服务器,该选项会阻止服务器在查找系统目录时使用索引。

一种做法是关闭服务器,并在命令行中包含 -P 选项来启动单用户 PostgreSQL 服务器。然后可以根据希望重建的范围,执行 REINDEX DATABASE、REINDEX SYSTEM、REINDEX TABLE 或 REINDEX INDEX。如果拿不准,就使用 REINDEX SYSTEM 来重建该数据库中的所有系统索引。然后退出单用户服务器会话并重新启动常规服务器。关于如何与单用户服务器接口交互的更多信息,参见 postgres 参考页。

另一种方法是启动一个常规服务器会话,并在其命令行选项中包含 -P。具体做法因客户端而异,但对于所有基于 libpq 的客户端,都可以在启动客户端之前将环境变量 PGOPTIONS 设置为 -P。注意,尽管这种方法不需要阻止其他客户端,但在修复完成之前,阻止其他用户连接到受损数据库可能仍然更稳妥。

REINDEX 类似于删除并重新创建索引,因为索引内容都是从头重建的。不过,两者在锁方面的考量相当不同。REINDEX 会阻止该索引所属表上的写入,但不阻止读取。它还会对正在处理的特定索引获取 ACCESS EXCLUSIVE 锁,从而阻塞试图使用该索引的读取。尤其是,无论查询内容如何,查询规划器都会尝试对该表的每个索引获取 ACCESS SHARE 锁,因此 REINDEX 实际上会阻塞几乎所有查询,只有那些计划已缓存且不使用这个索引的某些预备查询除外。相比之下,DROP INDEX 会短暂地对父表获取 ACCESS EXCLUSIVE 锁,同时阻塞写入和读取。随后的 CREATE INDEX 会阻止写入但不阻止读取;由于索引不存在,读取不会尝试使用它,因此不会发生阻塞,但读取可能被迫使用代价高昂的顺序扫描。

对单个索引或表重新索引,要求用户是该索引或表的拥有者。对模式或数据库重新索引,则要求用户是该模式或数据库的拥有者。特别要注意,这意味着非超级用户也可能重建其他用户拥有的表的索引。不过,作为一个特殊例外,当非超级用户执行 REINDEX DATABASE、REINDEX SCHEMA 或 REINDEX SYSTEM 时,除非该用户拥有共享系统目录(而这通常并非如此),否则会跳过共享系统目录上的索引。当然,超级用户始终可以重新索引任何对象。

分别可使用 REINDEX INDEX 和 REINDEX TABLE 对分区索引和分区表重新索引。指定分区关系的每个分区都会在单独的事务中重新索引。在处理分区表或分区索引时,这些命令不能在事务块内使用。

当对分区索引或分区表执行带 TABLESPACE 子句的 REINDEX 时,只有叶分区的表空间引用会被更新。由于分区索引本身不会更新,建议另外对这些分区索引单独执行 ALTER TABLE ONLY,以便后续附加的任何新分区都继承新表空间。如果命令失败,可能不会把所有索引都移动到新表空间。重新运行该命令将重建所有叶分区,并把先前未处理的索引移动到新表空间。

如果 SCHEMA、DATABASE 或 SYSTEM 与 TABLESPACE 一起使用,则系统关系会被跳过,并生成一条 WARNING。TOAST 表上的索引会重建,但不会移动到新的表空间。

并发重建索引

重建索引可能会干扰数据库的正常运行。通常 PostgreSQL 会锁住要重建其索引的表,阻止其写入,并通过一次扫描完成整个索引构建。其他事务仍可读取该表,但如果它们试图在表中插入、更新或删除行,就会阻塞直到索引重建完成。如果系统是在线生产数据库,这可能产生严重影响。对非常大的表重新索引可能需要很多小时,即便是较小的表,索引重建也可能在一段对生产系统而言不可接受的时间内阻止写入操作。

PostgreSQL 支持在尽量少阻塞写入的情况下重建索引。这种方法是通过指定 REINDEX 的 CONCURRENTLY 选项来调用的。使用该选项时,PostgreSQL 必须为每个需要重建的索引对表执行两次扫描,并等待所有现有、可能使用该索引的事务结束。因此,这种方法比标准索引重建需要更多的总工作量,完成时间也明显更长,因为它必须等待那些可能修改索引的未完成事务。不过,由于它允许在索引重建期间继续进行正常操作,所以这种方法适合在生产环境中重建索引。当然,索引重建带来的额外 CPU、内存和 I/O 负载也可能拖慢其他操作。

并发重新索引会依次经历以下步骤。每个步骤都在单独的事务中运行。如果有多个要重建的索引,则每个步骤都会先遍历所有索引,再进入下一步。

  1. 先向系统目录 pg_index 添加一个新的临时索引定义。该定义将用于替换旧索引。随后会在要重新索引的索引及其关联表上获取会话级 SHARE UPDATE EXCLUSIVE 锁,以防止处理期间发生任何模式修改。

  2. 然后为每个新索引执行第一次构建。索引构建完成后,会将其标志 pg_index.indisready 切换为“true”,使其准备好接收插入;执行构建的事务结束后,它就会对其他会话可见。此步骤对每个索引都在单独的事务中完成。

  3. 接着执行第二次扫描,把第一次扫描期间新增的元组加入索引。此步骤同样对每个索引都在单独的事务中完成。

  4. 所有引用该索引的约束都会改为引用新的索引定义,同时索引名称也会被更改。此时,新索引的 pg_index.indisvalid 会切换为“true”,旧索引则切换为“false”,并执行一次缓存失效,使所有引用旧索引的会话中的相关缓存项失效。

  5. 在等待可能引用旧索引的运行中查询完成之后,旧索引的 pg_index.indisready 会切换为“false”,以防止任何新元组被插入其中。

  6. 删除旧索引。随后会释放为索引和表持有的 SHARE UPDATE EXCLUSIVE 会话锁。

如果在重建索引时出现问题,例如唯一索引上发生唯一性违反,REINDEX 命令会失败,但除了原有索引外,还会留下一个“无效”的新索引。这个索引在查询时会被忽略,因为它可能不完整;不过它仍会带来更新开销。psql 的 \d 命令会把这样的索引报告为 INVALID:

postgres=# \d tab
       Table "public.tab"
 Column |  Type   | Modifiers
--------+---------+-----------
 col    | integer |
Indexes:
    "idx" btree (col)
    "idx_ccnew" btree (col) INVALID

如果被标记为 INVALID 的索引带有 _ccnew 后缀,那么它对应于并发操作期间创建的临时索引,推荐的恢复方法是使用 DROP INDEX 将其删除,然后再次尝试 REINDEX CONCURRENTLY。如果无效索引带有 _ccold 后缀,则它对应于未能删除的原始索引;由于重建本身实际上已经成功,推荐的恢复方法就是直接删除该索引。为了保持名称唯一,无效索引名的后缀后还可能附加非零数字,例如 _ccnew1、_ccold2 等。

常规索引构建允许同一张表上的其他常规索引构建同时进行,但一张表在同一时间只能有一个并发索引构建。在这两种情况下,同时都不允许对该表进行其他类型的模式修改。另一个区别是,常规的 REINDEX TABLE 或 REINDEX INDEX 命令可以在事务块内执行,而 REINDEX CONCURRENTLY 不行。

与任何长时间运行的事务一样,对表执行 REINDEX 可能会影响并发 VACUUM 在其他表上能够移除哪些元组。

REINDEX SYSTEM 不支持 CONCURRENTLY,因为系统目录不能并发重新索引。

此外,用于排他约束的索引不能并发重新索引。如果在此命令中直接指定这样的索引,就会报错。如果并发地对包含排他约束索引的表或数据库重新索引,这些索引将被跳过。(不使用 CONCURRENTLY 选项时,可以重新索引这类索引。)

每个运行 REINDEX 的后端都会在 pg_stat_progress_create_index 视图中报告其进度。详见第 28.4.4 节。

示例

重建单个索引:

REINDEX INDEX my_index;

重建表 my_table 上的所有索引:

REINDEX TABLE my_table;

在不假定系统索引已经有效的情况下,重建某个数据库中的所有索引:

$ export PGOPTIONS="-P"
$ psql broken_db
...
broken_db=> REINDEX DATABASE broken_db;
broken_db=> \q

重建某个表的索引,并在重建期间不阻塞对相关关系的读写操作:

REINDEX TABLE CONCURRENTLY my_broken_table;

兼容性

在 SQL 标准中没有 REINDEX 命令。

报告文档问题

阅读 上游文档. 通过 PostgreSQL 文档反馈表单.