REINDEX — 重建索引
REINDEX [ (option[, ...] ) ] { INDEX | TABLE | SCHEMA } [ CONCURRENTLY ]nameREINDEX [ (option[, ...] ) ] { DATABASE | SYSTEM } [ CONCURRENTLY ] [name] whereoptioncan be one of: CONCURRENTLY [boolean] TABLESPACEnew_tablespaceVERBOSE [boolean]
REINDEX 使用存储在索引所属表中的数据来重建索引,替换该索引的旧副本。有以下几种使用 REINDEX 的场景:
索引已经损坏,不再包含有效的数据。虽然理论上这绝不应该发生,但实际上由于软件缺陷或硬件故障,索引可能会损坏。REINDEX 提供了一种恢复方法。
一个索引已经变得 “臃肿”,即它包含许多空页或接近空的页。在 PostgreSQL 中,某些不常见的访问模式可能导致 B-tree 索引出现这种情况。REINDEX 提供了一种方法,通过写入一个不含死页的索引新版本,来减少索引的空间消耗。更多信息请参见 Section 24.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 在每个索引被重建时以 INFO 级别打印进度报告。
boolean 指定所选选项应该被打开还是关闭。可以写 TRUE、ON 或 1 来启用该选项,写 FALSE、OFF 或 0 来禁用它。boolean 值也可以被省略,此时将假定 TRUE。
new_tablespace将在其中重建索引的表空间。
如果怀疑用户表上的某个索引损坏了,可以使用 REINDEX INDEX 或 REINDEX TABLE 简单地重建该索引或该表上的所有索引。
如果需要从系统表索引的损坏中恢复,情况会更加棘手。在这种情况下,系统本身绝不能使用任何可疑的索引。(实际上,在这种场景中,你可能会发现服务器进程在启动时就立刻崩溃,原因正是依赖于损坏的索引。)要安全地恢复,必须用 -P 选项启动服务器,该选项阻止服务器在系统目录查找中使用索引。
一种做法是关闭服务器,然后启动单用户 PostgreSQL 服务器,并在其命令行中包含 -P 选项。接着,根据你想要重建的范围,可以发出 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 运行期间,search_path 会被临时改为 pg_catalog, pg_temp。
重建单个索引或表需要对表拥有 MAINTAIN 特权。注意,虽然对分区索引或分区表执行 REINDEX 需要对分区表拥有 MAINTAIN 特权,但这类命令在处理各个分区时会跳过特权检查。重建一个模式或数据库要求是该模式或数据库的拥有者,或者拥有 pg_maintain 角色的特权。特别要注意,因此非超级用户也可以重建其他用户拥有的表的索引。不过,作为一种特殊的例外,REINDEX DATABASE、REINDEX SCHEMA 和 REINDEX SYSTEM 会跳过共享目录上的索引,除非用户对这些目录拥有 MAINTAIN 特权。
分别通过 REINDEX INDEX 或 REINDEX TABLE 支持重建分区索引或分区表。指定分区关系的每一个分区都会在一个单独的事务中被重建。在处理分区表或分区索引时,这些命令不能在事务块内使用。
在对分区索引或分区表使用带 TABLESPACE 子句的 REINDEX 时,只有叶子分区的表空间引用会被更新。由于分区索引不会被更新,建议对它们单独使用 ALTER TABLE ONLY,这样以后附加的新分区才能继承新的表空间。如果失败,可能没有把所有索引都移动到新表空间。重新运行该命令将重建所有叶子分区,并把之前未处理的索引移动到新表空间。
如果 SCHEMA、DATABASE 或 SYSTEM 与 TABLESPACE 一起使用,系统关系会被跳过,并且只会产生一条 WARNING。TOAST 表上的索引会被重建,但不会被移动到新表空间。
重建索引可能会干扰数据库的正常运行。通常,PostgreSQL 会锁定正在重建其索引的表以禁止写入,并通过一次表扫描来执行整个索引构建。其他事务仍然可以读取该表,但如果它们试图在该表中插入、更新或删除行,就会被阻塞,直到索引重建完成。如果系统是一个正在运行的生产数据库,这可能造成严重的影响。非常大的表建立索引可能需要许多个小时,即使对于较小的表,索引重建把写入者锁在外面的时间长度对生产系统而言也可能长到无法接受。
PostgreSQL 支持以对写入加锁最少的方式重建索引。这种方法通过指定 CONCURRENTLY 这一 REINDEX 选项来启用。使用这个选项时,PostgreSQL 必须为每个需要重建的索引对表执行两次扫描,并等待所有可能使用该索引的现有事务终止。这种方法比标准的索引重建需要更多的总工作量,并且完成时间显著更长,因为它需要等待可能修改该索引的未完成事务。不过,由于它允许在索引重建期间继续正常操作,这种方法对于在生产环境中重建索引非常有用。当然,索引重建带来的额外 CPU、内存和 I/O 负载可能会拖慢其他操作。
一次并发索引重建会发生以下步骤。每一步都在一个单独的事务中运行。如果有多个索引需要重建,那么每一步都会遍历所有索引,然后再进入下一步。
一个新的临时索引定义被添加到目录 pg_index 中。这个定义将被用来替换旧的索引。会在正在重建的索引及其关联的表上获取会话级的 SHARE UPDATE EXCLUSIVE 锁,以防止在处理期间发生任何模式修改。
对每个新索引都会执行第一遍构建索引的扫描。索引构建完成后,它的标志 pg_index.indisready 会被切换为 “真”,使其准备好接受插入,并且在执行构建的事务结束后对其他会话可见。这一步对每个索引都在一个单独的事务中完成。
然后执行第二遍扫描,把第一遍扫描运行期间添加的元组加入进来。这一步同样对每个索引在一个单独的事务中完成。
所有引用该索引的约束都被改为引用新的索引定义,并且索引的名称也被更改。此时,pg_index.indisvalid 会被切换:新索引切换为 “真”,旧索引切换为 “假”,并且会执行一次缓存失效处理,使所有引用旧索引的会话都被失效。
在等待可能引用旧索引的运行中查询完成之后,旧索引的 pg_index.indisready 会被切换为 “假”,以阻止任何新的元组插入。
旧索引被删除。针对这些索引和该表的 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 视图中报告其进度。详情请参见 Section 27.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 命令。