MySQL binlog安全清理指南:掌握PURGE命令,避免主从复制中断
mysql-bin.000001、mysql-bin.000002……如果某天你发现数据目录里这样的文件越来越多甚至把磁盘空间撑到告警第一反应大概率是把它们删了。这个问题听起来实在太简单简单到很多人在生产环境里直接敲了rm -f mysql-bin.0000xx然后高兴地看着磁盘使用率降了下来。但作为一个在数据库运维里摸爬滚打多年的人我想告诉你删除binlog日志文件这个动作是所有MySQL操作里最容易引发二次事故的动作之一。这篇文章我会从binlog到底是什么讲起把官方推荐的清理命令、生产维护方案、我遇到过的事故案例和误删后的应对思路都拆开说清楚。无论你是运维、DBA还是自建MySQL的开发者看完应该都能建立一套自己的binlog清理流程。1. 为什么删除binlog这么简单的问题却是运维事故的重灾区1.1 binlog到底是什么删除它到底在删什么很多人把binlog当成普通日志文件觉得删了无非是少点历史记录。这是最危险的理解。binlog是MySQL Server层的二进制日志它记录的是所有会引起数据变更的写操作包括INSERT、UPDATE、DELETE、CREATE TABLE、ALTER TABLE这些但SELECT和SHOW这类读操作不会被记录。它的核心工作方式是一个接一个地追加写入事件event写满一个文件后切换到下一个于是形成一串编号连续的文件序列。这串文件不是孤立的。MySQL同时维护一个.index索引文件里面按顺序记录着每个binlog文件的路径。比如默认的数据目录下可能有mysql-bin.000001到mysql-bin.000100这100个文件.index里就对应着100行路径。真正管理这些文件的不是操作系统而是MySQL自己。你执行的任何删除动作本质上都是在和这套“文件序列 索引”的机制打交道。为什么说“删除binlog日志文件”是个系统工程因为binlog不只是一堆历史文件它承担着三个关键角色。第一它是数据恢复的“时间线”全量备份加binlog回放是MySQL最经典的恢复体系第二它是主从复制的“数据源”从库依赖拉取主库的binlog来保持同步第三它是很多外部数据管道的“输入”比如常见的增量同步工具就是解析binlog内容来工作的。你每删掉一个binlog文件就相当于在这条时间线上抽掉了一截。1.2 三个不能忽略的使用场景恢复、复制、外部数据管道先说说数据恢复。假设某天凌晨3点数据库出了故障你需要用前一天晚上的全量备份再配合2点到3点之间的binlog把数据恢复到故障前一刻。如果这个时间段的binlog文件早就被你当作“没用的日志”删了那么恢复窗口就断了数据只能恢复到全量备份那一刻中间丢失的数据只能自认倒霉。再说主从复制。从库的IO线程会跑到主库拉取binlog文件写入自己的relay log再由SQL线程回放。主库如果提前把某个binlog删了而从库还没拉到那个文件从库的IO线程会直接报错复制中断。这个错误码通常就是1236后面跟着一句“无法找到对应的binlog文件”。复制一断主从数据就开始漂移如果不及时发现后面所有基于从库的读写分离、报表查询都会拿到过期数据。外部数据管道就更好理解了。很多团队用工具监听binlog来实时同步数据到数仓或缓存系统它们的消费位点可能落后于主库实时写入好几分钟甚至更久。如果主库按自己的节奏把binlog删了而消费端还没读到那个位置那这部分数据变更就再也拿不到了。所以你会看到删除binlog从来不是删“几个文件”那么简单它实际影响的是整个数据链路的连续性。2. 官方给出的正确清理姿势PURGE与自动过期机制2.1 自动过期一条参数让binlog“到点自己走”MySQL早就提供了自动清理机制目的就是避免binlog无限堆积。在MySQL 5.7及之前的版本用的是expire_logs_days单位是天。比如设置expire_logs_days7表示只保留最近7天的binlog。到了MySQL 8.0官方引入了binlog_expire_logs_seconds单位是秒可以精确到小时甚至分钟级别。需要留意的是expire_logs_days在8.0里已经标记为废弃升级到8.3之后更是直接移除了所以新环境请优先配置binlog_expire_logs_seconds。配置方式有两种。临时生效可以执行SET GLOBAL binlog_expire_logs_seconds 604800;这会把保留时间设为7天对后续新生成的binlog立即生效但不会立刻去删除已有的旧文件。要让配置长期生效需要在my.cnf的[mysqld]段里写死[mysqld] binlog_expire_logs_seconds 604800注意自动过期只是“兜底策略”。它会在binlog切换或MySQL后台线程扫描时清理超出保留时间的文件但不会在你刚改完参数的下一秒就释放空间。磁盘告警这种事等它慢慢扫是不现实的还是得配合手动清理。2.2 手动清理PURGE BINARY LOGS的语法和边界手动清理是运维人员最常用的手段命令是PURGE BINARY LOGS。它的核心语法有两种PURGE BINARY LOGS TO mysql-bin.000010; PURGE BINARY LOGS BEFORE 2024-08-01 00:00:00;TO语法是我个人比较推荐的语义非常清晰保留指定文件以及它之后的所有文件删除它之前的所有文件。举个例子假设当前有mysql-bin.000001到mysql-bin.000100执行PURGE BINARY LOGS TO mysql-bin.000088;那么000001到000087会被删掉000088及之后全部保留。很多新手会把这个方向搞反以为是“删到000088为止”那是错的理解这里一定要记住。BEFORE语法则是按时间清理删除时间早于指定时间点的文件。生产里我其实用得相对少因为时间边界的判断不如文件名那么直观而且还需要额外确认时间戳的准确性。如果要用建议先想清楚日期格式再结合业务低峰期执行。还有一个关键点PURGE不会删除当前正在写入的那个binlog文件。如果你指定的目标文件恰好是主库正写着的活跃文件MySQL会拒绝执行或者要求你先把日志切换走。所以在手动清理前通常需要先执行一次FLUSH BINARY LOGS;这个命令会让MySQL关闭当前正在写的binlog新建一个编号更大的文件继续写。这样你就能放心清理更早的历史文件因为不会再有人占用它们了。2.3 RESET MASTER这种“大杀器”什么情况下才允许用聊完PURGE必须单独说说RESET MASTER。这是一个极其危险的命令它会把所有binlog文件全部清空然后重新从mysql-bin.000001开始编号。这跟PURGE有本质区别PURGE是删除一部分历史文件RESET MASTER是彻底推倒重来。什么时候才允许用一般是构建一套全新复制环境、初始化数据目录或者在做测试环境的数据重置时。生产环境里只有当你确认所有从库都已经不再依赖当前主库的binlog、所有数据已经完成备份、复制拓扑已经拆除时才可以考虑。实际上我几乎没见过哪个正常运维流程需要手动执行RESET MASTER的它更像是一把“自毁钥匙”放那里是让特殊场景用的不是给日常磁盘清理用的。下面这张表可以直观对比几种清理方式的差别操作方式清理范围是否更新index索引对从库的影响适用场景rm直接删除文件指定文件不更新留下脏索引极易断复制任何时候都不建议PURGE BINARY LOGS TO指定文件之前的所有文件正常更新若边界判断错误会断复制常规手动清理PURGE BINARY LOGS BEFORE指定时间之前的文件正常更新若时间边界错误会断复制按时间批量清理RESET MASTER所有binlog并重新编号重建必然影响所有从库重建复制环境自动过期参数超出保留时间的文件正常更新风险低长期兜底策略3. 一次因rm删除binlog引发的复制断裂事故复盘3.1 事故现场磁盘告警后的十五分钟有一年我负责的某套业务系统遇到过非常典型的一次事故。凌晨两点多监控平台弹出一条告警主库所在磁盘使用率已经到95%。登录服务器一看/data/mysql目录下躺着几百个binlog文件粗略一算占了将近200GB的空间。说实话binlog堆积到这个程度本身就是平时没有清理策略造成的但当时大家都觉得“先把空间释放出来再说”。当时有位同事图快直接在数据目录下敲了rm -f mysql-bin.000020 mysql-bin.000021紧接着又删了几个更早的文件。磁盘空间确实立刻下来了一截告警暂时消除。但大概过了十分钟从库的告警也跟着响了复制中断Last_IO_Errno直接报了1236。大家一开始还没反应过来以为是网络抖动后来才发现是binlog物理文件已经被删掉了。3.2 排查链路从主库到从库错误在哪一层暴露出来这个事故的排查过程很能说明问题。先是在主库上执行SHOW BINARY LOGS;结果发现MySQL列出来的binlog列表里还包含那几个已经被rm删掉的文件名。因为直接删文件不会通知MySQL去更新.index索引所以列表和磁盘实际内容已经对不上了。再ls一下数据目录mysql-bin.000020和mysql-bin.000021确实不存在但索引里还记录着它们的路径。接着去从库执行SHOW SLAVE STATUS\G;看到的关键字段是这样的Master_Log_File: mysql-bin.000021 Read_Master_Log_Pos: 387451 Last_IO_Errno: 1236 Last_IO_Error: Got fatal error 1236 from master when reading data from binary logMaster_Log_File停在mysql-bin.000021表示从库的IO线程正需要去主库拉取这个文件可它已经被物理删除了。MySQL的复制是基于一段连续的binlog事件流的中间少了一个文件就没有任何办法跳过缺口继续复制。你把主库后面的新binlog全部给从库也没用缺口永远补不上。最后没办法只能从备份重新搭建从库。这个过程包括重新全量备份、恢复到新实例、重新配置复制关系前后折腾了好几个小时。业务那边虽然主库没有直接挂但所有读流量都压在从库上从库不可用的时间里整个读写链路都受到了影响。3.3 根因分析与事后补救为什么这条路永远不能走复盘这次事故根子其实有三个第一binlog生命周期完全没有治理平时没人管等磁盘满了才临时抱佛脚第二操作者不知道binlog和主从复制的依赖关系把rm当成了普通日志清理第三没有做binlog归档想恢复也没有可用的备份副本可回补。之后我们把清理流程彻底规范了。所有binlog统一由PURGE BINARY LOGS处理禁止任何人直接操作数据目录下的文件。同时加了监控项binlog目录使用率超过70%就提前告警而不是等到95%才被动处理。更重要的是我们建立了一套每日归档机制先把要清理的binlog复制到备份存储确认没问题之后再执行清理。这样即使万一误删也能用归档文件补回来。这里要特别强调一句不管磁盘空间多紧张rm删除binlog文件都是绝对不能碰的红线。MySQL提供了专门的清理命令就是想让你在清理的同时保持完整的索引和复制安全边界绕过它去“提高效率”最后一定是在给事故埋雷。4. 高频删除场景下的binlog维护体系4.1 删除前必须确认的三件事如果你已经准备用PURGE BINARY LOGS清理binlog了动手之前一定要确认三件事任何一件不合格都先别删。第一所有从库的复制位点都已经越过了你要删除的边界。操作方法是在主库执行SHOW MASTER STATUS;记住当前的File和Position然后逐个从库执行SHOW SLAVE STATUS\G;重点看Master_Log_File。你的purge目标文件必须小于所有从库的Master_Log_File意思就是所有从库都已经读到这个文件之后了你才删得安全。如果某个从库还停在你准备删除的文件上一执行就断复制。第二你要删除的binlog范围已经做好了备份。哪怕觉得“这些文件太老了肯定没用了”也先归档再删。因为“没用”这个判断经常是错的等真正需要回查某条历史数据时才后悔就来不及了。第三当前是业务低峰期。一个大库可能有上百个binlog文件要删PURGE虽然比rm安全但它依然会逐个unlink文件并更新索引期间会有一定的IO波动。高峰期批量删除几十上百个文件容易把本来就繁忙的磁盘IO再往上推一截。4.2 目录与磁盘规划给binlog一个独立的家我在很多服务器上看到过binlog和数据文件放在同一个挂载点的配置这种做法隐患很大。磁盘空间全被binlog占满时连数据文件的新增写入都会受影响可能连带业务报错。我的建议是如果条件允许把binlog目录单独挂在一个独立分区或磁盘上和生产数据目录隔离开。容量规划上大致的估算公式是单日binlog生成量乘以保留天数再留20%左右的缓冲。比如某业务高峰期一天能写10GB的binlog打算保留7天那日志分区至少要准备10GB×7×1.2也就是84GB左右。预留得太紧遇到一次大促或者批量更新磁盘很快又满了预留得太宽则是浪费资源。先把单日生成量这个数据摸清楚再定分区大小才是合理的做法。另外还可以关注一下max_binlog_size参数它控制单个binlog文件的最大大小默认是1GB。这个值会影响文件数量和清理粒度。文件切得太大binlog切换不频繁但单文件备份、传输都不方便切得太小文件数量多清理和索引维护也会频繁一些。我的习惯是保持1GB默认值这样一千个文件大约就是1TB日志量心里换算比较方便。4.3 备份、归档与purge的联动节奏清理binlog最稳的节奏我总结成了一个固定流程先切换再归档校验通过后purge最后核对结果。具体可以这样操作。先执行FLUSH BINARY LOGS;保证当前活跃的binlog被切到新文件所有待清理文件都已经是“历史”状态。然后拿着SHOW BINARY LOGS;的输出把要清理的文件复制到备份机或者其他存储上可以顺手用gzip压缩binlog的文本压缩率通常还不错。这一步做完之后重点校验归档文件大小和原始文件是否一致避免拷了一半就当作备份成功。确认没问题了再执行PURGE BINARY LOGS TO mysql-bin.0000xx;最后再查一次SHOW BINARY LOGS;和磁盘空间确保列表里没有残留的失效记录、空间确实释放了。整个过程可以用一段简单的shell脚本串起来大致示意如下# 切换到新binlog mysql -e FLUSH BINARY LOGS; # 列出当前所有binlog人工或脚本确认清理边界 mysql -e SHOW BINARY LOGS; # 归档待清理文件示例中用rsync拷贝到备份主机 rsync -av /data/binlog/mysql-bin.0000xx backup-host:/backup/binlog/ # 校验归档成功后执行purge mysql -e PURGE BINARY LOGS TO mysql-bin.0000yy;这里要特别强调顺序一定是“先归档后清理”。如果归档那一步失败宁可让磁盘再紧张一会儿也不要强行删除本地文件。空间没了可以再扩数据文件没了那就真的没了。4.4 顺带说说relay log的清理别再搞混很多人在清理binlog时会把从库的relay log也一并“处理”掉结果处理出问题。relay log是从库自己生成的记录的是从主库拉取下来的binlog内容它的作用是从库本地回放数据跟主库binlog不是一回事。relay log的清理有专门的机制默认参数relay_log_purge为1也就是从库正常情况下会自动清理已经执行完毕的relay log。如果发现relay log堆积一般不是“清理手段不对”而是复制本身出了问题比如IO线程频繁重连、SQL线程卡住等。正确的做法是先检查SHOW SLAVE STATUS\G;里的IO/SQL线程状态把根因解决掉堆积的relay log自然会按照机制清掉。不要在一台复制状态异常的从库上手动删除relay log那样很容易把本就不稳定的复制关系进一步搞坏。分清binlog和relay log是做好日志清理的第一步。5. 被误删后的应急恢复思路与常见认知误区5.1 误删之后还能不能救回来万一真的发生了误删先别慌按照“备份是否还在”来分情况处理。如果之前做过binlog归档那就相对好办把归档文件拷回binlog目录确保文件名编号和.index索引一致再把从库的复制关系重新接上。不过这种事情有个前提就是归档时间点不能晚于你误删之前。如果你归档只做到昨天今天误删的文件根本没有副本那归档也帮不上忙。如果没有备份归档情况就比较麻烦了。主库可能还能正常运行但从库的复制链基本宣告断裂想要继续同步唯一靠谱的办法就是用全量备份重建从库。主库自身在重启时也可能遇到麻烦因为.index里记录的某些binlog文件在磁盘上已经不存在MySQL在恢复或切换binlog时可能报错。这种情况下数据恢复的成功率高度依赖现场条件我不建议任何人临时上网搜“恢复命令”就上手试先评估清楚再行动往往比冒险操作更有效。这件事给我们的教训还是那句老话备份是唯一的后悔药。binlog的归档备份应该纳入日常备份体系而不是想起来才做。5.2 关于binlog清理的几个常见误解我在跟不少同行交流时发现有些误解真的非常普遍这里集中澄清一下。第一个误解是“binlog被删掉不影响在线查询”。确实SELECT语句不会读binlog业务查询短期看不出异常。但一旦涉及备份恢复、主从切换、数据回放缺失的binlog就是无法弥补的裂缝。等出了问题再回头看损失的是整个恢复能力。第二个误解是“设置了过期参数文件到时间就会立刻被删掉”。自动过期机制的清理是异步的而且通常要等到binlog切换或后台线程扫描才会真正动手。磁盘被写满的那一刻它不一定来得及清所以不能只靠参数还是要有人工巡检和purge预案。第三个误解是“PURGE执行完磁盘马上就空了”。删除大量文件时MySQL需要逐个处理索引和文件系统这个过程中磁盘可用空间是慢慢回升的。如果发现执行后空间没立刻降下来不要反复执行命令先等一等再看。第四个误解是“从库已经同步到最新旧binlog随便删”。这个前面讲过了从库可能因为断线重连而需要回退到更早的位置多级复制架构下下游节点的位点更难判断。稳妥做法永远是保留足够的冗余让删边界远离所有从库的消费位点。5.3 我每次清理binlog前固定会做的动作最后分享一套我自己的固定动作也是踩过几次坑之后养成的习惯。清理binlog前我会按顺序执行下面这几步。第一步查主库当前状态记录SHOW MASTER STATUS;和SHOW BINARY LOGS;的输出明确现在写到哪个文件了哪些文件是历史文件。第二步逐个从库执行SHOW SLAVE STATUS\G;把所有从库的Master_Log_File记下来取最小值作为安全边界的参考purge的目标文件名必须比这个安全边界更早、更小。第三步把待清理范围内的文件归档到备份机并做一次文件大小校验。第四步确认归档成功后执行FLUSH BINARY LOGS;并PURGE BINARY LOGS TO 目标文件;。第五步清理完成后立刻再查一次SHOW BINARY LOGS;和磁盘使用率确认列表与实际文件匹配没有残留的脏索引。这套流程看似啰嗦实际执行下来也就几分钟。但就是这几分钟能让你避免一次从库断裂、一次长时间数据恢复、甚至一整天的业务告警。我自己现在的原则是任何操作只要和binlog有关一律按照“先查、再备份、后清理、最后核验”的顺序走绝不因为图快而走捷径。数据库里的每一条数据都可能是别人业务的命根子管理这些日志的时候谨慎从来不嫌多。