一句话结论:
binlog_expire_logs_seconds不是「到点就删」的定时器,而是「可以删的上限」——真正决定删不删的,是所有存活从库的最小复制位点。一旦从库断开,它就从「位点集合」里消失,它需要的日志立刻暴露给时间策略,到点即焚。 这篇把一次真实的「停机 58 小时,回来发现日志全没了」的 P0 复盘成可复现的机制、配置和重搭流程。
一、现象:周一早上,从库回不来了
某 SaaS 租户库,MySQL 8.0.36(主 16C64G,NVMe,binlog_format=ROW,GTID 模式),一主两从(一热备 + 一报表从)。主库日均 binlog 写入 40GB,配置:
binlog_expire_logs_seconds = 216000 # 60 小时,DBA 按「日均 40GB × 2.5 天 ≈ 100GB」拍的
binlog_max_size = 256M # 日均约 160 个文件
周五 20:00 报表从例行停机做 OS 升级(K8s 节点 drain 时忘了它挂着复制线程),到周一 09:30 才恢复。起来第一件事 START REPLICA,结果:
ERROR 1236 (HY000) at line 1:
Got fatal error 1236 from master when reading data from binary log:
'Cannot replicate because the master purged required binary logs:
replica logged the incident... requested file: mysql-bin.012988'
从库记录的消费位点是 mysql-bin.012988,而主库当时磁盘上最老的文件是 mysql-bin.013119——中间 131 个文件(约 33GB)被 binlog_purge 干净删掉了。热备因为一直在线,位点参与过「最小位点」计算,毫发无损;报表从断链 58 小时 > 60 小时保留窗,直接出局。
影响面:报表链路中断 6 小时 40 分钟(含重搭),期间 3 张 BI 看板数据停在周五 20:00,客户侧 11 条工单。
二、原理:purge 的判定公式,和你以为的不一样
很多人对 binlog_expire_logs_seconds 的理解是「日志文件年龄超过 N 秒就删」。实际的主库 purge 判定(binlog_purge_worker 周期触发)是两条同时满足才删:
- 时间条件:文件年龄 ≥
binlog_expire_logs_seconds; - 位点条件:文件严格早于「所有当前注册在线的从库的最小已请求位点」。
关键点在第二条的「当前注册在线」:从库一旦断开,它就不出现在这个集合里,主库不会为它保留任何日志。断链 1 秒,它的保护就没了——不存在「宽限期」。所以这次事故的数学本质是:
从库断链 58h + 重连后追赶 0h > 保留窗 60h − 断链前已有延迟 ~0
→ 断链时刻起,约 2 小时后(周五 22:00 前后)它需要的日志就开始被逐批删除
第二个机制细节:文件删除是整文件粒度,且删除以「事务边界」为界。一个超大事务即使开始于最老文件,也会把整个文件钉住不删(binlog 文件不拆事务)。所以实际占盘经常比 日均量 × 天数 多出 1–2 个文件——反过来也意味着,你以为「还差 3 小时才到点」,但最老文件可能因为里面有个 2GB 大事务,被提前整体删掉后,你缺的位点连「补一个文件」的机会都没有。
第三个细节:1236 有两种长相。Master has purged binary logs containing GTIDs that the slave requested 和 requested file not found 都是 1236——前者说明 GTID 集合已经不覆盖从库缺口,后者说明位点根本对不上。两者都只有一条路:重搭,回补无解。
三、做法:三件事,事前 2 天就该做完
1. 保留窗按「最慢从库的恢复预算」定,而不是按磁盘算
# 保留窗 = 最大可接受从库延迟 + 重搭启动窗口,再留 1 天余量
# 本例:允许从库断链 ≤ 24h 内恢复 + 1d 余量 → 72h;直接给 7 天(604800)更稳
binlog_expire_logs_seconds = 604800
binlog_max_size = 1G # 少切文件,少一次「钉住文件」的边界风险
7 天 × 40GB ≈ 280GB 占盘,对 NVMe 主库是完全可以接受的保险。记住:保留窗是所有从库共享的恢复预算,砍它等于砍整个拓扑里最弱一环的容灾能力。
2. 每 5 分钟对账一次「缺口 vs 保留窗」
监控脚本(cron 或接入告警平台):
#!/usr/bin/env bash
# 输出:每个在线从库「缺多少文件 / 最老文件还能活多久」
OLD=$(mysql -N -e "SHOW BINARY LOGS" | tail -1 | awk '{print $1}')
AGE=$(mysql -N -e "SELECT TIMESTAMPDIFF(SECOND, EXPIRE_TIME, NOW()) \
FROM performance_schema.replication_connection_status") # 8.0.26+
while read -r SLAVE FILE POS; do
echo "replica=$SLAVE consumed=$FILE oldest_on_master=$OLD"
done < <(mysql -N -e "SHOW REPLICA HOSTS")
# 规则:consumed 文件 < oldest 文件 → 红色告警(已不可恢复,立即重搭)
# 规则:oldest 文件年龄 / 保留窗 > 70% 且存在断链从库 → 黄色告警
核心告警只有一条:「存在断链从库,且其已消费文件早于主库最老文件」= 红色,P0 级。这条告警若周五 22:00 响,重搭成本是 50 分钟;周一早上才发现,成本是 6 小时 40 分钟。
3. 重搭标准动作(本次实测:512GB 从库,全程 43 分钟)
# 1) 从库先停复制、清状态(不要试图 RESET 后重连,缺口已不可回补)
STOP REPLICA;
RESET REPLICA ALL;
# 2) 物理全量 + 捕获位点(percona-xtrabackup 8.0.36 对应版本)
xtrabackup --backup --target-dir=/backup/full \
--user=replicator --password=xxx \
--slave-info --gtid --rstream=/backup/relay.binlog
# 3) 从库准备数据:prepare + 替换 + 灌 relay
xtrabackup --prepare --target-dir=/backup/full
rm -rf /var/lib/mysql/*; cp -a /backup/full/* /var/lib/mysql/
mysql -e "STOP SLAVE SQL_THREAD; SOURCE 'file:/backup/relay.binlog'; START SLAVE SQL_THREAD;"
# 4) 重建复制(关键:不要配 --skip-slave-start,防止下次重启悄悄掉队)
CHANGE REPLICA TO SOURCE_HOST='10.0.x.x', SOURCE_USER='replicator',
SOURCE_AUTO_POSITION=1, GET_SOURCE_PUBLIC_KEY=1;
START REPLICA;
# 5) 验收:GTID 集合对齐 + 位点进入「最老文件之后」
mysql -e "SELECT REPLICA_IO_STATE, GTID_SUBSET(gtid_executed, @@global.gtid_executed) FROM performance_schema.replication_asynchronous_connection_failover"
# 业务侧:对比 3 张核心表行数 / 抽样 20 行 checksum 一致
四、踩过的坑(反直觉清单)
- 「位点条件保护我」是错觉:保护只对你「在线」有效。断链瞬间保护归零,没有宽限期、没有告警、没有日志提示(主库侧
purge只留一条普通级别的Purged binary log)。 - 1236 不可重试:任何
RESET REPLICA/ 改位点 / 重连都是无效操作,缺口是集合级的(GTID 不连续),唯一出路是重搭。我们值班同学第一次尝试RESET后重连,多浪费了 25 分钟。 - 占盘比算式多 1–2 个文件:事务不跨文件(binlog 切分在事务边界),最老文件被大事务钉住时,删除会「跳着删」,监控上表现为占用量锯齿波动——别当 bug 处理。
--skip-slave-start是慢性毒药:它让「从库重启后悄悄不进复制」成为常态,这次事故里那个节点就带着这个配置。重搭后务必去掉。- 保留窗和
binlog_max_size要一起看:60h 保留 + 256MB 文件 = 约 750 个文件,purge 单轮要逐个 unlink,磁盘 IO 高峰时段会叠加;改 1G 文件后文件数降到约 30 个,purge 几乎无感。
五、结论与可复用清单
- 保留窗 = max(从库最大可接受断链时长, 重搭启动窗口) + 1 天余量,按拓扑里最弱一环定,不按磁盘压力定;
- 告警只盯一条:断链从库的已消费文件 < 主库最老文件 → 立即重搭,别等 1236 找上门;
- 1236 出现 = 回补无解,直接进入 xtrabackup 重搭流程,不要尝试任何位点修复;
- 从库启动配置禁用
--skip-slave-start,断链要有「自动告警 + 自动摘读」兜底; - 把「断链从库清单 + 主库最老 binlog 文件 + 保留窗剩余时长」做成一张 5 分钟刷新一次的监控面板,这张表比任何单指标都更能反映拓扑健康度。
一句话收尾:binlog 保留期不是磁盘管理参数,是容灾参数。你设的每一个小时,都是给最晚回来的那台从库留的座位——座位撤了,它就只能重新排队。