Skip to content

备库配置(primary_conninfo) ​

备库的搭建本质上只有两件事:把主库的数据目录复制过来,然后告诉这个副本「去哪个主库、以什么身份、取哪条 WAL 流」。第一件事由 pg_basebackup 完成,第二件事由数据目录里的 standby.signal 信号文件加上 primary_conninfo 连接串完成。pg_basebackup -R 之所以被推荐,就是因为它把这两件事一次性做完,避免手工拼装配置时的遗漏。

配置备库最容易出问题的地方不在「写什么」,而在「连接串里的每个键到底影响什么」。primary_conninfo 是一个 libpq 连接串,里面的 host / port / user / password / application_name / ssl* 等键各自承担明确职责:application_name 决定主库 pg_stat_replication 里显示的名字(也是同步复制匹配用的名字),connect_timeout 决定备库连不上主库时的等待粒度,而复制相关的行为则由 primary_slot_name 等独立参数控制——它们不在连接串里。把这些边界分清楚,才能写出可维护的配置。

另外要提醒的是:备库并不是「配置一次就不管」的静态副本。它需要持续从主库拉 WAL,一旦主库侧 WAL 被回收、网络中断、或备库自身的重放被长查询阻塞,复制都会出问题。因此备库的配置必须同时考虑「怎么连」与「被阻塞时怎么办」,后者涉及 hot_standby_feedback、max_standby_streaming_delay 这一组参数。本页按「搭建 → 连接 → 只读 → 延迟估算 → 排错」的顺序展开。

前置知识

建议先阅读:主库配置(wal_level / max_wal_senders)

基础概念 ​

备库侧配置围绕「信号文件 + 连接串 + 只读行为」三组展开。下表先给出全貌。

配置项位置作用
standby.signal数据目录声明「本实例是备库」,持续重放不停止
primary_conninfopostgresql.conf / postgresql.auto.conf连接主库的连接串
primary_slot_name同上使用的物理复制槽名
hot_standby同上是否允许只读查询(默认 on)
hot_standby_feedback同上是否向主库反馈快照,避免查询冲突
max_standby_streaming_delay同上流式复制下,重放等待查询结束的最长时间
max_standby_archive_delay同上从归档恢复 WAL 时的同类等待上限
wal_receiver_timeout同上备库判定主库失活的超时
recovery_min_apply_delay同上延迟重放的时间(延迟备库)
recovery_target_timeline同上备库跟随的时间线,默认 latest

pg_basebackup -R 与手工搭建 ​

搭建备库有两种路径,它们在「配置写入」这一步上不同:

维度pg_basebackup -R手工搭建
数据目录来源工具自动传输并保持一致性从零 initdb 或拷贝,需自行保证一致
信号文件自动创建 standby.signal手工 touch standby.signal
连接串自动把 primary_conninfo 写入 postgresql.auto.conf手工编辑配置文件
出错概率低高(容易漏信号文件或漏连接串)
适用场景标准做法学习、特殊目录布局、加链路的定制
是否支持压缩/并行支持(-z / -j)取决于手工实现

-R 是 --write-recovery-conf 的简写。它会在数据目录里创建 standby.signal,并把 primary_conninfo 追加到 postgresql.auto.conf。注意写入的是 postgresql.auto.conf(由 ALTER SYSTEM 维护的文件),而不是 postgresql.conf;后者的优先级更低,因此不要在两个文件里同时设置同一个参数而产生困惑。

standby.signal 的语义 ​

standby.signal 是一个空文件,存在即生效。它的语义是:本实例以「持续恢复」模式启动,永不主动结束。与之相对的是 recovery.signal(一次性 PITR 恢复,到达目标后停止或提升)。两者不能共存。

bash
# 最简形式:创建一个空文件
touch /var/lib/postgresql/18/main/standby.signal

在 PostgreSQL 12 之前,这些恢复参数写在 recovery.conf 里,文件被处理后会被重命名为 recovery.done。从 PostgreSQL 12 开始,recovery.conf 被彻底移除,改为「信号文件 + 常规配置文件」。因此在新版本上看到教程提到 recovery.conf,可以直接判断那套步骤已经过时。

