Theory · MySQL · Replication & HA
三线程 × binlog 格式 × 并行回放 × GTID × 异步 / 半同步 / 组复制 —— 复制是 binlog 的下一站
主库 dump 线程推送 binlog → 从库 receiver 写 relay log → applier(coordinator + workers)并行回放
延迟从哪来、Seconds_Behind_Master 为什么不准、写后读不一致的三种工程解法
异步(默认)会丢尾部事务;半同步 AFTER_SYNC 基本不丢;MGR 多数派共识自动选主
Why Replication
读多写少是绝大多数业务的形状:主库承担写,多个从库分摊读流量——读能力横向扩展的最低成本方案(代价:延迟带来的不一致,见第 8 页)。
备份在从库做(mysqldump / 快照),不阻塞主库;全量备份 + binlog 可做按时间点恢复 PITR(见 三大日志 deck)。机房级灾备也靠跨机房复制。
主库故障时提升从库接管(failover)。复制的"追平程度"直接决定 RTO 与 RPO——这也是异步 / 半同步 / MGR 分级的根源(第 13 页推演)。
| 拓扑 | 形态 | 适用与代价 |
|---|---|---|
| 一主多从 | 1 主 N 从,从库可读可备份 | 最常见;主库 dump 线程随从库数线性增加(级联可缓解) |
| 级联复制 | 主 → 中间从 → 下级从 | 减轻主库推送压力;代价:中间层挂了下游全断、延迟叠加 |
| 双主(主主) | 两主互推,单边写或分片写 | 图省事的高可用;注意自增 id 偏移、写冲突、循环复制(server_id 过滤) |
| 组复制 MGR | 多节点多数派共识 | 真正的分布式高可用(第 12 页);限制最多,运维要求最高 |
log_bin=ON)、全部节点 server_id 唯一、复制账号带 REPLICATION SLAVE 权限。搭建入口(8.0.22+ 新语法):CHANGE REPLICATION SOURCE TO ... SOURCE_AUTO_POSITION=1; START REPLICA;Core Slide · Three Threads · §19.2.3
Format Impact · §19.2.1 / §19.2.1.3
| 格式 | 复制语义 | 体积 / 回放开销 | 风险 |
|---|---|---|---|
| STATEMENT | 从库重放语句(与主库各自执行一遍) | 最小;批量操作只记一条 | 不确定语句主从结果可能不同(见下);上下文(时区/字符集/系统变量)差异放大风险 |
| ROW(8.0 默认) | 从库重放行变更(主库执行的结果直接照搬) | 最大;binlog_row_image=FULL 默认记全行前后镜像 | 语义最安全;代价是写放大与从库回放逐行开销(并行回放缓解) |
| MIXED | 默认 statement,不安全语句自动切 row | 介于两者 | 折中;"何时切换"由实现判定,边界不透明 |
| statement "陷阱"真实清单(§19.2.1.3) | 官方判定 |
|---|---|
| NOW() / CURRENT_TIMESTAMP() | safe——被特判处理:随语句写入时间上下文,从库重放取同值。网上"NOW() 导致不一致"是流传最广的讹传 |
| RAND() / UUID() / SYSDATE() / USER() 等 | unsafe——每次执行结果不同,从库重放必然偏差 |
| 带 LIMIT 的 UPDATE / DELETE(无确定 ORDER BY) | unsafe——命中哪些行取决于执行计划,主从可能选到不同行 |
| INSERT ... SELECT 等混合语句 + 上述函数 | unsafe;MIXED 模式下自动转 row |
8.0 默认 binlog_format=ROW;手册 §19.2.1 注明 binlog_format 设置自 8.0.34 起弃用——官方方向是 row-only。RC 隔离级别下只允许 row(见 MVCC deck)。
Parallel Applier · Evolution & Versions
| 版本 | 机制 | 局限 / 说明 |
|---|---|---|
| ≤ 5.5 | 单线程 SQL 线程回放 | 主库并发写入,从库单线程追赶——延迟的结构性根源 |
| 5.6 | 库级并行(schema-based,replica_parallel_type=DATABASE) | 不同库的事务可并行;单库热点业务几乎无效 |
| 5.7 | 组级并行 LOGICAL_CLOCK:同主库组提交(group commit)的事务并行回放 | 粒度取决于主库组提交规模;依赖主库保持组间不重叠 |
| 8.0 | WRITESET:主库按"行哈希集合"计算事务依赖(binlog_transaction_dependency_tracking=WRITESET),无行冲突即可并行 | 跨库、细粒度;写入集合需开启 transaction_write_set_extraction |
| 8.0.27+ | 默认多线程:replica_parallel_workers 默认 0→4,MTA 开箱即用;replica_preserve_commit_order=ON 默认保证提交序 | 0 值自 8.0.30 起弃用;默认机制下多数场景无需再手工调 WRITESET |
| 8.0.35 / 8.4.0 | 依赖追踪变量弃用(8.0.35)→ 移除(8.4.0) | 升级坑:my.cnf 里残留 binlog_transaction_dependency_tracking=WRITESET 会导致 8.4 无法启动,升级前必须清理配置 |
Replication Lag
| 来源 | 机理 | 缓解 |
|---|---|---|
| ① 从库回放单点 | 主库写是并发的,从库回放曾是单线程(或并行度不足)——结构性差距 | MTA(workers≥4 默认)+ WRITESET 依赖;仍单库热点则有限 |
| ② 大事务 | 主库执行 1 小时的事务,binlog 整块传输,从库也要回放近 1 小时,且期间该事务"钉住"回放进度 | 拆小事务;批处理分批提交 |
| ③ DDL | 大表 DDL 无法并行、锁表时间长;statement 格式回放时同样全量执行 | gh-ost / pt-osc 在线改表、低峰执行 |
| ④ 从库自身压力 | 从库承载读流量,CPU/IO 被查询抢占,回放资源不足 | 从库降载、延迟超阈值把从库摘出读池 |
| ⑤ 网络 | 跨机房/跨地域 RTT 与带宽不足,推送慢 | 就近部署、压缩协议 replica_compressed_protocol |
| ⑥ 从库规格弱 | 从库机器配置低于主库(省钱后遗症) | 从库规格对齐主库——最朴素也最常被忽视 |
Replica_IO_Running / Replica_SQL_Running(两条链路健康)、Seconds_Behind_Master(粗估,见下页)、Retrieved_Gtid_Set vs Executed_Gtid_Set(收到 vs 回放的差值——更可靠的积压度量)。跨库精准监控可用 pt-heartbeat。
复制是物理上的异步流水线,延迟不可能为零。正确的做法不是追求消灭延迟,而是:读侧容忍(第 8 页策略)+ 写侧兜底(半同步)+ 监控告警(阈值摘除)。
Seconds_Behind_Master · Read-Your-Writes
① 基于事件时间戳与从库当前时间的差——主从时钟偏差直接污染数值;② 大事务回放期间不更新:一个回放 1 小时的事务,SBM 长期显示接近 0,结束后瞬间跳变;③ IO 断开时显示 0:relay log 被回放追平后 SBM=0,但主库新事务根本没传过来——"假追平"。生产建议 pt-heartbeat 或 GTID 差值。
用户改头像(写主库)→ 立刻刷新页面(读从库)→ 头像还是旧的。写操作到从库回放之间存在秒级窗口,任何"写后立刻读"打到从库都会踩中。
| 策略 | 做法 | 代价 / 适用 |
|---|---|---|
| 强制读主 | 写后 N 秒内(或按用户/会话标记)的读固定走主库 | 简单可靠;主库读压力回升,窗口期靠拍脑袋 |
| GTID 等待 | 写事务后取 GTID(session_track_gtids=OWN_GTID);读从库前 SELECT WAIT_FOR_EXECUTED_GTID_SET(gtid, timeout) 等该事务回放完成 | 精确的"读到自己的写";超时则回退读主;实现稍复杂 |
| 会话粘性 | 同一用户会话固定路由到主库(粘性时间窗) | 与强制读主同族;中间件层(ProxySQL/ShardingSphere)实现 |
| 半同步兜底 | 写等"至少一台从库收到 binlog"再返回 | 只保证不丢,不保证已回放可读——不能单独解决写后读 |
GTID · §19.1.3
-- GTID = source_id : transaction_id 3E11FA47-71CA-11E1-9E33-C80AA9429562:23 └─ server_uuid(实例唯一)┘ └ 序号 ┘ -- 关键集合变量 gtid_executed 已执行的全部 GTID gtid_purged 已执行但 binlog 已被清掉 Retrieved_Gtid_Set 从库已收到(relay log) Executed_Gtid_Set 从库已回放 -- gtid_mode 四态灰度 OFF → OFF_PERMISSIVE → ON_PERMISSIVE → ON (前提 enforce_gtid_consistency=ON)
| 维度 | file + position | GTID(AUTO_POSITION=1) |
|---|---|---|
| 定位方式 | 手工记录/计算 binlog 文件名与偏移 | 从库上报 gtid_executed 集合,主库自动发送缺失事务 |
| 故障切换 | 提升新主后,各从库要逐个"找位点",找错即丢数据/重复 | 改 CHANGE REPLICATION SOURCE TO ... AUTO_POSITION=1,自动对齐 |
| 幂等性 | 位点回拨会重复执行事务 | 执行过的事务天然不重复(集合判定) |
| 运维成本 | 位点是黑盒,靠脚本管理 | 集合可读可算(集合差 = 积压),迁移/搭建即声明状态 |
Async · The Default Mode
主库提交事务(binlog 落盘)→ 立即返回客户端 OK,不等任何从库;binlog 由 dump 线程异步推送,从库异步回放。主从之间是最终一致:延迟窗口内从库读旧值。
从库 receiver 主动连接主库发起复制(拉的姿态),主库为该连接起 dump 线程持续推送事件(推的行为)——"从库拉起连接、主库推数据"。主库不关心从库是否收到,这就是异步。
| 维度 | 表现 |
|---|---|
| 性能 | 最优:提交路径零跨网络等待,吞吐不受从库数量影响 |
| 数据安全 | 主库宕机可丢尾部事务(客户端已收到 OK、binlog 未送达从库)——第 13 页完整推演 |
| 故障切换 | 需要人工/脚本选新主、对齐位点或 GTID;丢多少取决于"追平程度" |
| 适用 | 绝大多数业务:能容忍极小概率尾部丢失、用性能换 SLA;配合"备份 + binlog"兜底恢复 |
Semisync · AFTER_SYNC vs AFTER_COMMIT · §19.4.10
Semisync · Timeout & Degradation
| 参数(8.0.26+ / 旧名) | 默认值 | 说明 |
|---|---|---|
rpl_semi_sync_source_enabled(rpl_semi_sync_master_enabled) | OFF | 总开关;需先安装插件 rpl_semi_sync_source(从库侧 rpl_semi_sync_replica) |
rpl_semi_sync_source_wait_point | AFTER_SYNC | 两种等待点(上页);旧名 rpl_semi_sync_master_wait_point |
rpl_semi_sync_source_timeout | 10000 ms | 超时无 ack → 自动退化为异步(semisync 关闭态),从库追上后自动恢复——"半同步不保证永远半同步" |
rpl_semi_sync_source_wait_for_replica_count | 1 | 等几个从库 ack;设 N 提高不丢概率,也放大提交延迟 |
所有从库都慢/断时,主库等到超时(默认 10 秒)后切回异步继续提交——可用性优先。这意味着半同步的"不丢"承诺只在"至少一台从库在线"时成立;监控 Rpl_semi_sync_source_status / Rpl_semi_sync_master_status 判断当前是否处于异步态。
每事务多一次"发送 + 等 ack"的网络 RTT(外加从库 relay log 落盘时间)。跨机房部署时 RTT 直接加到每个写事务上——半同步从库要就近部署;等待个数从 1 调到 2,延迟再叠加。
Group Replication · Chapter 20
事务在组内经 XCom(Paxos 变体)达成多数派共识、全局有序后提交:已提交事务必然被多数节点持有——少数节点宕机不丢数据。这是它区别于异步/半同步的根本:不是"等谁收到",而是"多数派说行才算"。
group_replication_single_primary_mode=ON 默认:组内自动选主(primary),其余节点只读,主故障自动选新主;多主模式全员可写,靠 certification(基于 WRITESET 的写集冲突检测)仲裁,冲突方回滚。
| 要求 / 限制 | 说明 |
|---|---|
| 表必须有主键 | certification 依赖主键计算写集冲突;无主键表无法入组 |
| 仅 ROW 格式 binlog + GTID ON | 写集提取与事务认证的前提 |
| 仅 InnoDB | 依赖事务引擎的隔离与回滚能力 |
| 建议 3–9 节点 | 多数派容忍 (N−1)/2 故障:3 节点容忍 1 个;每写多一次组内通信开销 |
group_replication_consistency | 控制故障切换后读一致性(BEFORE / AFTER / BEFORE_AND_AFTER 等:等积压回放再放行读写) |
Failover Walkthrough · Comparison
-- 异步复制 + 主库断电,切换到从库 t1 T1 提交 → 主库 binlog 落盘 → 客户端收到 OK(订单已确认) t2 T2 提交 → 同上 t3 dump 线程只推到 T1 就断(延迟/断网) t4 主库断电 → 提升从库为新主 t5 新主没有 T1 尾部之后的事务? —— 没有 T2:已确认的提交"消失" 老主恢复后:以新主为基准,GTID 自动补齐 (老主多出的 T2 属于"分叉",被覆盖/回滚)
| 模式 | 丢失窗口 | 切换 | 适用 |
|---|---|---|---|
| 异步 | 可能丢尾部事务(客户端已确认) | 人工/脚本,补齐靠 GTID | 性能优先、容忍极小概率丢失 |
| 半同步 (AFTER_SYNC) | 基本不丢(binlog 已到 ≥1 从库);超时退化时回到异步 | 半自动(配合 MHA/Orchestrator 等) | 金融级妥协方案:要"基本不丢"又要性能 |
| MGR | 不丢(多数派已持有已提交事务) | 组内自动选主 | 要求强一致与自动 HA;接受写放大与限制 |
Interview QA · 1/2
主库为每个从库连接开 dump(sender)线程推送 binlog 事件;从库 receiver(原 IO)线程接收写 relay log;applier(原 SQL 线程,8.0 为 coordinator + workers)读取 relay log 并行回放。核心:一推、一写、一回放。
file+pos 手工指定 binlog 文件与偏移,切换要逐个找位点、易错;GTID 以 server_uuid:seqno 标识事务,从库上报 gtid_executed,主库自动补发差集(AUTO_POSITION=1),且执行过的事务天然不重复。
不会。手册把 NOW()/CURRENT_TIMESTAMP() 特判为 safe:时间上下文随语句写入 binlog,从库取同值。真正 unsafe 的是 RAND()/UUID()/SYSDATE() 等每次结果不同的函数,以及无确定 ORDER BY 的 LIMIT 更新。
来源:回放并行度不足、大事务、DDL、从库读负载抢占、网络、从库规格。缓解:默认 MTA(workers=4)+ WRITESET、拆大事务、gh-ost 改表、延迟超阈值摘除从库、从库规格对齐。
它是基于事件时间戳的估算:①主从时钟偏差直接污染;②大事务回放期间不更新,结束瞬间跳变;③IO 断开时 relay log 追平即显示 0,真实积压不可见。更可靠:GTID 集合差(Retrieved vs Executed)或 pt-heartbeat。
①写后 N 秒强制读主;②session_track_gtids=OWN_GTID 拿 GTID,读从库前 WAIT_FOR_EXECUTED_GTID_SET 等回放,超时回退读主;③会话粘性路由。注意半同步只保证"不丢",不保证"已回放可读"。
5.6 库级并行(不同库)→ 5.7 LOGICAL_CLOCK 组级(同组提交并行)→ 8.0 WRITESET 行级依赖 → 8.0.27 起 replica_parallel_workers 默认 4,MTA 开箱即用;8.0.35 依赖追踪变量弃用、8.4.0 移除。
多 worker 并行回放时提交顺序可能与主库不同,从库只读会看到乱序历史。设为 ON:按主库提交顺序落库(并行执行、顺序提交),从库只读一致性有保障——8.0.27 起默认 ON。
Interview QA · 2/2
AFTER_SYNC(默认):binlog 落盘 → 等从库 ack → InnoDB commit → 返回;崩溃窗口内 binlog 已到从库,切换不丢(lossless)。AFTER_COMMIT:先 commit(其他会话已可见)再等 ack;此时崩溃切换,已可见的事务在从库上"消失"。
等 receiver 把事件写入 relay log 并落盘即 ack,不等 SQL 回放。所以半同步保证"从库有这笔 binlog(不丢)",不保证"从库已可读到"。要可读需 GTID 等待或应用层校验。
会。rpl_semi_sync_source_timeout 默认 10000ms:超时无 ack 自动转异步继续提交(可用性优先),从库追上后自动恢复半同步。所以"半同步不丢"的前提是至少一台从库在线;用 Rpl_semi_sync_source_status 监控当前状态。
T1、T2 已提交且客户端收到 OK,但 dump 只推到 T1 主库就断电;提升从库为新主 → T2 消失。这是 RPO 被击穿。缓解:半同步(AFTER_SYNC)或 MGR;老主恢复后以新主为基准,GTID 自动补齐/裁剪分叉。
半同步只保证"≥1 从库收到 binlog",切换正确性靠外部工具兜;MGR 用 XCom(Paxos 变体)多数派共识全局定序,已提交事务必被多数节点持有,主故障组内自动选主、certification 冲突检测。一句话:半同步是"搬运保证",MGR 是"共识保证"。
certification 靠提取事务的写集(涉及行的主键集合)判断并发冲突:无主键无法唯一标识行、statement 格式拿不到行级写集——两者都让冲突检测无从做起。因此 MGR 只收 InnoDB + 主键 + ROW + GTID 的负载。
已执行、但对应 binlog 已被 expire 清掉的那部分 GTID。新从库靠全量备份 + SET GLOBAL gtid_purged 声明"这段历史我有备份、不用 binlog 补",主库从 purged 之后的位点发事务——声明错会导致缺事务或重复报错。
①binlog_transaction_dependency_tracking 8.0.35 弃用、8.4.0 移除——配置残留直接无法启动(WRITESET 用户升级前必须清理);②术语与参数以 source/replica 命名为准;③8.4 同时翻转了一批 InnoDB 默认值(如 change_buffering=none),从库与主库参数要一起看齐,避免主从行为差异。
Related Decks
三大日志与 crash-safe —— binlog 的生成与组提交(复制的内容从哪来)
事务基础 —— 隔离级别与复制语义的边界
LRU 缓存(手撕实现) —— 哈希 + 双链表的 O(1) 结构与手撕
MVCC —— RC 仅 row binlog 的隔离级联动
InnoDB 锁体系 —— 从库回放的事务冲突与主库写入同源
数据流 = dump 推 → receiver 写 relay log → applier 并行回放
定位 = file+pos(手工)vs GTID(集合自动补差)
一致 = 不丢(半同步)× 可读(GTID 等待)两层保证
模式 = 异步(可丢)→ 半同步(退化风险)→ MGR(多数派)
版本 = 8.0.22 术语改名 · 8.0.27 默认 MTA · 8.0.35 弃用 / 8.4 移除
References
本 deck 全部版本敏感结论可溯源至下列一手材料——复习时回到手册原文,别背二手结论。
| dev.mysql.com/doc/refman/8.0/en/replication-threads.html | §19.2.3 Replication Threads:dump/sender、receiver、applier(coordinator+workers)与术语改名 |
| dev.mysql.com/doc/refman/8.0/en/replication-formats.html | §19.2.1 Replication Formats:默认 ROW、MIXED 自动切换、binlog_format 8.0.34 弃用 |
| dev.mysql.com/doc/refman/8.0/en/replication-rbr-safe-unsafe.html | §19.2.1.3:safe/unsafe 判定——NOW() 特判 safe;RAND/UUID/LIMIT 为 unsafe |
| dev.mysql.com/doc/refman/8.0/en/replication-semisync.html | §19.4.10:默认 AFTER_SYNC、ack=relay log 落盘、AFTER_COMMIT 对比、超时降级 |
| dev.mysql.com/doc/refman/8.0/en/replication-gtids-concepts.html | §19.1.3.1 GTID Format and Storage:uuid:seqno、gtid_executed/purged、自动定位 |
| dev.mysql.com/doc/refman/8.0/en/group-replication.html 及 Chapter 20 | Group Replication:XCom(Paxos)、single-primary 默认、certification、一致性保证 |
| dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-27.html · news-8-0-35.html · /8.4/en/news-8-4-0.html | 8.0.27 workers 默认 4;8.0.35 btdt 弃用;8.4.0 btdt 移除 |