↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

测试与开发版本: 19
测试版PostgreSQL 19 Beta 4 尚未正式发布,内容与行为在正式发布前可能变化。正式内容请看当前版本手册首页。

WAIT

WAIT — 等待 WAL 达到目标 LSN

大纲

WAIT FOR LSN 'lsn'
    [ WITH ( option [, ...] ) ]

其中 option 可以是:

    MODE 'mode'
    TIMEOUT 'timeout'
    NO_THROW

而 mode 可以是:

    standby_replay | standby_write | standby_flush | primary_flush

描述

等待直到根据指定的 mode 达到指定的 lsn, 该模式决定等待 WAL 被写入、刷盘还是重放。 如果没有指定 timeout,或者其值为零,则此命令会无限期等待 lsn。

超时时会发出错误,除非在 WITH 子句中指定了 NO_THROW。 对于备库模式(standby_replay、standby_write、 standby_flush),如果在达到 lsn 之前服务器被提升, 也会发出错误。如果指定了 NO_THROW,命令会以状态字符串报告这些结果,而不抛出错误。

可能的返回值是 success、timeout 和 not in recovery。

参数

lsn #

指定要等待的目标 LSN。

WITH ( option [, ...] ) #

此子句指定等待操作的可选参数。支持以下参数:

MODE 'mode' #

指定要等待的 LSN 处理类型。如果未指定,默认值为 standby_replay。有效模式如下:

  • standby_replay:等待该 LSN 在备库上被重放(应用到数据库)。 成功完成后,pg_last_wal_replay_lsn() 将返回大于或等于目标 LSN 的值。 此模式只能在恢复期间使用。

  • standby_write:等待包含该 LSN 的 WAL 被写入备库磁盘, 但不一定已经刷盘。这比 standby_flush 更快,但持久性保证更弱, 因为数据可能仍在操作系统缓冲区中。备库上已有的 WAL(来自基础备份、 归档恢复或先前的流式传输)以及从主库新接收的 WAL 均可满足此条件。 此模式只能在恢复期间使用。

  • standby_flush:等待包含该 LSN 的 WAL 在备库上刷盘。 这提供了持久性保证,而无需等待 WAL 被应用。备库上已有的 WAL(来自基础备份、 归档恢复或先前的流式传输)以及从主库新接收的 WAL 均可满足此条件。 此模式只能在恢复期间使用。

  • primary_flush:等待包含该 LSN 的 WAL 在主库上刷盘。 成功完成后,pg_current_wal_flush_lsn() 将返回大于或等于目标 LSN 的值。 此模式只能在主库上使用(不能在恢复期间使用)。

TIMEOUT 'timeout' #

在指定且 timeout 大于零时,命令会一直等待到 lsn 达到,或者直到指定的 timeout 到期为止。 值为零(默认值)表示命令会无限期等待。

timeout 是以毫秒为单位的时长。也可以将其指定为一个字符串, 其中包含数值及其后的时间单位(见 第 19.1.1 节)。 最大值为 2147483647 ms。

小数值会舍入到最接近的毫秒数。因此,注意半毫秒或更短的 timeout 会向下舍入为零,这意味着无限期等待。

NO_THROW #

指定在超时或在主库上运行时不抛出错误。在这种情况下,可以从返回值中获取结果状态。

当 timeout 或 not in recovery 是应用预期并打算处理的结果时, 可使用此选项,例如重试等待、报告复制延迟,或为后续操作选择另一台服务器。 命令随后会将结果作为状态返回;应用必须检查该状态,才能认定已达到目标 LSN。 如果应用必须在达到目标 LSN 后才能继续,则应省略此选项, 这样等待未成功时就会通过错误停止正常执行。

返回状态也会使显式事务保持可用;如果不使用此选项,相应错误会要求先回滚事务, 或回滚到保存点,才能发出后续命令。

此选项只改变 timeout 和 not in recovery 的报告方式。 其他错误仍会抛出,包括无效输入(例如格式错误的 LSN 或无法识别的选项值), 以及等待开始前检查的所有条件(例如在恢复期间请求 primary_flush, 或在持有锁的同时等待备库 LSN)。此选项也不限制等待时长; 要限制时长,应指定 TIMEOUT。

输出

success #

此返回值表示已成功达到目标 lsn。

timeout #

此返回值表示在达到目标 lsn 之前发生了超时。

not in recovery #

此返回值表示数据库服务器不处于恢复状态。 这可能意味着数据库服务器在接收到命令时并不处于恢复状态(即在主库上执行), 也可能意味着它在达到目标 lsn 之前被提升。 在提升的情况下,此状态表示发生了时间线变更,应用程序应重新评估目标 LSN 是否仍然适用。

备注

WAIT 必须作为顶层命令执行,不能从函数、过程或 DO 块中执行。 当前事务持有快照时也不能执行。WAIT 本身不获取快照, 因此可以在 REPEATABLE READ 或 SERIALIZABLE 事务中 第一条获取快照的语句之前运行,但不能在该语句之后运行;无论处于何种隔离级别, 如果游标或导出的快照使事务持有快照,也不能运行。此处持有的快照可能延迟重放, 而 standby_replay 会等待重放,其他备库模式最终也可能需要等待重放。 这也是 WAIT 被实现为命令而不是函数或过程的原因,因为函数和过程执行时会持有快照。