primary_conninfo 的键一览 ​

primary_conninfo 是标准 libpq 连接串,值用单引号包裹,键值对用空格分隔。常用的键:

键是否必需说明
host必需主库地址(主机名或 IP)
port建议主库端口,默认 5432
user必需主库上的复制角色名
password视认证方式对应角色的密码
application_name强烈建议备库名,用于监控与同步复制匹配
connect_timeout建议连接主库的超时秒数
sslmode按需disable / require / verify-full 等
sslrootcert按需CA 证书路径(verify-* 时必需)
keepalives_idle按需TCP keepalive 空闲时间

注意 primary_slot_name、hot_standby、hot_standby_feedback 不是连接串的键,它们是独立参数。把它们写进 primary_conninfo 不会生效,也不会报错——这是一个常见但很难察觉的错误。

primary_slot_name 与 WAL 保留 ​

primary_slot_name 指定备库在主库上使用的物理复制槽名。使用复制槽后,主库会按这条连接回报的进度保留 WAL,不再依赖 wal_keep_size 的粗略估算。

前置条件是:该槽必须已经存在于主库上(用 pg_create_physical_replication_slot() 创建)。如果备库配置了一个不存在的槽名,备库启动时会报错,复制无法建立。这也是为什么推荐「先建槽、再起备库」的顺序。

hot_standby 与只读查询 ​

hot_standby 默认 on,表示允许备库在重放 WAL 的同时对外提供只读查询。设为 off 时,备库在恢复期间不接受任何连接,只能作为「纯粹的备份摆件」——这只在特殊场景(如对一致性要求极高、不希望查询干扰重放)下使用。这个参数需要重启。

有了 hot_standby = on,备库就能承担只读流量、报表查询、备份源等角色。但代价是:长查询会阻塞 WAL 重放,重放需要清理的行版本被查询占用时会产生冲突。这与下一个概念直接相关。

hot_standby_feedback 与查询冲突 ​

在备库上,重放是由主库 WAL 驱动的。当主库上的 VACUUM 清理了某个旧行版本,而备库上恰好有一个长时间运行的查询还需要这个版本时,就会产生查询冲突。备库处理冲突的方式是「取消那个查询」(默认行为),或者「等待查询结束」(受 max_standby_streaming_delay 约束)。

hot_standby_feedback 让备库把自己的查询所需的快照信息(以 xmin 的形式)定期反馈给主库,主库据此推迟清理这些行版本,从而避免冲突。默认 off。开启它的代价是:主库的清理会被备库的长查询「拖住」,可能造成主库表膨胀。因此这是一个「保护备库查询 vs 保护主库空间」的取舍。

只读实例的限制 ​

备库作为只读实例,允许的命令范围较广(SELECT、COPY TO、DECLARE/FETCH、SHOW/SET、事务控制、LOCK TABLE 的弱锁级别、SAVEPOINT、PREPARE/EXECUTE 等),但一切写入都被禁止(INSERT/UPDATE/DELETE/MERGE/COPY FROM/TRUNCATE/DDL)。原因很直接:备库不产生 WAL,若允许写入就会出现两条独立的 WAL 流。

有一个容易忽略的点:备库上的事务不会被分配事务 ID,因为它不写 WAL。因此依赖写事务 ID 的操作(包括序列的 nextval())在备库上不可用。

判断备库状态的函数 ​

备库上有一组函数用于判断自身状态和计算延迟,它们是排错和监控的基础:

函数返回含义
pg_is_in_recovery()boolean是否处于恢复(备库)状态
pg_last_wal_receive_lsn()pg_lsn已从网络收到的最后位置
pg_last_wal_replay_lsn()pg_lsn已重放到的最后位置
pg_last_xact_replay_timestamp()timestamptz最后一条被重放事务的提交时间
pg_wal_lsn_diff(lsn1, lsn2)numeric两个 LSN 之间的字节差

SHOW in_hot_standby(PostgreSQL 14 起可用)则以参数形式返回「当前是否处于热备状态」,比调用函数更省事。

