SQL COMMAND · 索引与统计
REINDEX
重建索引
REINDEX索引与统计引入 10(基线)现存至 20 devel4 次语法变更
- 动词
REINDEX- 对象
—- 分组
- 索引与统计
- 引入版本
- 10(基线)
- 状态
- 现存
- 语法变更次数
- 4
- 手册小节数
- 6
版本轨迹
相对 PostgreSQL 17 语法未变,正文有更新。
语法铁道图 PostgreSQL 18
沿轨道从左向右阅读,分岔表示选择,绕行表示可选,回环表示重复。方框为参数,点击带下划线的参数可展开子规则。
REINDEX · 语法 2
option
锁模式 PostgreSQL 18
具体锁模式取决于操作变体、目标对象和执行阶段;同一命令可同时取得表锁与行锁。查看完整冲突矩阵
SHARE UPDATE EXCLUSIVE· 表级锁REINDEX … CONCURRENTLY:PostgreSQL 12 起;会话级 SHARE UPDATE EXCLUSIVE 锁覆盖被重建的索引及所属表,分多个事务完成构建、验证、切换与清理。SHARE· 表级锁REINDEX(所属表):非并发重建在所属表上取得 SHARE;被重建的索引另取 ACCESS EXCLUSIVE,不能把索引锁误写成整个表的 ACCESS EXCLUSIVE。ACCESS EXCLUSIVE· 表级锁REINDEX(被重建索引):此锁作用于索引本身,而非所属表;查询规划也会访问索引,所以非并发 REINDEX 仍可能阻塞查询。
语法概要
REINDEX [ (option[, ...] ) ] { INDEX | TABLE | SCHEMA } [ CONCURRENTLY ]nameREINDEX [ (option[, ...] ) ] { DATABASE | SYSTEM } [ CONCURRENTLY ] [name] 其中option可以是以下之一: CONCURRENTLY [boolean] TABLESPACEnew_tablespaceVERBOSE [boolean]
描述
REINDEX使用索引所属表中存储的数据重建索引,并替换索引的旧副本。以下几种场景适合使用REINDEX:
一个索引已经损坏,不再包含有效数据。尽管理论上这不该发生,但在实践中索引可能因软件缺陷或硬件故障而损坏。
REINDEX提供了一种恢复方法。一个索引已经“膨胀”,也就是说其中包含许多空页或几乎为空的页。在 PostgreSQL 中,B-树索引在某些不常见的访问模式下可能出现这种情况。
REINDEX可通过写入一个不含死页的新版本索引来减少索引的空间消耗。详见第 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选项启动服务器,该选项会阻止服务器在查找系统目录时使用索引。
一种做法是关闭服务器,并在命令行中包含-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运行期间,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支持在尽量少阻塞写入的情况下重建索引。这种方法是通过指定REINDEX的 CONCURRENTLY选项来调用的。使用该选项时,PostgreSQL必须为每个需要重建的索引对表执行两次扫描,并等待所有现有、可能使用该索引的事务结束。因此,这种方法比标准索引重建需要更多的总工作量,完成时间也明显更长,因为它必须等待那些可能修改索引的未完成事务。不过,由于它允许在索引重建期间继续进行正常操作,所以这种方法适合在生产环境中重建索引。当然,索引重建带来的额外 CPU、内存和 I/O 负载也可能拖慢其他操作。
并发重新索引会依次经历以下步骤。每个步骤都在单独的事务中运行。如果有多个要重建的索引,则每个步骤都会先遍历所有索引,再进入下一步。
先向系统目录
pg_index添加一个新的临时索引定义。该定义将用于替换旧索引。随后会在要重新索引的索引及其关联表上获取会话级SHARE UPDATE EXCLUSIVE锁,以防止处理期间发生任何模式修改。然后为每个新索引执行第一次构建。索引构建完成后,会将其标志
pg_index.indisready切换为“true”,使其准备好接收插入;执行构建的事务结束后,它就会对其他会话可见。此步骤对每个索引都在单独的事务中完成。接着执行第二次扫描,把第一次扫描期间新增的元组加入索引。此步骤同样对每个索引都在单独的事务中完成。
所有引用该索引的约束都会改为引用新的索引定义,同时索引名称也会被更改。此时,新索引的
pg_index.indisvalid会切换为“true”,旧索引则切换为“false”,并执行一次缓存失效,使所有引用旧索引的会话中的相关缓存项失效。在等待可能引用旧索引的运行中查询完成之后,旧索引的
pg_index.indisready会切换为“false”,以防止任何新元组被插入其中。删除旧索引。随后会释放为索引和表持有的
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视图中报告其进度。详见第 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命令。
另见
语法演化
相邻大版本之间的差异,新的在前。版本号链接到对应快照。
PostgreSQL 18← 17正文更新
正文更新PostgreSQL 17← 16正文更新
正文更新PostgreSQL 16← 15语法变化
+ REINDEX [ ( option [, ...] ) ] { INDEX | TABLE | SCHEMA } [ CONCURRENTLY ] name+ REINDEX [ ( option [, ...] ) ] { DATABASE | SYSTEM } [ CONCURRENTLY ] [ name ]− REINDEX [ ( option [, ...] ) ] { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] name正文更新PostgreSQL 15← 14正文更新
正文更新PostgreSQL 14← 13语法变化
+ CONCURRENTLY [ boolean ]+ TABLESPACE new_tablespace+ VERBOSE [ boolean ]正文更新PostgreSQL 13← 12语法变化
+ REINDEX [ ( option [, ...] ) ] { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] name+ VERBOSE− REINDEX [ ( VERBOSE ) ] { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] name正文更新PostgreSQL 12← 11语法变化
+ REINDEX [ ( VERBOSE ) ] { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] name正文更新PostgreSQL 11← 10正文更新
正文更新
同组命令
| 命令 | 动词 | 对象 | 版本变动 | 最近变更 |
|---|---|---|---|---|
REINDEX | REINDEX | — | 164 次 | |
| 重建索引 | 现存 | |||
CREATE INDEX | CREATE | INDEX | 153 次 | |
| 定义一个新索引 | 现存 | |||
ALTER INDEX | ALTER | INDEX | 132 次 | |
| 更改索引的定义 | 现存 | |||
DROP INDEX | DROP | INDEX | — | |
| 移除一个索引 | 现存 | |||
CREATE STATISTICS | CREATE | STATISTICS | 193 次 | |
| 定义扩展统计信息 | 现存 | |||
ALTER STATISTICS | ALTER | STATISTICS | 173 次 | |
| 更改扩展统计对象的定义 | 现存 | |||
DROP STATISTICS | DROP | STATISTICS | — | |
| 删除扩展统计对象 | 现存 | |||