18.8 复制与事务日志的关系
一句话
复制不是 “另写一套同步逻辑”,而是把事务日志搬过去重放:日志是 “变更的唯一真相”,复制只是它的消费者;理解这一点,才能解释 “为什么备库会延迟”“为什么切主会丢数据”“为什么逻辑复制能跨版本”。
日志的三种身份
| 日志 | 记什么 | 谁消费 | 用途 |
|---|---|---|---|
| WAL / redo(物理) | 页级变更(偏移、字节) | 崩溃恢复、物理备库 | 原子性 + 持久性 + HA |
| binlog / 逻辑日志 | 语句或行的逻辑变更 | 备库、CDC、数仓 | 复制 + 数据分发 |
| undo / 回滚段 | 旧版本 | 事务回滚、MVCC 读 | 隔离性 |
关系一句话:WAL 保证 “本机不丢”,复制保证 “别处也有”。复制位点(LSN / GTID / binlog position)就是 “日志被消费到哪” 的游标。
同步与半同步:确认点在谁那里
| 模式 | 提交何时返回 | 丢数据风险 | 延迟 |
|---|---|---|---|
| 异步 | 主库写完即返回 | 主库挂 = 丢未复制的日志 | 最低 |
| 半同步 | 至少一个备库确认收到(或落盘) | 仍有窗口(取决于确认层级) | +1 RTT |
| 同步(全副本) | 多数派落盘 | 不丢(在多数派内) | +1 RTT,可用性下降 |
关键细节:半同步的 “确认” 是收到还是落盘、是一个还是多数派,决定了真实的 RPO。写成 “半同步所以不丢数据” 是常见误判。
复制延迟从哪来
| 来源 | 表现 | 缓解 |
|---|---|---|
| 大事务 | 备库重放卡住,延迟陡增 | 拆批提交 |
| 单线程重放 | 主库并行、备库串行 | 并行复制(按库/按组提交) |
| 备库资源不足 | 重放慢于产生 | 提升备库规格、减少只读查询干扰 |
| 网络带宽 | 日志传输排队 | 压缩、专线、限流 |
| 长事务/大 DDL | 元数据锁阻塞 | 在线 DDL、低峰执行 |
延迟的直接后果是读己之写失效:客户端写完立刻读备库,读到旧值(见 18.4 复制延迟与读一致性)。
日志保留与复制槽
| 机制 | 作用 | 风险 |
|---|---|---|
| 复制槽(slot)/ 归档 | 备库没消费就不删日志 | 备库长期断连 → 日志堆积 → 磁盘打满 |
| 归档(archive) | 保留历史 WAL 供 PITR | 存储成本,需定期清理策略 |
| 日志过期删除 | 控制磁盘 | 删太早 → 备库断流,只能重建 |
配置:把 RPO 写在参数里
# PostgreSQL:同步提交与同步备库
synchronous_commit = on # 本地 WAL 落盘才返回(不丢本机)
synchronous_standby_names = 'ANY 1 (replica1, replica2)' # 至少一个备库确认
wal_level = logical # 需要逻辑复制/CDC 时
# MySQL:半同步插件
rpl_semi_sync_source_enabled = ON
rpl_semi_sync_source_timeout = 1000 # 超时后退化为异步(RPO 变差!)
-- 看复制滞后(PostgreSQL)
SELECT client_addr, state, sent_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;
反模式
| 反模式 | 后果 |
|---|---|
| 把复制当备份 | 误删同步到备库,两边一起没 |
| 备库跑重分析查询 | 重放被拖慢,延迟持续上涨 |
| 复制槽不监控 | 磁盘被 WAL 打满,主库只读 |
| 认为 “半同步 = 零丢失” | 确认层级不清,切主仍丢数据 |
| 大事务 + 逻辑复制 | 下游消费积压,位点追不上 |
| 复制位点不做持久化 | 重启后从错误位置重放,数据错乱 |
| 备库长期断连却不告警 | WAL 堆积,主库磁盘写满 |
三条铁律
🔑 日志是真相,复制是消费者;位点即 “消费到哪”。
🔑 RPO 由 “提交确认点” 决定,不由 “是否同步” 这个名字决定。
🔑 复制 ≠ 备份;备份必须独立、可恢复到任意时间点。
自检
Q1 半同步复制下,主库宕机还可能丢数据吗?
▸ 答案:可能。取决于确认层级:若备库只 “收到” 未 “落盘”,或只确认了一个备库而该备库也故障,就会丢。要接近零丢失,需要 “多数派落盘”(本质是共识),代价是延迟与可用性。
Q2 复制延迟 30 分钟,最可能的三个原因?
▸ 答案:① 大事务/大 DDL(备库单线程重放卡住);② 重放能力不足(备库规格低、并行度低、还在跑只读查询);③ 网络或日志传输瓶颈。排查顺序:看 Seconds_Behind_Master/pg_stat_replication 的位点差 → 找大事务 → 看备库资源。
Q3 复制的位点(LSN/GTID)有什么用?
▸ 答案:它是 “日志消费游标”:备库据此知道从哪继续重放,切主时据此判断谁的数据更新,监控据此算延迟。没有位点就无法做 “追赶”“断点续传”“一致性校验”。
Q4 为什么复制槽可能导致主库磁盘写满?
▸ 答案:因为复制槽的语义是 “未被消费的日志不能删”。备库长期断连(或消费停滞)时,WAL 持续堆积;磁盘满后主库可能进入只读或崩溃。必须监控槽的滞后量并设置告警/上限策略。
Q5 为什么逻辑复制能做 “部分表同步”,物理复制不行?
▸ 答案:物理复制传输的是页级字节,必须整库一致;逻辑复制传输的是行级语义,可以按表/按操作过滤。代价是逻辑复制需要主键、需要处理 DDL 同步,且延迟通常略高。
Q6 切主时如何尽量不丢数据?
▸ 答案:三步:① 确认多数派已落盘(或至少同步备库已确认);② 切主前停止写入并等待位点追平;③ 用 fencing/租约防止旧主继续写(见 21.11 领导者租约)。没有第 ③ 步,旧主恢复后会与新主同时写,产生双主。
Q7 rpl_semi_sync_source_timeout=1000 意味着什么?
▸ 答案:意味着备库 1 秒不确认就退化为异步:主库继续提交,但不再保证备库收到。它保住了可用性(不会卡死写入),却悄悄放大了 RPO——这正是 “半同步 = 不丢数据” 的常见误判来源。要真正控 RPO,应监控 半同步退化次数 并告警。
关联
- 前置:18.1 复制模型:单主 / 多主 / 无主、18.2 同步、半同步、异步、18.3 复制日志 (语句 / 行 / 逻辑)
- 并列:18.4 复制延迟与读一致性
- 下游:21.11 领导者租约、10.4 快照与备份