延迟估算的两种口径 ​

「延迟」在备库上有两种口径,混用会产生误判:

  • 字节延迟:pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()),衡量「已收到但还没重放」的 WAL 字节数。这个值只有在备库与主库都持续有写入时才有意义。
  • 时间延迟:now() - pg_last_xact_replay_timestamp(),衡量「最后一条被重放的事务」距现在多久。空闲时段(没有新事务)这个值会不断增大,但此时备库其实完全跟得上。

正确解读要两者结合:字节延迟长期大于 0 说明重放跟不上接收;时间延迟大但字节延迟为 0,通常只是因为没有新写入。

语法与用法 ​

搭建备库的最小流程如下。

在主库上先创建复制槽(若使用槽):

sql
SELECT pg_create_physical_replication_slot('standby1_slot');

在备库上用 pg_basebackup 拉取基础备份:

bash
pg_basebackup \
  -h 192.168.1.10 -p 5432 -U repl \
  -D /var/lib/postgresql/18/main \
  -Fp -Xs -P -R \
  --slot=standby1_slot \
  --wal-method=stream

参数含义速读:

参数含义
-D目标数据目录
-Fp输出格式为 plain(普通目录)
-XsWAL 以流式同时传输(--wal-method=stream)
-P显示进度
-R写入 standby.signal 与 primary_conninfo
--slot使用主库上的复制槽

如果不用 -R,需要手工创建信号文件并写连接串:

bash
touch /var/lib/postgresql/18/main/standby.signal
ini
# postgresql.auto.conf 或 postgresql.conf
primary_conninfo = 'host=192.168.1.10 port=5432 user=repl password=Ch4ngeM3_Str0ng application_name=standby1 connect_timeout=10'
primary_slot_name = 'standby1_slot'
hot_standby = on

启动并验证:

bash
pg_ctl start -D /var/lib/postgresql/18/main
sql
SELECT pg_is_in_recovery() AS is_standby,
       pg_last_wal_receive_lsn() AS last_receive,
       pg_last_wal_replay_lsn()  AS last_replay;
text
 is_standby | last_receive | last_replay
------------+--------------+-------------
 t          | 0/301A120    | 0/301A120
(1 row)

详细说明 ​

为什么推荐 pg_basebackup 而不是文件系统拷贝 ​

数据目录的一致性不是「拷得快」就能保证的。一个正在运行的 PostgreSQL 实例,其数据文件随时在被后台进程修改(脏页刷盘、WAL 写入、检查点)。直接用 cp -r 拷贝一个运行中的数据目录,几乎必然得到一个不一致的副本。

pg_basebackup 走的是复制协议,服务端在备份期间会把数据目录调整到一个「可被一致恢复」的状态(备份模式),并附带必要的 WAL。它还会做校验。因此它既安全又简单:一条命令就能得到一个可用的备库起点,而且 -R 顺手把配置写好。

对比参考:pg_basebackup。

-R 到底写了什么 ​

理解 -R 写入了什么,才能在手工维护时不出错。它做两件事:

  1. 在数据目录创建空的 standby.signal;
  2. 在 postgresql.auto.conf 里追加一行 primary_conninfo(内容来自命令行上的连接参数)。

它不会写 primary_slot_name(即使你用了 --slot,槽名也只在备份期间使用,不会自动成为长期的 primary_slot_name),也不会写 hot_standby(因为默认已是 on)。因此使用复制槽时,仍然需要手工补上 primary_slot_name:

ini
primary_slot_name = 'standby1_slot'

这是一个非常容易遗漏的点:用 --slot 做完备份,只保证了备份期间的 WAL 不丢,并不等于长期复制会使用这个槽。要让它长期生效,必须显式配置 primary_slot_name。

primary_conninfo 各键的实际影响 ​

把连接串当成「能连上就行」是常见误区。下面这些键会直接影响生产行为:

键影响的方面建议
application_name主库视图显示名、同步复制匹配名每台备库唯一且有意义
connect_timeout主库不可达时备库多久放弃一次尝试设为 10~30 秒,配合日志观察
sslmode是否加密传输、是否验证证书跨不可信网络用 verify-full
keepalives_idleTCP 保活空闲时间网络设备会断开空闲连接时调小
password认证凭据优先用 ~/.pgpass 或证书,避免明文

特别注意 application_name:它不仅影响可读性,还是同步复制匹配的唯一依据。如果主库配置了 synchronous_standby_names = 'FIRST 1 (standby1)',而备库没有把 application_name 设为 standby1,同步复制就不会生效,且不会有明显的错误提示。

备库的只读限制与临时表 ​

备库允许创建临时表,这是有意的设计:临时表的数据不写入 WAL(它在会话期间存在,会话断开即消失),因此不会与重放冲突。这让备库上的报表查询可以自由使用临时表做中间结果。

但要注意两点:其一,临时表在不支持 wal_level 相应配置的情况下仍受限制;其二,临时表的 autovacuum 行为与主库不同,长期存在大量临时表的会话会在备库上产生本地膨胀。

除临时表外,一切 DDL 与 DML 都不可用。这意味着依赖「在备库上建索引」的运维方式不可行——索引必须在主库创建,然后通过重放传播到备库。

重放延迟的来源与定位 ​

备库延迟(replay lag)的来源可以分成三类,定位方法各不相同:

延迟来源现象定位方法
网络/接收慢pg_stat_wal_receiver 的 latest_end_lsn 长时间不前进检查网络带宽与 pg_stat_wal_receiver 的连接状态
重放慢(I/O 或 CPU)flush_lsn 领先 replay_lsn 且差距扩大检查备库磁盘、startup 进程 CPU
查询冲突阻塞日志出现取消查询消息;长查询频繁被中断检查 pg_stat_database_conflicts 与长查询

第一类问题在主库侧表现为 walsender 的 sent_lsn 停滞;第二类在主库侧表现为 flush_lsn 与 replay_lsn 之间的差距;第三类需要同时看备库日志与冲突计数。

hot_standby_feedback 的取舍 ​

开启 hot_standby_feedback 会带来一个不那么直观的副作用:主库的表可能变得更膨胀。原因链是:备库反馈快照 → 主库保留对应行版本 → VACUUM 不能清理 → 表与索引膨胀 → 查询变慢。

取舍的判断标准是:

  • 备库上有长时间的报表/分析查询,且不能容忍被取消 → 建议开启。
  • 主库空间紧张、写入量极大,且备库查询可以容忍偶发取消 → 建议关闭,改用 max_standby_streaming_delay 控制等待上限。
  • 备库主要用于故障切换(不跑长查询)→ 关闭即可。

max_standby_*_delay 的语义 ​

这两个参数控制「重放为了等查询结束,最多等多久」:

  • max_standby_streaming_delay:流式复制时的上限,默认 30 秒。
  • max_standby_archive_delay:从归档恢复 WAL(非流式)时的上限,默认 30 秒。

超过上限之后,重放会取消阻塞它的查询并继续推进。因此调大这个值会「保护查询、牺牲延迟」,调小则「保护延迟、牺牲查询」。值为 -1 表示无限等待(不建议,会让复制延迟无限增长)。

备库的时间线跟随 ​

备库默认通过 recovery_target_timeline = 'latest' 跟随最新时间线。这在故障切换后很重要:当主库被提升(时间线递增)后,其他备库如果还跟随旧时间线,就会重放「本该废弃的历史」。现代版本默认 latest 已经处理了大部分情况,但在复杂的切换链路(多次提升、多个归档源)中,仍需要理解时间线的含义。

时间线的深入内容参见时间线管理。

备库的日志与诊断 ​

备库的启动与复制状态在日志里有两类值得关注的输出:

  • 启动时:entering standby mode、started streaming WAL from primary at ... on timeline ...;
  • 运行中:replication terminated by primary server、could not receive data from WAL stream、recovery restart point at ...。

对于「复制为什么断了」这类问题,日志几乎是唯一的线索。建议把备库日志与主库日志放在同一个可检索的地方,并按时间对齐查看——因为断开往往是主备双方共同作用的结果(例如主库回收了 WAL,备库才发现取不到)。

