当前位置:首页>排行榜>PG 流复制:同步模式怎么选

PG 流复制:同步模式怎么选

  • 更新时间 2026-10-16 00:36:34
PG 流复制:同步模式怎么选

一、先给结论

同步复制只做一件事:提交时等几个从库点头。写错组合,挂一台从库就写死主库。

而"等谁、等几个",全写在 synchronous_standby_names 这一个参数里——改一个关键字,就是另一种容错行为。

一句话结论:生产一主两从选 ANY 1,挂一台从库不影响写入;只有业务明确要求"数据必须同时落在两台机器上"时,才用 ANY 2 或 FIRST 2,并且必须接受"任意一台从库故障 = 写入停摆"。

记两个关键字就够了:

  • FIRST 认顺序
    ——按名单顺序取前 N 个,第一个活着就得等它,它慢,大家一起慢
  • ANY 认数量
    ——名单里任意 N 个回了 ACK 就算数,谁先回应谁作数

二、主库 commit 的时候,在等谁

一次同步提交,主库要走完四步:

  • 写完本地 WAL
  • 按 synchronous_standby_names 挑出目标备库,把 WAL 推过去
  • 等备库按 synchronous_commit 指定的级别回 ACK
  • 收到足够 ACK,才把"提交成功"返回给客户端

这就解释了同步复制下的一个常见错觉:以为是"从库变慢了",其实主库的每一次提交,都在等从库。从库磁盘抖一下,业务侧感受到的是数据库整体变慢。

两个参数各管一头:

参数
管什么
常用取值
synchronous_standby_names
等谁、等几个
空串 / 裸列表 / FIRST n / ANY n
synchronous_commit
等到哪一步
off
 / local / remote_write / on / remote_apply

这篇只讲第一个——"等谁"。第二个"等到哪一步",坑一点不比这个少,留到下一篇单独说。

三、六种配法,六种行为

以一台主库加两台从库(pg2、pg3)为例,synchronous_standby_names 的几种写法,对应的行为完全不同:

标"阻塞"的那几行是同一类问题:当确认数等于从库总数时,同步就没有冗余了。 少一个 ACK,主库只能站在原地等,pg_stat_activity 里一排 wait_event = SyncRep,客户端全部超时。

而 FIRST 1 和 ANY 1 的区别只在"等谁":

  • FIRST 1
     认顺序——pg2 只要在名单里且活着,主库就一直等它。它那块盘抖一下,全公司的提交跟着抖
  • ANY 1
     认数量——pg2 不响应,pg3 回 ACK 一样算数。天生抗单点,所以它是生产一主两从最常用的配置

一个容易记混的细节:sync_priority 在 ANY 模式下恒为 1,它不排序。要看顺序,只能用 FIRST。

四、sync_state 四个取值怎么读

pg_stat_replication 里有一列 sync_state,判断"现在到底谁在扛同步"就靠它:

取值
含义
sync
当前被选中,主库 commit 在等它
potential
在候选名单里,但本轮没被选中(FIRST 模式特有)
quorumANY
 模式的候选,凑够数量即提交
async
完全不在同步名单里,纯异步

看一眼就能回答两个问题:现在是谁在扛同步(sync 那一行)、切换代价有多大(有几个 sync / quorum 候选)。

五、怎么选:三种场景,三种答案

场景
选择
为什么
代价
一般业务(默认)
ANY 1 (pg2,pg3)
一台从库挂了不影响写入,可用性风险最低
少一份同步副本;主库故障切换时可能丢一个事务窗口
账务 / 关键数据
ANY 2 (pg2,pg3)
事务必须落在两台机器上才算提交成功
挂任意一台从库 → 写入停摆
,必须配好高可用与告警
读扩展为主
置空(纯异步)
延迟最低,主库提交不等任何人
主库故障时可能丢数据,切换后的数据缺口无法预估

选型原则其实只有一句:先问清楚"这份数据丢一个事务窗口能不能接受"。

能接受,就 ANY 1;不能接受,就 ANY 2 或 FIRST 2——但必须把"从库故障 = 写入停摆"这一条写进运维预案和告警里,而不是只在设计文档里写一句"我们开了强同步"。