恢复进行期间,如果会话已经持有锁,且尚未达到目标 lsn,则会拒绝 standby_replay(默认)、standby_write 或 standby_flush 模式的等待。这样的锁可能使启动进程直接或通过另一个会话 等待当前会话,而当前会话又在等待启动进程推进恢复。这个循环在当前会话一侧不涉及锁等待, 因而死锁检测无法发现,也没有机制可以打破它。如果已经达到目标,等待会立即返回,因此始终允许。

应在事务块之外发出 WAIT FOR,或将其作为事务块的第一条语句, 在运行任何会获取锁的操作之前执行。这也是下文示例所示读己之写模式的自然顺序: 先等待目标 lsn,再运行必须看到相应更改的查询。 注意,在 READ COMMITTED 下,即使先前语句的快照已经释放, 它获取的锁仍会保留。因此,即使隔离级别允许等待,在这类语句之后执行等待仍会被拒绝。

此限制也适用于 standby_write 和 standby_flush, 尽管流复制可以在不依赖启动进程的情况下推进这两个位置。这两个位置都不会落后于重放位置, 因此当 walreceiver 未处于活动状态时,重放可能是推进它们的唯一途径。 如果所持有的锁阻塞了重放,会话就在等待重放推进,而重放又在等待会话释放锁。 在流复制下,只有 WAL 持续到达时,这些位置才会独立推进。如果接收在达到目标之前停止, 被阻塞的启动进程就无法重新启动 walreceiver。它也无法重放推进重启点和回收 WAL 所需的 较新检查点记录,因此 pg_wal 可能被填满,导致接收在达到目标之前停止。 所以,即使等待开始时流复制处于活动状态,此限制也仍然适用。

WAIT 会根据指定的 mode 一直等待直到达到指定的 lsn。standby_replay 模式会等待该 LSN 被重放(应用到数据库), 这有助于在使用异步副本读取和主库写入时实现读己之写一致性, 前提是目标 LSN 位于主库上相关写事务的 COMMIT 记录末尾或其后。 standby_flush 模式会等待 WAL 在副本上刷入持久存储,或者等待备库上已有的 WAL 已被重放。 standby_write 模式会等待 WAL 被写入操作系统,或者已经重放;对于新接收的 WAL, 这比刷盘更快,但持久性保证更弱。 primary_flush 模式会等待 WAL 在主库上刷盘。 在所有情况下,最后一次修改的 LSN 应当存储在客户端应用程序一侧或连接池一侧。

备库模式(standby_replay、standby_write、 standby_flush)只能在恢复期间使用,而 primary_flush 只能在主库上使用。对当前服务器状态使用错误的模式会导致错误。 如果在使用备库模式等待期间备库被提升,命令将返回 not in recovery(或者在未指定 NO_THROW 时抛出错误)。提升会创建新的时间线,而正在等待的 LSN 可能指向旧时间线中的 WAL。

WAIT只比较LSN数值;它并不知道 WAL 记录属于哪条时间线。 当备库跨上游时间线切换继续恢复时,这一点很重要—例如,级联备库的上游被提升。 在这种情况下,只要所选等待模式使用的位置达到或超过该LSN数值, WAIT就会返回success,而不考虑该 LSN属于哪条时间线。需要确认目标指向预期时间线的应用程序必须自行验证时间线。

在备库服务器上,WAIT 会话可能会被恢复冲突中断。 某些恢复冲突是不可避免的:例如,重放一个表空间删除操作时,无论后端正在执行什么,都会通过终止所有后端来解决冲突。 在备库上使用 WAIT 的应用程序应准备好处理此类中断,例如通过重试命令或回退到其他机制。

示例

可以使用 WAIT 命令等待 pg_lsn 值。 例如,应用程序可以更新 movie 表,并获取位于相关写事务的 COMMIT 记录末尾或其后的 lsn。 在这里展示的默认自动提交模式下,UPDATE 会在后续的 SELECT 之前提交。 由于 synchronous_commit 可能设置为 off,此示例在主库上使用 pg_current_wal_insert_lsn 获取 lsn。

postgres=# UPDATE movie SET genre = 'Dramatic' WHERE genre = 'Drama';
UPDATE 100
postgres=# SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/0306EE20
(1 row)

然后,应用程序可以使用提交之后从主库获得的 lsn 运行 WAIT。 之后,主库上所做的更改应当保证在副本上可见。

postgres=# WAIT FOR LSN '0/0306EE20';
 status
---------
 success
(1 row)
postgres=# SELECT * FROM movie WHERE genre = 'Drama';
 genre
-------
(0 rows)

等待刷盘(数据在副本上持久化):

postgres=# WAIT FOR LSN '0/0306EE20' WITH (MODE 'standby_flush');
 status
---------
 success
(1 row)

带超时的等待写入:

postgres=# WAIT FOR LSN '0/0306EE20' WITH (MODE 'standby_write', TIMEOUT '100ms', NO_THROW);
 status
---------
 success
(1 row)

等待主库刷盘:

postgres=# WAIT FOR LSN '0/0306EE20' WITH (MODE 'primary_flush');
 status
---------
 success
(1 row)

如果在超时之前未达到目标 LSN,则会抛出错误:

postgres=# WAIT FOR LSN '0/0306EE20' WITH (TIMEOUT '0.1s');
ERROR:  timed out while waiting for target LSN 0/0306EE20 to be replayed; current standby_replay LSN 0/0306EA60

同一个示例使用带 NO_THROW 选项的 WAIT:

postgres=# WAIT FOR LSN '0/0306EE20' WITH (TIMEOUT '100ms', NO_THROW);
 status
---------
 timeout
(1 row)

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游会在正式发布前持续修订。