wal_receiver_timeout 与连接维持 ​

备库侧有一个与主库 wal_sender_timeout 对称的参数:wal_receiver_timeout,默认 60 秒。它控制备库在多久没收到主库消息后判定连接已失效并尝试重连。

两侧的语义需要放在一起看:

参数配置位置触发方超时后的行为
wal_sender_timeout主库主库终止这条 walsender
wal_receiver_timeout备库备库终止连接并重新发起复制

如果两者差异过大(例如主库 60 秒、备库 300 秒),会出现「主库已经放弃这条连接并把 walsender 释放了,备库还在傻等」的窗口。此时如果主库的 max_wal_senders 名额紧张,备库重连时可能直接被拒。因此建议两侧保持接近的取值。

需要注意超时值要留出足够的空间容纳正常的高负载抖动。在写入量极大、单次 WAL 批次很大的链路上,备库可能长时间忙于处理而没有及时回消息,超时过短会导致频繁误断。

备库的复制槽与提升后的处理 ​

备库使用的物理复制槽有一个容易忽略的性质:槽是主库上的对象,不会随备库一起被提升。当备库被提升为新主库后,旧主库上的槽就失去了使用者;而新主库自身如果需要向其他备库提供服务,需要它自己创建槽。

这意味着一次故障切换之后,通常要做几件事:

  1. 在新主库上为其余备库创建物理复制槽(若原先没有);
  2. 让其余备库把 primary_conninfo 指向新主库;
  3. 清理旧主库上遗留的、已无使用者的复制槽,避免它继续保留 WAL。

第 3 步经常被遗忘,结果是在旧主库重新上线后,那些僵尸槽让它的 pg_wal 持续增长。清理前务必确认没有备库仍在使用该槽(pg_replication_slots.active 为 f 且确认无使用者)。

备库的性能参数考虑 ​

一个常见的错误是「备库只是副本,参数照抄主库就行」。实际上备库的工作负载性质与主库不同,有几类参数值得单独权衡:

  • 重放相关的 I/O:startup 进程重放是顺序写,但会带来大量随机读(访问被修改的页面)。备库的存储应当能承受这类混合负载。
  • 共享缓冲区:备库的 shared_buffers 同样影响重放与只读查询的效率,不应设得过小。
  • maintenance_work_mem / work_mem:如果备库主要服务报表查询,这两个参数可能需要比主库更大。
  • 检查点:备库也会做检查点(重放检查点),checkpoint_timeout、max_wal_size 会影响重放后的恢复速度。相关机制参见WAL 与检查点最佳实践。

原则是:备库的内存与存储参数按「它会跑什么查询」来配,而不是按「它是不是主库」来配。同时要保证提升之后立刻能承担写入负载——这意味着 WAL 与检查点相关参数也不应落后于主库太多。

备库搭建后的自检清单 ​

搭建完成后,用下面这张清单逐项确认,可以把大部分潜在问题在正式投产前暴露出来。

检查项期望结果
pg_is_in_recovery()t
standby.signal 存在存在且为空文件
primary_conninfo 生效SHOW primary_conninfo 能返回连接串
primary_slot_name与主库上已创建的槽名一致
pg_stat_wal_receiver有 1 行且 status = streaming
主库 pg_stat_replication出现对应 application_name 的一行
只读限制写操作被拒绝
延迟pg_wal_lsn_diff 差值接近 0

备库的归档与 restore_command 协同 ​

流复制并不排斥 WAL 归档,两者可以(也常常应该)同时启用。理解它们的协同关系,能避免备库因为「主库 pg_wal 被回收」而卡住。

基本机制是:备库的 startup 进程需要一个 WAL 段时,会按以下顺序尝试获取——

  1. 本地 pg_wal 目录里是否已经有这个段(通常由 walreceiver 提前接收并写入);
  2. 如果没有,且配置了 restore_command,就调用它从归档中取回;
  3. 如果两者都拿不到,且流复制仍然连通,就向主库请求(此时会经历一次 wal_retrieve_retry_interval 等待);
  4. 如果长时间都拿不到,备库会报错并停止复制。