还有个写法值得单独点出来:别用 'pg2' 这种只写一台的配置。 它看着像"精确指定",实际是把同步复制的全部风险押在一台机器上——你明明有两台从库,出事的时候一台也赔不上。

六、改配置:两条命令 + 一步验证

synchronous_standby_names 是 sighup 级参数,改完 reload 即可,不用重启实例:

ALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (pg2,pg3)';SELECT pg_reload_conf();

验证是否真的生效:

SELECT application_name, state, sync_priority, sync_stateFROM pg_stat_replication;

这里有个必须提前确认的细节:名单里的名字取自备库 primary_conninfo 的 application_name,既不是主机名,也不是 pg_stat_activity 里的用户名。写错了会怎样?主库找不到任何一个同步备库,所有提交直接挂起。

改之前先在备库上把名字核一遍:

SHOW primary_conninfo;   -- 备库执行,注意输出里带密码,别往聊天窗口贴

七、三个坑,每个都能砸出事故

7.1 从库挂了写入卡死:第一反应是降级,不是删配置

FIRST 2 / ANY 2 下从库故障,主库的行为是所有写事务原地等待。这时候有三种止血手段,性质完全不同:

手段
生效范围
后果
SET LOCAL synchronous_commit = off
当前事务
只牺牲这一个事务的持久性,最干净
ALTER SYSTEM SET synchronous_commit = off
 + reload
全局,立刻生效
所有提交不再等备库;切换时可能丢数据,事后必须改回
ALTER SYSTEM SET synchronous_standby_names = ''
全局
等于关掉同步复制,且极易忘记改回来——不推荐

synchronous_commit 的五个档位,本质是把"等到哪一步"逐级放宽:

取值
主库 commit 等到
off
本地写完即返回,不等
local
等本地 WAL 落盘
remote_write
等备库收到并写入 OS 缓冲(备库此刻 crash 会丢)
on
(默认)
等备库刷盘
remote_apply
等备库回放完成(最强,可保读一致性)

降级到 remote_write 是最常用的折中:备库还在,事务还等它收到,但不等它落地——写入阻塞能缓解,代价是备库如果在这段时间崩溃会丢几个事务。

降级是应急动作,不是长期方案。

7.2 复制槽会反向撑爆主库

这条比从库挂掉更凶,而且跟同步没关系——用了复制槽就有。

复制槽的语义是"主库承诺为这个备库保留 WAL,直到它确认收到"。于是:

  • 备库长期离线 → 主库持续保留 WAL → pg_wal 无限膨胀 → 磁盘满 → 主库写入全挂
  • 这时 wal_keep_size 救不了场:复制槽的保留优先级更高,WAL 不会被回收

先看清楚每个槽落后了多少:

SELECT slot_name, active,       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retainedFROM pg_replication_slots;

PG 13 起可以给单个槽设保留上限,超限后主库主动废弃这个槽:

max_slot_wal_keep_size = 16GB

要记住的是:槽被废弃不等于没代价——备库得重新做一次 pg_basebackup 全量。这道保护是"两害相权取其轻",不是免费的。

7.3 同步复制不等于高可用

同步复制保证的是数据不丢,不是服务不停。

主库宕机时,它不会自己把从库提上来。原生手段是人工 pg_ctl promote,从判断、决策到执行全在人手里。一主两从加同步复制,只把"数据一致性"这一半做完了;"自动切换"那一半要靠 Patroni + ETCD 这类外部组件。

所以配完同步复制,最该同步落下的是两个告警:从库状态掉了、写入延迟超标。前者不告警,你会在业务已经卡死之后才知道。

八、上线前的检查清单

检查项
要确认的事
参数
synchronous_standby_names
 里的名字与备库 application_name 完全一致
冗余
确认数小于从库总数(留一台余量),除非业务明确要求两份落地
复制槽
每个备库一个物理槽,且已设 max_slot_wal_keep_size
磁盘
pg_wal
 独立盘,容量告警阈值设在 70% 以下
降级预案
写阻塞时的处置顺序与执行人(先按事务降级,再考虑全局)
告警
sync_state
 由 sync / quorum 变 async、或写延迟超阈值要告警
演练
真停一台从库,确认"是阻塞还是继续写"与设计预期一致

最后一行最容易被跳过,也最值钱——同步复制的行为差异,只有真的拔掉一台从库才看得出来。


随机文章