SQL COMMAND · 其它对象
WAIT
等待 WAL 达到目标 LSN
WAIT其它对象引入 19于 20 移除0 次语法变更
- 动词
WAIT- 对象
—- 分组
- 其它对象
- 引入版本
- 19
- 状态
- 于 20 移除
- 语法变更次数
- 0
- 手册小节数
- 5
版本轨迹
PostgreSQL 19 新增此命令。
PostgreSQL 19 beta 4 尚未正式发布;本页来自对应手册快照,内容仍可能变化。
语法铁道图 PostgreSQL 19
沿轨道从左向右阅读,分岔表示选择,绕行表示可选,回环表示重复。方框为参数,点击带下划线的参数可展开子规则。
option
mode
语法概要
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)
语法演化
相邻大版本之间的差异,新的在前。版本号链接到对应快照。
PostgreSQL 20← 19移除此命令
PostgreSQL 19← 18新增此命令
同组命令
| 命令 | 动词 | 对象 | 版本变动 | 最近变更 |
|---|---|---|---|---|
COMMENT | COMMENT | — | 111 次 | |
| 定义或修改对象的注释 | 现存 | |||
REPACK | REPACK | — | — | |
| 重写表以回收磁盘空间 | 现存 | |||
WAIT | WAIT | — | — | |
| 等待 WAL 达到目标 LSN | 于 20 移除 | |||
ALTER LARGE OBJECT | ALTER | LARGE OBJECT | 141 次 | |
| 更改一个大对象的定义 | 现存 | |||