因此一个常见的加固做法是:在备库上也配置 restore_command,指向与主库相同的归档位置。这样即使流复制短暂中断、或主库因为某个已失效的复制槽回收了备库还需要的 WAL,备库仍能从归档里补齐缺口,而不必重建。

几点需要注意的细节:

  • 备库上的 restore_command 是用在恢复过程中的(archive_mode 在备库上只影响归档功能,不影响 restore_command 的使用)。如果备库也承担归档职责,archive_mode 可以设为 always,让备库在重放后也把 WAL 段归档出去,这是一种常见的高可用归档策略。
  • restore_command 在 PG12 之后同样写在 postgresql.conf 中(recovery.conf 已被移除),不再是独立的配置文件。
  • 如果归档本身就依赖主库的 archive_command,那要确保归档的落盘速度能跟上 WAL 生成速度;否则备库在需要回取时会因为「归档里还没有」而继续等待。

关于 restore_command 的写法与归档配置,参见归档配置;关于归档在备库上的角色,可参考连续归档章节。

实战示例 ​

示例一:用 pg_basebackup -R 一条命令搭好备库 ​

这是在干净环境里最快的方式。前置条件是主库已完成配置、槽已创建、网络与认证已通。

bash
# 主库上先建槽
psql -h 192.168.1.10 -U postgres \
  -c "SELECT pg_create_physical_replication_slot('standby1_slot');"
bash
# 备库上拉取基础备份(同时写入 standby.signal 与 primary_conninfo)
pg_basebackup \
  -h 192.168.1.10 -p 5432 -U repl \
  -D /var/lib/postgresql/18/main \
  -Fp -Xs -P -R --slot=standby1_slot
text
waiting for checkpoint on server...
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/3000028 on timeline 1
...
pg_basebackup: base backup completed
bash
# 确认 -R 生成了什么
ls -l /var/lib/postgresql/18/main/standby.signal
grep -n 'primary_conninfo' /var/lib/postgresql/18/main/postgresql.auto.conf
text
-rw------- 1 postgres postgres 0 Sep 30 10:00 /var/lib/postgresql/18/main/standby.signal
2:primary_conninfo = 'user=repl passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer host=192.168.1.10 port=5432 sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'

输出里没有 primary_slot_name,这正是前面强调的「--slot 不等于长期使用槽」。所以紧接着补一行:

ini
primary_slot_name = 'standby1_slot'

示例二:手工搭建(不用 -R) ​

如果想完全控制配置文件,可以不用 -R,自己写两个东西。

bash
pg_basebackup -h 192.168.1.10 -P -U repl \
  -D /var/lib/postgresql/18/main -Fp -Xs
bash
# 手动创建信号文件(注意必须在数据目录内)
touch /var/lib/postgresql/18/main/standby.signal
chown postgres:postgres /var/lib/postgresql/18/main/standby.signal
ini
# 在 postgresql.conf 末尾追加
primary_conninfo = 'host=192.168.1.10 port=5432 user=repl password=Ch4ngeM3_Str0ng application_name=standby1 connect_timeout=10'
primary_slot_name = 'standby1_slot'
hot_standby = on
hot_standby_feedback = off
max_standby_streaming_delay = 30s
bash
pg_ctl start -D /var/lib/postgresql/18/main

两种方式的结果等价,区别只在于「配置由工具写」还是「由人写」。手工方式更适合需要精细控制(例如额外加 sslmode=verify-full)的场景。

示例三:验证备库状态与延迟 ​

备库上线后,先确认它在恢复模式,再量化延迟。

sql
SELECT pg_is_in_recovery() AS is_standby,
       pg_last_wal_receive_lsn() AS receive_lsn,
       pg_last_wal_replay_lsn()  AS replay_lsn,
       pg_wal_lsn_diff(pg_last_wal_receive_lsn(),
                       pg_last_wal_replay_lsn()) AS apply_lag_bytes;
