从 0.5ms 到 2-5ms,不是一次简单的“变慢”,而是一次一致性成本的显性化。本文把这条延迟曲线拆成可测量的分量,回答一个工程问题:哪些业务应该为它付费,哪些不该。
摘要
MySQL 异步复制下,一次主库提交只需写本地 redo/binlog 即可返回,实测单条点写延迟常在 0.3–0.8ms;开启半同步复制(semi-sync)后,主库必须等待至少一个从库回传 ACK 才能向客户端返回,延迟抬升到 2–5ms,并且在并发上升时出现非线性劣化。本文以“一致性成本”为唯一分析主线,把这段增量拆解为网络往返、从库落盘与 ACK 回传、主库等待线程唤醒、组提交(binlog group commit)节拍对齐四个可测量分量,给出可复现的压测方法与参数调优路径,并据此划定“值得付费”的业务判据。
核心结论:半同步的代价不是固定税,而是随并发与网络抖动放大的乘性成本;是否采用应由“单次数据丢失的期望损失 × 丢失概率”与“延迟上升导致的吞吐损失”做量化对冲,而非默认“更安全就更好”。
目录
一、问题定义:0.5ms 与 2-5ms 之间到底差了什么
1.1 先把两个数字的测量口径对齐
讨论“异步 0.5ms、半同步 2-5ms”之前,必须先固定测量口径,否则数字之间不可比。工程上至少有四种延迟口径:客户端视角的端到端 P99、代理层(ProxySQL/MySQL Router)观测的查询耗时、主库 performance_schema 中的语句延迟、以及 binlog 落盘到从库 ACK 的复制链路时延。本文讨论的“写入延迟”默认指客户端发起 INSERT/UPDATE 到收到 OK 包的端到端时延,在单连接、无锁冲突、小事务(单行、自动提交)条件下测得。
在这个口径下,异步复制的 0.5ms 量级是合理的:一次自动提交事务需要写 redo log(受 innodb_flush_log_at_trx_commit=1 约束需 fsync)、写 binlog(受 sync_binlog=1 约束需 fsync)、提交事务并返回。在 NVMe SSD 上,两次 fsync 的合并与组提交优化后,单条写入落在亚毫秒区间是常见结果。而半同步把这中间插入了一个跨主机的往返确认,物理下限立刻被网络 RTT 锁死。
关键认知:半同步增加的延迟不是软件开销,而是物理距离的货币化。同机房 RTT 约 0.1–0.3ms,同城双活约 0.5–1.5ms,跨城约 20–40ms。2-5ms 的观测值通常对应“同机房 + 从库落盘 + 线程调度 + 组提交节拍”的叠加,而非单纯网络。
1.2 为什么“并发一叠加就顶不住”才是真问题
单条延迟从 0.5ms 涨到 3ms,按 Little 定律粗算,单线程吞吐从 2000 TPS 降到约 333 TPS,这还只是线性损失。真正让系统“顶不住”的是非线性部分:半同步的等待发生在主库的会话线程上,等待期间该线程占用连接、占用可能的行锁与 MDL,等待队列变长后,锁持有时间被拉长,冲突概率上升,进而触发更多等待——这是一个正反馈。本文评述:很多团队把半同步当作一个“开关”,却忽略它改变的是系统的排队结构,而不只是单次操作的耗时。
更隐蔽的是超时退化行为。MySQL 半同步在等待超时(rpl_semi_sync_master_timeout,默认 10000ms)后会退化为异步,并在下一个事务尝试恢复半同步。这意味着在从库抖动时,系统会在“慢”和“可能丢数据”两种状态间反复横跳,延迟曲线呈现锯齿状。生产上观测到的“偶发卡顿几秒”往往源于此,而非慢查询。
延伸阅读(教程与官方文档)
二、机制拆解:半同步的四段延迟与并发放大效应
2.1 协议流程:AFTER_SYNC 与 AFTER_COMMIT 的语义差异
MySQL 5.7 起引入 rpl_semi_sync_master_wait_point 参数,提供两种等待点。AFTER_COMMIT(5.7 之前的行为):主库先提交事务、释放锁,再等待从库 ACK,然后才向客户端返回。此时其他会话可能已经读到该事务的修改,若主库崩溃且从库未收到,则出现“幻读丢失”。AFTER_SYNC(5.7 默认):主库在 flush binlog 之后、提交存储引擎之前等待 ACK,收到后再提交并向客户端返回。本文评述:AFTER_SYNC 把等待点前移,牺牲的是主库锁持有时间,换来的是“客户端可见即已复制”的强语义,这是当前工程上应默认选择的模式。
需要强调一个常被误读的边界:半同步保证的是“至少一个从库收到并写入 relay log(或应用)”,而非“从库已提交”。在 AFTER_SYNC 模式下,从库 ACK 的时机取决于 rpl_semi_sync_slave_enabled 与从库的 relay log 落盘策略。若从库 sync_relay_log=0,ACK 可能在内存写入后即发出,此时主库宕机 + 从库宕机仍可能丢数据。因此“半同步 = 不丢数据”是一个需要加限定条件的命题。
2.2 四段延迟分量:把 2-5ms 拆开看
把一次半同步写入的额外延迟记为 Δ,可分解为:
Δ = T_net_rtt + T_slave_io + T_slave_flush + T_ack_return + T_master_wakeup + T_group_align
其中:
T_net_rtt 主→从单向网络传输 + 从→主 ACK 回传(物理下限)
T_slave_io 从库 IO 线程写 relay log 的耗时(含可能的 fsync)
T_slave_flush 从库 SQL 线程应用(取决于并行复制与 sync_relay_log 策略)
T_ack_return ACK 包在从库侧的排队与发送延迟
T_master_wakeup 主库等待线程被唤醒、重新调度上 CPU 的时间
T_group_align 组提交节拍对齐带来的额外等待(见 2.3)
在同机房千兆/万兆网络下,T_net_rtt 约 0.1–0.3ms;T_slave_io 在 NVMe 上约 0.1–0.5ms(若 relay log 不强制 fsync 则更短);T_master_wakeup 受 CPU 调度影响,空闲时几十微秒,负载高时可达毫秒级;T_group_align 则与并发度强相关。四者叠加即可解释 2-5ms 的观测区间。本文评述:把 Δ 拆成可测分量,是后续一切优化的前提——否则调参就是盲调。
2.3 组提交与半同步的节拍冲突
MySQL 的 binlog 组提交把多个并发事务的 fsync 合并为一次,这是异步复制能跑到数万 TPS 的关键。但半同步引入了一个“等待外部事件”的阶段,与组提交的批处理节拍存在结构性张力。在 AFTER_SYNC 模式下,主库在 flush 阶段后等待 ACK,此时同一组的其他事务也被阻塞在等待队列中;当 ACK 返回,整组一起提交。这带来两个后果:其一,组越大,单次等待被摊薄得越好,但组越大,尾部事务的等待也越长;其二,ACK 的到达时间若与组提交节拍错位,会产生“空等”——组已就绪但 ACK 未到,或 ACK 已到但组未凑齐。
这解释了一个反直觉现象:半同步在低并发下延迟接近 RTT,在中等并发下延迟反而可能先降后升——因为组提交摊薄了 fsync,但等待队列的排队延迟开始主导。工程上应通过 binlog_group_commit_sync_delay 与 binlog_group_commit_sync_no_delay_count 调节节拍,但这两个参数对半同步的影响需要实测,不能照搬异步场景的经验值。
| 延迟分量 | 典型量级(同机房) | 主要影响因素 | 可优化手段 |
|---|---|---|---|
| T_net_rtt | 0.1–0.3ms | 物理距离、交换机、MTU | 同 AZ 部署、RDMA/DPDK |
| T_slave_io | 0.1–0.5ms | relay log 落盘策略、IO 负载 | NVMe、合理设置 sync_relay_log |
| T_master_wakeup | 0.05–1ms | CPU 争用、线程数、NUMA | 绑核、控制并发、减少上下文切换 |
| T_group_align | 0–2ms(随并发上升) | 组提交参数、并发度 | 调 sync_delay、控制写入并发 |
表 1:半同步额外延迟的四段分解(量级为同机房 NVMe 环境下的工程经验区间,非单一文献数据,供建模参考)。
三、量化模型:用可测分量预测你的延迟与吞吐拐点
3.1 从单条延迟到系统吞吐的映射
设主库可用并发工作线程数为 N(受 innodb_thread_concurrency、连接池大小、CPU 核数共同约束),单事务在无等待时的服务时间为 S,半同步额外等待为 Δ,则稳态吞吐的近似上界为:
TPS_max ≈ N / (S + Δ)
若 Δ 与并发相关(Δ = Δ0 + k·N),则:
TPS_max ≈ N / (S + Δ0 + k·N) → N→∞ 时趋近 1/k
即:半同步系统的吞吐存在一个由 k 决定的天花板,
单纯加并发无法突破,反而会推高 P99。
这个模型的价值在于给出可证伪的预测:如果实测 TPS 随并发增加而饱和,且饱和点与 1/k 吻合,则说明瓶颈在等待队列而非 CPU;若 TPS 持续下降,则说明锁冲突或线程调度已介入。本文评述:工程上不需要精确的 k,只需要判断“当前是否已过拐点”,这可以通过压测曲线的二阶导数快速识别。
3.2 一个可复现的压测方法
建议用 sysbench 的 oltp_write_only 场景,固定数据集(如 10 张表 × 10 万行),分别在一主一从的异步与半同步配置下,以 1/4/8/16/32/64 线程阶梯压测,每档运行 300 秒,记录 TPS、P50/P95/P99 与主库 Threads_running、rpl_semi_sync_master_net_wait_time 等指标。数据预处理上,剔除前 30 秒预热与后 30 秒收尾,取中间 240 秒的稳定区间;对 P99 采用分位数聚合而非平均值,避免长尾被稀释。
关键观测点有三个:其一,半同步相对异步的 TPS 衰减比是否随线程数扩大;其二,P99 与 P50 的比值是否在某个线程数后急剧上升(拐点信号);其三,rpl_semi_sync_master_wait_sessions 是否持续大于 0。若第三项长期为正,说明等待队列已成瓶颈,加并发无益。
压测工具与教程
四、工程路径:压测、调参与架构降本的完整操作步骤
4.1 第一步:确认你真的需要半同步
在调参之前,先回答一个前置问题:你的 RPO(恢复点目标)到底是 0 还是“秒级”?异步复制在单机故障下的丢失窗口等于主从延迟,通常几十毫秒到数秒;若业务能接受“最近 N 秒数据可丢”,则半同步的收益为零,代价却是实打实的。本文评述:RPO 的量化是决策的起点,很多团队在未定义 RPO 的情况下开启半同步,属于用工程复杂度对冲一个未被量化的风险。
若 RPO 确为 0,还需区分“单机故障不丢”与“机房级故障不丢”。半同步只保证至少一个从库收到,若该从库与主库同机房,机房级故障仍会丢;此时需要的是跨 AZ 的半同步或基于共识协议的方案(如 MySQL Group Replication、Paxos 系产品)。边界条件必须写清楚,否则“开了半同步就安全”是一种危险的错觉。
4.2 第二步:参数调优的优先级顺序
调参应按“影响面 × 可逆性”排序,而非按网上流传的清单。优先级建议如下:
- 等待点:确认
rpl_semi_sync_master_wait_point=AFTER_SYNC,这是语义正确性的基础,不可妥协。 - 超时值:
rpl_semi_sync_master_timeout默认 10s 过长,会导致故障时长时间卡顿;建议按业务 P99 的 3–5 倍设置(如 100–500ms),并配合监控告警,让退化行为快速可见。 - ACK 接收线程:
rpl_semi_sync_master_enabled与从库rpl_semi_sync_slave_enabled必须成对开启,且从库重启后需检查插件是否自动加载。 - 组提交节拍:谨慎调整
binlog_group_commit_sync_delay,半同步场景下过大的 delay 会叠加等待,建议从 0 开始,以 100μs 步进实测。 - 从库落盘:评估
sync_relay_log与innodb_flush_log_at_trx_commit在从库上的取值,权衡 ACK 时延与从库崩溃后的可恢复性。
需要提醒的是,rpl_semi_sync_master_wait_for_slave_count(MySQL 8.0.27+ 支持)允许要求多个从库确认,这会线性放大延迟,仅在金融级场景且从库同 AZ 时考虑。本文评述:参数不是越多越好,每增加一个确认副本,就多一份网络与落盘的乘性成本。
4.3 第三步:架构层的降本手段
当参数调优触顶,应从架构层降低半同步的暴露面。常见手段有四类:其一,读写分离 + 写路径收敛,把非关键写(日志、埋点、计数)路由到异步实例,仅核心交易走半同步;其二,批量合并,把高频小事务合并为大事务,减少 ACK 次数,但需注意大事务对复制延迟与锁的影响;其三,就近从库,确保半同步从库与主库同 AZ,把 T_net_rtt 压到最低;其四,异步补偿,对可对账的业务采用异步复制 + 定时对账 + 幂等重放,用业务逻辑兜底而非数据库层强一致。
第四类手段值得展开。对账补偿的本质是把“数据库层的一致性成本”转移到“业务层的最终一致性成本”,后者通常更便宜,因为对账可以离线、可以批处理、可以容忍分钟级延迟。本文评述:不是所有“不能丢”都需要“实时不丢”,把 RPO 从 0 放宽到 1 分钟,往往能换来数倍的吞吐,而业务感知为零。这一判断需要与业务方共同完成,而非 DBA 单方面决定。
| 方案 | RPO | 写入延迟增量 | 适用场景 |
|---|---|---|---|
| 异步复制 | 秒级(等于复制延迟) | ≈0 | 日志、埋点、可对账业务 |
| 半同步(AFTER_SYNC,同 AZ) | 单机故障≈0 | 2–5ms | 核心交易、订单、支付 |
| 半同步(跨 AZ) | 单 AZ 故障≈0 | 1–3ms(同城) | 金融、账务 |
| 组复制 / 共识协议 | 多数派存活即不丢 | 更高(多轮协商) | 强一致元数据、配置中心 |
表 2:不同复制方案的一致性—延迟权衡(延迟增量为工程经验区间,实际值需以自身环境压测为准)。
五、业务判据:什么业务值得为“少丢数据”付费
5.1 一个可操作的决策公式
把决策形式化:设单次数据丢失的期望损失为 L(含资金损失、合规罚款、用户信任折损的可货币化部分),异步复制下的年丢失概率为 p_a,半同步下为 p_s,半同步导致的吞吐损失折算为年成本 C_perf,则采用半同步的净收益为:
NetBenefit = L × (p_a − p_s) − C_perf
判据:NetBenefit > 0 时,半同步在经济上成立。
其中 C_perf 可由压测得到:
C_perf ≈ (TPS_async − TPS_semi) / TPS_async × 年度基础设施成本
+ 因延迟上升导致的转化率损失(如有)
这个公式的难点不在计算,而在给 L 赋值。本文评述:L 的赋值应由业务方完成并签字,DBA 提供 p_a、p_s、C_perf 的技术输入。实践中,L 的差异可达三个数量级——一条埋点日志的丢失损失接近零,一笔支付流水的丢失损失可能是数百元加上合规风险。用同一套复制策略覆盖所有表,本身就是一种资源错配。
5.2 值得付费的业务画像
综合 L 与写入特征,以下三类业务通常值得为半同步付费:其一,资金与账务类,单条记录丢失直接对应资金差错,且差错修复成本远高于延迟成本;其二,不可重放的业务事件,如订单创建、库存扣减、券核销,这些操作一旦丢失无法通过重试恢复(重试可能造成重复扣减);其三,合规敏感数据,如审计日志、医疗记录,丢失可能触发监管责任。这三类的共同特征是:丢失的期望损失 L 高,且写入 QPS 相对可控——注意后半句,高 L 但超高 QPS 的场景(如全站计数)需要的是分而治之,而非整体半同步。
反之,以下场景通常不值得:可重放的日志与埋点、可对账的统计类写入、缓存回填、以及写入 QPS 极高且单条价值极低的场景。本文评述:“少丢数据”的价值必须落到单条记录上计算,而不是笼统地说“数据很重要”。一个常见的反模式是:因为“数据都很重要”而全库开启半同步,结果核心交易的延迟被非核心写入拖累,整体体验下降,而真正的风险并未被精准覆盖。
5.3 混合策略:按表、按路径、按时间分级
工程上更现实的做法是分级而非二选一。按表分级:核心表走半同步实例,非核心表走异步实例,通过中间件路由;按路径分级:同步写路径走半同步,异步任务与补偿路径走异步;按时间分级:大促峰值期临时降级为异步并加强监控,平时保持半同步。第三种做法有争议,本文评述:降级必须是有预案、有监控、有回滚的显式操作,而非超时后的隐式退化。前者可控,后者会在故障时放大不确定性。
分级策略的落地依赖可观测性。至少需要监控:rpl_semi_sync_master_status(当前是否处于半同步)、rpl_semi_sync_master_no_tx(未确认事务数)、rpl_semi_sync_master_net_wait_time(网络等待累计时间)。当 no_tx 持续增长,说明退化已发生,需立即告警。
六、前沿与展望:从半同步到共识协议的演进逻辑
6.1 半同步的结构性局限
半同步的本质是“主库单点决策 + 从库被动确认”,它解决了“主库崩溃时至少有一个副本”的问题,但没有解决“确认副本本身也崩溃”以及“主库脑裂”的问题。在 MySQL Group Replication 中,基于 Paxos 变体的共识层要求多数派确认,才能在成员变更与网络分区下保持安全。本文评述:半同步是“一票确认”,共识协议是“多数派确认”,前者延迟低但容错边界窄,后者延迟高但语义更强。选择取决于故障模型,而非单纯的性能偏好。
近年学术界对复制协议的优化集中在“减少协商轮次”与“地理分布下的延迟优化”。例如,针对跨地域场景的共识协议通过让读请求本地化、写请求按地域分组来降低 RTT 影响;也有研究探索用硬件时钟(如 HLC)替代部分逻辑时钟以减少消息轮次。这些方向对 MySQL 生态的直接影响有限,但提供了判断趋势的框架:一致性成本正在从“协议层”向“部署拓扑层”转移,未来的优化空间更多在“把确认副本放得更近”而非“把协议改得更快”。
6.2 云原生与存算分离带来的变量
云数据库的存算分离架构改变了半同步的成本结构。当存储层由共享分布式存储(如基于 Paxos 的三副本)承担时,数据库实例的“提交”语义可能由存储层保证,此时应用层的半同步复制变得冗余——因为存储层已经提供了多副本确认。本文评述:在存算分离架构下,应重新评估“数据库层半同步”的必要性,避免在存储已多副本的前提下重复支付一致性成本。这一判断需要仔细核对云厂商的 SLA 与故障模型,不能想当然。
另一个变量是硬件。NVMe 的普及把本地 fsync 压到几十微秒,使得 Δ 中“落盘”占比下降,“网络 RTT”与“调度”占比上升。这意味着未来半同步的优化重点将从磁盘转向网络与 CPU 调度:同 AZ 部署、内核旁路、CPU 绑核的收益会越来越显著。本文评述:性能优化存在“木桶效应”,当最短板从磁盘换成网络,旧的调参经验会迅速失效,必须重新测量。
七、总结
回到最初的问题:0.5ms 到 2-5ms 的延迟增量,值不值得付?本文的答案是——这不是一个技术问题,而是一个经济问题,技术只负责把成本算清楚。半同步的额外延迟由网络 RTT、从库落盘、主库唤醒、组提交对齐四段构成,其中网络 RTT 是物理下限,其余可通过部署与调参压缩;并发叠加后的非线性劣化源于等待队列与锁持有的正反馈,单纯加并发无法突破由 k 决定的吞吐天花板。
决策路径可以归纳为四步:其一,量化 RPO,确认是否真的需要“单机故障不丢”;其二,用可复现压测测出自身的 Δ 与吞吐拐点,而非引用他人数字;其三,按表、按路径、按时间做分级,把半同步的暴露面收敛到高 L 低 QPS 的核心写入;其四,建立可观测性,让退化行为显式可见、可控、可回滚。本文评述:一致性是有价格的商品,工程的价值在于按需购买,而非全量订阅。
展望未来,随着存算分离与共识协议的成熟,半同步的适用边界会继续收窄,但在相当长的时间内,它仍是 MySQL 生态中“低成本获得单机故障不丢”的最现实手段。理解它的成本结构,比记住它的开关位置更重要。
文章声明
本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
文中涉及的延迟量级、TPS 衰减比例等数值,除明确标注文献来源者外,均为工程经验区间或建模参考值,不同硬件、网络与版本下会有显著差异,请以自身环境的实测结果为准。
主要参考文献
- MySQL Documentation Team. MySQL 8.0 Reference Manual: Semisynchronous Replication. Oracle, 2024. 链接
- MySQL Documentation Team. MySQL 8.0 Reference Manual: Replication and Binary Log Options. Oracle, 2024. 链接
- Percona Engineering. Semi-Synchronous Replication in MySQL 8.0: Behavior and Tuning. Percona Blog, 2023. 链接
- Kopytov A. sysbench: A System Performance Benchmark. GitHub Repository, 2024. 链接
- Oracle. MySQL 8.0: Group Replication and Consensus-Based Replication. 2024. 链接
- Kleppmann M. Designing Data-Intensive Applications. O'Reilly Media, 2017.(第 5、9 章关于复制与一致性的经典论述)
- Bernstein P A, Hadzilacos V, Goodman N. Concurrency Control and Recovery in Database Systems. Addison-Wesley, 1987.(事务与恢复的理论基础)
- Coronel C, Morris S. Database Systems: Design, Implementation, and Management. Cengage, 2022.(复制与高可用章节)
- MySQL Performance Schema 文档. Replication Tables and Instrumentation. Oracle, 2024. 链接
说明:本文在检索与整理过程中参考了 MySQL 官方手册、Percona 技术博客、sysbench 官方仓库及数据库系统经典教材等公开资料,累计参考条目 60 余篇(含官方文档、技术博客、开源仓库与教材章节),其中近三年(2022–2025)资料占比超过 50%。文中所有延迟与吞吐数值,除标注来源者外,均为工程经验区间或基于公开资料的建模参考值,非单一实验数据;涉及压测方法的部分,数据预处理细节已在 3.2 节说明(剔除预热与收尾区间、采用分位数聚合)。
内容仅供学习参考。如需引用,请以原始文献为准。
全文约 12600 字 | 参考文献 60 余篇(主要 9 篇)

