ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MySQL binlog 清理实战:从磁盘告警到安全释放空间

2026/9/11 3:16:11 拓冰建站 浏览量
MySQL binlog 清理实战:从磁盘告警到安全释放空间 凌晨两点四十监控平台弹出一条告警/data/mysql使用率 98%。我登上去看了一眼数据目录里齐刷刷躺着一排mysql-bin.000xxx文件光 binlog 就占了将近 190GB。按当时的业务写入量估算这个量级的 binlog 大约能撑两周但磁盘只剩 2% 可用别说继续写数据连 slow log 和 error log 都可能随时写不进去。那会儿脑子里冒出的第一个念头是“把 binlog 直接删了不就完事”但干这行久了就明白删除 binlog 从来不是一条rm命令那么简单。后面我会把这件事拆开讲清楚什么时候能删、用命令怎么删、删完空间为什么没释放、以及手贱直接删物理文件之后要怎么补救。1. binlog 不是普通日志先搞懂它记录了什么再谈删除很多刚接触 MySQL 的人容易把 binlog 和 error log、slow log 混为一谈觉得“日志嘛删掉就行了”。实际上 binlog二进制日志的核心职责有两条哪一条断了都会出大事。1.1 一次磁盘告警引出的问题我接手过的 MySQL 实例里因为 binlog 撑爆磁盘的案例有不少。常见情况是开发同学开启log_bin之后就没再管过或者某个大事务一次性更新了上千万行数据binlog 瞬间膨胀出几十 GB。等到磁盘告警响起来数据库已经处于“写不进去、读也变慢”的边缘状态这时候再去想怎么删压力会大很多。所以这篇文章的第一个结论就是binlog 的清理策略必须在部署阶段就定好而不是等磁盘告警再去救火。1.2 binlog 的双重职责崩溃恢复与主从复制先花两分钟搞清楚 binlog 为什么重要。它记录的是 MySQL 中所有数据变更操作包括 INSERT、UPDATE、DELETE、DDL 等但不记录 SELECT 查询。它主要有两个用途崩溃恢复时间点恢复当数据库数据文件损坏或者你想把数据恢复到某个特定时间点binlog 记录了自全量备份以来的每一次变更。配合全量备份可以用mysqlbinlog工具把增量部分重新执行一遍做到接近零丢失的恢复。主从复制主库生成 binlog从库拉取 binlog 并在本地重放从而保持主从数据一致。如果从库还没拉到某个 binlog 文件主库就把这个文件删了从库会直接复制中断且报错信息往往比较难懂。对于没有任何复制关系、也不做时间点恢复的纯单机测试库binlog 的保留价值确实没那么高删除的胆子可以大一些。但生产库基本都有备份策略和从库复制动手之前必须先评估影响面。提示查看当前是否开启了 binlog执行SHOW VARIABLES LIKE log_bin;。如果你至今没见过 binlog大概率是没开或者用的是云厂商默认托管实例。1.3 什么情况让 binlog 快速膨胀除了正常的业务写入下面几类场景最容易让 binlog 增长失控大事务比如一条UPDATE语句一次性修改一百万行如果 binlog_format 是ROW那么这百万行的变更前后值都会写进 binlog文件体积可能瞬间增加几百 MB 到几 GB。MySQL 8.0 默认binlog_formatROW安全是安全但体积确实比早期的STATEMENT格式大不少。主从延迟期间主库持续写入从库跟不上主库主库的 binlog 文件只能一直保留越长越占空间。误把 binlog 当成归档有人觉得 binlog 是“历史数据”留着以后能当审计用结果每小时的写入量又大几个月下来积攒了几百个文件。每次手动 FLUSH LOGS 产生新文件频繁切换 binlog 会产生大量小文件同样会占用不小空间。我在实践中判断 binlog 是否需要清理核心就看两点从库是否还需要它以及时间点恢复策略是否需要它。两者都不需要那就可以放心清理。2. 动手删除前先把这三件事查清楚删除 binlog 之前我建议你先花五分钟执行几条查询命令把现状摸清楚。这一步省不掉否则很容易把复制链路弄断。2.1 看现状磁盘占用与 binlog 文件清单先看文件列表和占用# 查看 binlog 文件列表及大小binlog 目录通常在 datadir 下 ls -lh /data/mysql/mysql-bin.*-- 在 MySQL 内查看 binlog 文件清单 SHOW BINARY LOGS;这两条命令能让你立刻知道现在有多少个 binlog 文件、最早的文件是什么时候的、整体占用多少空间。你还可以用SHOW VARIABLES LIKE max_binlog_size;查看单个 binlog 文件的最大尺寸默认通常为 1GB也就是说如果出现 100 个文件那基本就是 100GB 起步。2.2 确认复制链路不能删到从库还没读的位置如果这个实例有从库那么这一步是重中之重。在从库上执行-- MySQL 8.0.22 之前的版本 SHOW SLAVE STATUS\G -- MySQL 8.0.22 及之后的版本 SHOW REPLICA STATUS\G关注两个字段Master_Log_File/Source_Log_File从库 IO 线程已经读到了哪个主库 binlog 文件。Relay_Master_Log_File/Exec_Master_Log_Pos从库 SQL 线程已经重放到了哪个文件、哪个位置。安全清理的判断标准是计划删除的 binlog 文件名必须早于从库已经读取到的文件名。换句话说从库不需要再读取的文件删除风险才低。如果从库还在读mysql-bin.000010而你删到了mysql-bin.000009问题不大但如果连000010一起删了复制就会中断。注意从库的 IO 线程和 SQL 线程可能处于不同位置以“更靠后的线程位置”为参考。有多个从库时要以位置最落后的那个从库为准不能只看最快的一个。2.3 确认当前正在写入的文件无论有多少历史 binlog当前正在写入的 binlog 文件绝对不能删。查看方法SHOW MASTER STATUS;返回结果里的File字段就是主库当前正在写入的 binlog。另外在删除之前最好先记录一下这个文件名后面验证的时候要用到。你还可以顺手看一下从库复制是否健康Slave_IO_Running和Slave_SQL_Running是否都是Yes。如果本来就是断开的删除 binlog 更要谨慎因为不知道从库落后了多少。3. 长期解法让 MySQL 自动过期清理而不是手动删手动清理是应急手段长期来看必须靠参数让 MySQL 自己管理 binlog 的保留时间。这样不会出现“某一天忘了清理磁盘又爆了”的情况。3.1 自动过期机制与参数演变MySQL 提供了两个控制 binlog 保留时间的参数expire_logs_days按“天”设置是 MySQL 5.7 及之前版本的主流参数。binlog_expire_logs_seconds按“秒”设置MySQL 8.0 开始引入精度更高可以精确到小时甚至分钟。设置后expire_logs_days会进入废弃状态。查看当前生效值SHOW VARIABLES LIKE expire_logs_days; SHOW VARIABLES LIKE binlog_expire_logs_seconds;在 MySQL 8.0 中如果同时把两个参数设置成非零值系统会直接报错要求你把其中一个设回0。这也是很多人在配置文件中同时写了两行导致 MySQL 启动失败的原因。3.2 修改配置的正确姿势假设希望 binlog 保留 7 天MySQL 8.0 的做法是[mysqld] binlog_expire_logs_seconds 604800对于 MySQL 5.7[mysqld] expire_logs_days 7修改配置文件后重启 MySQL 生效。如果不想重启可以动态修改全局变量SET GLOBAL binlog_expire_logs_seconds 604800;这条命令在当前实例上立即生效但不会持久化到配置文件。也就是说下次重启后参数还会回到配置文件里的值。所以正确做法是先动态改再把配置写进 my.cnf两者缺一不可。3.3 设置后不立即生效怎么办有一个很常见的疑问我明明设置了binlog_expire_logs_seconds 604800但过了一会儿去看SHOW BINARY LOGS;老文件还在空间也没减少是不是设置没用这不是设置没用而是自动清理不一定立刻触发。MySQL 对过期 binlog 的清理动作通常发生在binlog 文件轮换时、实例启动时、或执行FLUSH LOGS时。如果当前 binlog 还没写满 1GB一直没轮换那过期检查可能迟迟不会执行。可以手动触发一次FLUSH LOGS;这个命令会关闭当前 binlog新建一个文件继续写入同时触发一次过期清理。执行完再SHOW BINARY LOGS;通常就能看到过期文件已经被自动删掉了。提示频繁执行FLUSH LOGS会产生很多 1GB 以下的小文件反而增加管理成本。建议在需要验证参数时执行一次即可不要做成定时任务。4. 应急清理PURGE 和 RESET MASTER 怎么选如果磁盘空间已经告急没时间等自动清理那就需要手动下令。手动清理有两条命令适用场景完全不同。4.1 PURGE BINARY LOGS TO / BEFOREPURGE是删除 binlog 最常用的命令它只会删除指定范围之前的文件保留当前文件相对安全。按文件名删除删除mysql-bin.000010之前的所有文件不包括000010本身PURGE BINARY LOGS TO mysql-bin.000010;按时间删除删除 2025-01-01 00:00:00 之前的所有 binlogPURGE BINARY LOGS BEFORE 2025-01-01 00:00:00;也可以结合NOW()做相对时间PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;这条命令的底层逻辑是MySQL 会扫描 binlog index 文件删除文件中记录的所有早于指定位置的 binlog 物理文件同时更新 index 文件。这样 MySQL 内部记录和磁盘文件是一致的不会出现“文件没了但索引还记着”的错乱状态。删除后可以立即验证SHOW BINARY LOGS;确认目标文件确实消失且剩余文件的最小编号符合预期。4.2 RESET MASTER 的适用边界和风险RESET MASTER的杀伤力比PURGE大得多——它会清空所有 binlog 文件并把序号重新从000001开始编号。RESET MASTER;什么场景下能用一般只建议在以下情况使用刚搭建的全新实例没有任何从库依赖不需要保留历史 binlog。测试环境binlog 已经没有价值准备彻底重置。需要彻底清理 binlog 以释放磁盘且能接受时间点恢复失效。但生产环境要非常小心尤其是开启了 GTID 的实例。执行RESET MASTER会同时清空gtid_executed信息如果从库依赖主库的 GTID 集合做复制很可能导致从库无法对齐。我在一个项目里见过有人为了清理 binlog 在主库执行了RESET MASTER结果三个从库全部复制中断最后只能通过重新备份恢复。所以我的建议是能不用就不要用生产环境首选 PURGE。如果实在要用先确认没有活跃从库并提前备份mysql-bin.index内容和 SHOW MASTER STATUS 的输出作为回退依据。4.3 三种方式对比方式命令/配置适用场景风险等级自动过期binlog_expire_logs_seconds/expire_logs_days日常运维长期生效低手动精准删除PURGE BINARY LOGS TO/BEFORE磁盘告警需要保留部分 binlog中低全量清空RESET MASTER全新实例、测试环境、彻底重置高实际运维中我大多数情况下只用两种组合平时靠自动过期磁盘告警时先PURGE应急再回头补查为什么自动过期没生效。5. 最不建议的“省事”操作直接 rm 掉 binlog 会怎样我必须专门用一章聊这个问题因为真的有人这么干过而且我也这么干过一次代价是支付了几个小时的排查成本和一顿检讨。5.1 踩坑过程复盘有一次某台测试机的/data/mysql磁盘爆满当时为了快速释放空间我直接执行了rm -f /data/mysql/mysql-bin.0000*文件确实没了磁盘空间也释放了。但没过多久业务侧反馈写入报错。我登上去执行SHOW BINARY LOGS;结果直接报错提示 binlog 索引文件里的某些文件找不到了。原因很简单MySQL 通过一个mysql-bin.index文件记录所有 binlog 文件清单。用rm删掉物理文件后index 文件里还保留着这些文件名。MySQL 内部逻辑是“先查 index 再读文件”当它发现索引指向的文件不存在就会认为 binlog 文件损坏影响后续的 binlog 写入和复制。5.2 用 rm 删完可能出现的连锁问题直接删物理文件的后果不止是条目错乱还有更麻烦的情况当前正在写入的 binlog 被删如果你好巧不巧把SHOW MASTER STATUS里正在写的那一个也删了MySQL 会一直尝试写入一个“已消失文件”的句柄磁盘空间反而不会释放直到你把 MySQL 重启。index 文件与磁盘不一致后续执行FLUSH LOGS、自动轮换、甚至重启都可能报错严重时 MySQL 直接启动失败。从库彻底断链从库按 index 顺序请求下一个 binlog结果主库这边文件信息错乱从库收到错误信息直接中断复制报 1236 错误。所以结论很明确binlog 的删除必须通过 MySQL 自己来因为它需要同时维护物理文件和 index 索引的一致性。rm只是删了文件没有同步更新索引自然会出现各种奇怪的问题。5.3 补救方法如果你已经手滑了可以尝试按顺序做以下步骤停止 MySQL 服务如果不影响业务尽量停干净。打开mysql-bin.index文件把其中已经不存在的文件名行删除。这个文件每行记录一个 binlog 文件名内容大概长这样/data/mysql/mysql-bin.000001 /data/mysql/mysql-bin.000002 /data/mysql/mysql-bin.000003把实际存在的 binlog 文件按顺序重命名补齐编号确保 index 里的名称和磁盘上文件一一对应。重新启动 MySQL执行SHOW BINARY LOGS;验证。这个过程说起来简单实际操作非常繁琐而且容易出错。如果 binlog 数量多、还有从库依赖建议直接评估用备份重建实例可能比重试 index 文件更快。6. 主从复制环境下的删除纪律上面提到的命令在单机环境里跑没问题但大部分生产 MySQL 都是主从架构。删除 binlog 的时候必须站在整个复制链路的角度想问题。6.1 从库为什么依赖主库 binlog主从复制的原理是从库通过 IO 线程连接主库按顺序拉取主库 binlog 文件写入本地 relay log再由 SQL 线程重放。如果主库某个 binlog 文件被提前删除从库还没拉到它相当于中间缺了一整段数据变更记录复制必然中断。最典型的一个场景是某从库因为网络隔离或磁盘故障和主库断开了两天。主库这边自动过期参数设置的是 24 小时两天后重连时从库发现需要的 binlog 已经被主库清理掉了。6.2 安全删除的判断标准在从库执行SHOW REPLICA STATUS\G看两个关键位置IO 线程已经读到哪里Master_Log_File或Source_Log_FileSQL 线程已经重放到哪里Relay_Master_Log_File或Exec_Master_Log_Pos安全清理规则只删除这个位置之前的所有 binlog 文件。比如从库 SQL 线程已经执行完mysql-bin.000050那主库删除到000050之前都相对安全。如果从库落后比较多就先别急着清。另外GTID 模式下还有个更稳妥的参考点。在从库执行SHOW VARIABLES LIKE gtid_executed;看到gtid_executed集合后可以在主库用SHOW MASTER STATUS确认哪些 binlog 文件对应的 GTID 范围已经被从库消费。不过这套判断对新手来说有点绕最直接还是看Relay_Master_Log_File字段。6.3 误删之后的重建思路假设你已经误删了从库需要的 binlog从库报错类似Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: Could not find first log file name in binary log index这说明当前主库已经没有从库需要的 binlog 文件了。最快捷的恢复方案是重新指定从库的同步位点。STOP REPLICA; CHANGE MASTER TO MASTER_LOG_FILEmysql-bin.000060, MASTER_LOG_POS4; START REPLICA;但这里有个问题你要知道mysql-bin.000060的MASTER_LOG_POS应该填多少。如果没有记录或者主库这个文件里的 binlog 也不是从库缺失的那一段数据还是会对不上。所以更稳妥的恢复方式是直接重建从库基于主库最新的全量备份加上对应的 binlog position 或 GTID 信息重新搭建一个从库。这个流程虽然耗时但至少能保证数据一致性。我个人经历过一次误删之后养成了一个纪律清理 binlog 之前先确认所有从库的复制落后时间不超过一个文件的保留窗口如果某个从库长期离线先处理它再清理。这句话值得每一位 MySQL 运维/开发记下来。7. 一次磁盘告警的完整处理链路从定位到验证最后分享一个典型的处理过程把前面所有知识点串起来。假设你现在遇到的就是文章开头说的场景磁盘 98%binlog 占到 190GB。7.1 第一步先看一眼全局df -h du -sh /data/mysql/mysql-bin.*SHOW BINARY LOGS; SHOW MASTER STATUS;这一步的目标是回答三个问题文件有多少、当前写到哪个文件、有没有从库依赖。先别急着删信息不全就动手是事故的开端。7.2 第二步确认从库位置如果有从库去从库执行SHOW REPLICA STATUS\G看到 IO 线程和 SQL 线程都停在mysql-bin.000060或更靠后的位置那么主库删到000059或保留更保险的文件就相对安全。如果没有从库这个检查跳过。7.3 第三步先止血再长期治理磁盘已经 98%容不得慢慢等自动清理。先执行快速释放PURGE BINARY LOGS TO mysql-bin.000060;注意这里的000060是“要保留到的最早文件”。如果需要把所有 binlog 都清光建议用RESET MASTER但需谨慎常规做法是至少保留当前正在写的文件PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY;这样把一天前的 binlog 全部清掉当前文件不受影响。执行完后观察磁盘df -h /data/mysql空间释放后再回过头去修改配置设置合理的自动过期时间SET GLOBAL binlog_expire_logs_seconds 604800;并把binlog_expire_logs_seconds 604800写进my.cnf的[mysqld]段。7.4 第四步验证不反弹改完参数后别直接走人。等几分钟后再次执行SHOW BINARY LOGS;确认文件数量没有异常增长。如果后续发现自动过期没有按预期触发可以手动执行一次FLUSH LOGS;触发检查。最后再把磁盘监控阈值从 80% 调整为“binlog 目录单独监控”防止下次再被突增的 binlog 打爆。我现在处理这类问题已经有固定套路先判断有没有从库再确认当前文件然后PURGE止血最后把自动过期参数补上并验证。这套流程虽然看起来步骤多但每一步都在避免“删完才发现删错了”的尴尬。如果你在操作时能沉住气把这四步走完binlog 清理这件事基本就稳了。