text
 is_standby | receive_lsn | replay_lsn | apply_lag_bytes
------------+-------------+------------+-----------------
 t          | 0/301B2C0   | 0/301B2C0  |               0
(1 row)
sql
-- 时间口径的延迟
SELECT pg_last_xact_replay_timestamp() AS last_replay_ts,
       now() - pg_last_xact_replay_timestamp() AS replay_delay;
text
        last_replay_ts        |    replay_delay
------------------------------+--------------------
 2026-09-30 10:05:12.34+08    | 00:00:00.412
(1 row)

若 apply_lag_bytes 持续为 0,说明备库已完全跟上接收进度。若它长期不为 0,需要进一步看是「接收慢」还是「重放慢」,方法见下一示例。

示例四:判断瓶颈在接收还是重放 ​

在备库上看 pg_stat_wal_receiver,可以区分「主库没发」与「本地没应用」。

sql
SELECT status,
       receive_start_lsn,
       written_lsn,
       flushed_lsn,
       latest_end_lsn,
       slot_name,
       sender_host,
       last_msg_receipt_time
FROM pg_stat_wal_receiver;
text
  status   | receive_start_lsn | written_lsn | flushed_lsn | latest_end_lsn |   slot_name   |  sender_host  |  last_msg_receipt_time
-----------+-------------------+-------------+-------------+----------------+---------------+---------------+-------------------------
 streaming | 0/3000028         | 0/301B2C0   | 0/301B2C0   | 0/301B2C0      | standby1_slot | 192.168.1.10  | 2026-09-30 10:05:13.1+08
(1 row)

读法:

  • status = streaming 表示接收正常;若为 waiting 说明正在重连或等待主库;
  • flushed_lsn 是「已接收并刷盘」的位置,可用于完整性判断;
  • written_lsn 是「已接收并写入但未刷盘」,官方明确说明不应用于数据完整性判断;
  • 若 latest_end_lsn 长时间不前进,瓶颈在接收侧(网络或主库发送)。

反过来,如果 flushed_lsn 在前进而 pg_last_wal_replay_lsn() 不前进,说明瓶颈在重放侧(备库 I/O 或查询冲突)。

示例五:观察查询冲突 ​

当备库上有长查询与重放冲突时,冲突会被计数。这是判断「延迟是否由查询引起」的直接依据。

sql
SELECT datname,
       confl_tablespace,
       confl_lock,
       confl_snapshot,
       confl_bufferpin,
       confl_deadlock
FROM pg_stat_database_conflicts
ORDER BY datname;
text
 datname  | confl_tablespace | confl_lock | confl_snapshot | confl_bufferpin | confl_deadlock
----------+------------------+------------+----------------+-----------------+----------------
 postgres |                0 |          3 |             17 |               0 |              0
 appdb    |                0 |          0 |              2 |               0 |              0
(1 row)

confl_snapshot 增长最常见,它对应「查询需要的旧行版本已被清理」这类冲突。如果这个计数快速上升且业务可接受,可以考虑开启 hot_standby_feedback;如果业务不能容忍查询被取消,则必须调整查询本身(例如缩短单次查询时长)。

示例六:确认只读限制确实生效 ​

sql
-- 备库上尝试写入,应当被拒绝
CREATE TABLE t_demo(id int);
text
ERROR:  cannot execute CREATE TABLE in a read-only transaction
sql
-- 查一次状态,确认自己确实是备库
SELECT pg_is_in_recovery(), current_setting('transaction_read_only');
text
 pg_is_in_recovery | current_setting
-------------------+-----------------
 t                 | on
(1 row)

注意事项 ​

recovery.conf 在 PostgreSQL 12 已被移除。 不要再创建这个文件,它不会生效。恢复参数写在 postgresql.conf / postgresql.auto.conf,「这是备库」由 standby.signal 声明。

-R 不会写入 primary_slot_name。 用 --slot 只保证备份期间使用该槽。要让长期复制使用槽,必须手工配置 primary_slot_name,否则主库上的槽会一直处于 inactive 状态并持续堆积 WAL。

primary_slot_name 指定的槽必须先存在。 如果主库上没有对应槽,备库启动会失败。正确顺序是「主库建槽 → 备库使用」。

primary_slot_name 不是 primary_conninfo 的键。 把 primary_slot_name、hot_standby、hot_standby_feedback 写进连接串不会生效,也不会报错。它们是独立参数。

备库的数据目录必须与主库同主版本。 物理复制的页格式与 WAL 格式绑定主版本,跨大版本无法启动为备库。

standby.signal 必须在数据目录里,且属主正确。 放在配置文件目录、或属主不是 PostgreSQL 运行用户,都会导致启动时不被识别。表现为实例以主库方式启动,进一步可能导致数据分叉。

备库上的长查询会阻塞重放。 这是设计使然,不是 bug。要减少影响,可以用 hot_standby_feedback 或 max_standby_streaming_delay,或从查询侧限制单次执行时长(如设置 statement_timeout)。

hot_standby_feedback 会把主库的清理拖住。 开启后主库可能因保留旧版本而膨胀。开启前要评估主库的空间余量与表的膨胀容忍度,不要默认打开。

时间口径延迟在空闲时段没有参考价值。 now() - pg_last_xact_replay_timestamp() 在没有新事务时会不断增大,这是「没有新事务」,不是「备库卡住」。判断健康度要结合字节延迟与写入量。

不要手工在备库数据目录里改文件。 备库的数据目录由重放驱动,手工修改会在下一次重放覆盖该页面时被抹掉,或直接导致重放错误、复制中断。索引等对象一律在主库创建。

最佳实践 ​

  1. 优先使用 pg_basebackup -R,并补齐 primary_slot_name。 让工具负责容易出错的部分(信号文件、连接串),人工只补它不写的那一项。

  2. 给每台备库设置唯一的 application_name。 它既决定监控视图里的可读性,也是同步复制匹配的依据。命名建议包含环境与序号,例如 prod-standby-1。

  3. 连接串里显式设置 connect_timeout。 默认行为下,主库网络中断可能让备库长时间处于「正在连接」而没有明确日志。设置合理的超时便于更快发现与定位问题。

  4. 跨不可信网络必须启用 SSL 并验证证书。 使用 sslmode=verify-full 与 sslrootcert,避免 WAL 在传输链路中被窃听或篡改。

  5. 不要用明文密码,优先 ~/.pgpass 或客户端证书。 连接串会出现在 SHOW primary_conninfo、pg_stat_wal_receiver.conninfo 以及配置文件中,明文密码的暴露面比想象的大。

  6. 备库上线后立即做主备双向确认。 既看备库的 pg_is_in_recovery 与 pg_stat_wal_receiver,也看主库的 pg_stat_replication。只看一端容易漏掉「主库以为连上了、备库其实没在重放」这类问题。

  7. 为备库单独设置 statement_timeout。 备库常被用于报表分析,难免出现慢查询。给只读会话设置超时上限,可以避免单条查询长期阻塞重放。

  8. 根据业务性质决定 hot_standby_feedback,不要照抄配置。 长查询多且不可取消则开启;主库空间紧张且备库只作切换用则关闭。决策要有依据并写入运维文档。

  9. 把备库的延迟纳入监控。 至少监控「字节延迟」「时间延迟」「pg_stat_wal_receiver.status」「重放冲突计数」四项,参见《pg_stat_replication 监控》一页。

  10. 备库磁盘预留足够的 pg_wal 空间。 网络抖动或主库突发写入时,备库可能短暂积压大量 WAL。预留空间可以避免因磁盘满导致的崩溃。

  11. 定期验证备库的可切换性。 备库能持续重放不等于能顺利接管流量。应当定期在演练环境执行一次提升,验证数据完整性与应用连通性(参见灾难恢复演练)。

  12. 给备库与主库使用一致的参数基线。 尤其是内存、检查点、WAL 相关参数。备库在提升为主库后会立即承担写入,参数差异过大会在切换瞬间暴露性能问题。

下一节

继续学习:pg_stat_replication 监控

参考链接 ​