ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从磁盘告警到安全清理:MySQL binlog 保留策略全解

从磁盘告警到安全清理:MySQL binlog 保留策略全解 1. 从一次磁盘告警说起做 MySQL 久了几乎都会遇到一个经典场景晚上收到磁盘空间告警登上服务器一看/data/mysql目录占了好几百 GB其中binlog文件占了八成以上。有人直接删文件有人顺手改了expire_logs_days结果主从延迟瞬间飙红甚至从库直接断掉不知道从哪个点继续追赶主库。这时候才意识到binlog 的保留策略不是配置个参数就完事这么简单。先说清楚一件事binlog 是 MySQL 用来记录所有数据变更的二进制日志主从复制、数据恢复、审计排查几乎都离不开它。它不像错误日志那样攒着就行也不像慢查询日志那样可以随手轮转它有自己的一套写入、轮转、清理机制。如果对保留策略理解不到位要么磁盘被撑爆要么关键时刻发现日志已经被清没了两个方向都是事故。这篇文章我打算把 binlog 保留策略从原理到落地完整过一遍binlog 到底是怎么生成的、为什么不能随便手删、expire 参数和PURGE BINARY LOGS命令怎么用、主从环境下清理要注意什么、Docker 部署时有哪些坑以及我自己在线上踩过的一些问题。适合刚接手 MySQL 运维的开发、DBA也适合那些已经在用 MySQL 但还没认真梳理过日志保留策略的人。2. 为什么需要一套合理的 binlog 保留策略2.1 binlog 承载的三个核心价值很多人对 binlog 的理解停留在主从复制要开log-bin但保留策略怎么做取决于你打算让这些日志发挥什么作用。第一是主从复制。从库通过 IO 线程拉取主库的 binlog再通过 SQL 线程重放整个过程相当于把主库的历史操作在从库上重演一遍。如果主库把还没来得及被从库拉走的 binlog 清掉了从库就会报1236错误只能重建或重新定位。第二是数据恢复。mysqlbinlog可以解析 binlog 并分段回放误删数据、误更新整表时配合全量备份做全量增量恢复是最常用的手段。第三是审计和问题排查。谁在什么时间执行过什么语句binlog 里都有记录虽然它不是专门的审计日志但关键时刻非常有用。所以保留策略的核心不是在删不删之间选而是在保留多久、保留多少、清到什么位置之间做平衡。删早了丢恢复能力删晚了占磁盘清理到错误的位点还会打断同步链路。2.2 没有策略时最容易踩的三种情况第一种情况是参数没配MySQL 5.7 的默认expire_logs_days是 0表示永不过期。很多 MySQL 5.7 实例跑了两三年binlog 越堆越多直到磁盘满才被发现。第二种情况是只改了expire_logs_days没有关注主从延迟和从库拉取进度。如果从库因为网络或大事务延迟了很久主库按时间自动清掉旧 binlog延迟恢复后照样断链。第三种情况是运维同学图省事直接rm -rf mysql-bin.0000xx把物理文件删了但 binlog 索引文件mysql-bin.index还指向这些文件后续写新日志和主从拉取都会出问题。我见过不少团队把保留策略简单等价于定期删文件实际上真正的保留策略至少要考虑四点保留多久、按什么单位清理、用什么命令清理、清理前如何确认从库安全。下面每一部分我都会展开说。3. binlog 工作机制和保留策略的核心参数3.1 binlog 文件是怎么生成和轮转的binlog 的默认文件名是mysql-bin.000001、mysql-bin.000002这样递增的序列写入位置记录在mysql-bin.index索引文件里。每次 MySQL 启动、执行FLUSH LOGS、或者单个 binlog 文件达到max_binlog_size默认 1GB就会生成一个新的 binlog 文件新的事件继续往新文件里写。注意max_binlog_size只是一个参考值遇到超大事务时单个文件会超过这个大小因为它不会把一个事务拆到两个 binlog 文件里。这个机制决定了 binlog 清理的基本单位是文件而不是事件。你不能只删除某个 binlog 文件里的前一半内容只能整个文件清理。所以在评估保留策略时先算清楚我每天会产生多少个 binlog 文件比直接拍脑袋定保留 7 天要靠谱得多。算一个实际例子假设业务峰值时每天产生 30GB binlog按 1GB 一个文件算就是 30 个文件。保留 3 天就是 90GB保留 7 天就是 210GB。如果磁盘只有 500GB还要留给数据文件、系统文件、临时文件7 天方案大概率不够用要么扩容要么压缩归档要么调整保留天数。3.2 expire_logs_days 和 binlog_expire_logs_seconds 怎么选MySQL 5.7 及更早版本使用expire_logs_days单位是天默认 0 表示不过期。MySQL 8.0 引入binlog_expire_logs_seconds单位是秒而且 8.0 的官方文档明确建议把保留时间配置精确到秒级别比如 3 天可以写成604800。这里有个历史沿革的坑如果你在 MySQL 8.0 里同时设置了expire_logs_days和binlog_expire_logs_secondsMySQL 8.0 早期版本会优先使用binlog_expire_logs_seconds并且会提示expire_logs_days已废弃。到了 MySQL 8.4expire_logs_days干脆被移除了。如果你的环境还是 MySQL 5.7就先老老实实用expire_logs_days如果用 8.0 及以上直接配置binlog_expire_logs_seconds最省心。这个参数生效的时机也需要说明一下binlog 的过期检查不是实时删除MySQL 在写新的 binlog 文件、以及执行PURGE LOGS相关操作时才会触发清理逻辑。也就是说即使你改了保留参数旧的 binlog 也不会立刻消失要等触发器运行。如果急需释放空间必须手动执行清理命令。3.3 三个相关参数sync_binlog、max_binlog_size、binlog_format很多人只盯着保留参数忽略了 binlog 生成速率的影响因素。实际上binlog_formatROW、STATEMENT、MIXED、sync_binlog、max_binlog_size这几个参数都会影响 binlog 文件数量和大小。binlog_format如果是 ROW记录的是每行变更前后的完整镜像适合数据一致性要求高的场景但日志量会比 STATEMENT 大很多。一个 UPDATE 语句更新 100 万行STATEMENT 可能只记录一条 SQLROW 会记录 100 万行变更。所以同样业务负载下ROW 格式的 binlog 一天可能生成 50GBSTATEMENT 可能只有 10GB。sync_binlog1表示每次事务提交都强制刷盘安全性高但 fsync 开销大对日志生成速度也有影响。max_binlog_size会影响文件切分的频率设小了文件碎片多设大了单文件回放耗时较长主从复制时拉取单个大文件占用带宽也更久。配置保留策略之前我建议先把这些参数梳理一遍。保留策略解决的是日志存多久而这些参数决定的是每天都产生多少日志和日志安全到什么程度。前者是容量规划后者是性能和安全性取舍。4. 实操一把binlog 保留策略的完整落地过程4.1 第一步先摸清当前 binlog 现状动手配置之前先看两样东西当前 binlog 文件列表和磁盘占用。SHOW BINARY LOGS;这个命令会列出所有 binlog 文件以及每个文件的大小。看到文件数量特别多的时候心里先有个数。接下来看当前正在写哪个文件SHOW MASTER STATUS;输出里的File就是当前正在使用的 binlog 文件Position是写入位置。查询结果里还有一个Executed_Gtid_Set开启 GTID 的实例会有内容清理时要特别小心后面我会细说。磁盘占用方面可以直接按目录统计du -sh /data/mysql/mysql-bin.*还可以在 MySQL 里看参数配置SHOW VARIABLES LIKE expire_logs_days; SHOW VARIABLES LIKE binlog_expire_logs_seconds; SHOW VARIABLES LIKE max_binlog_size; SHOW VARIABLES LIKE binlog_format; SHOW VARIABLES LIKE sync_binlog;把这些信息记录到表格里能很直观地看出现在有没有保留策略、策略是什么、实际每天新增多少日志。这就是制定保留天数的依据。4.2 第二步确定保留天数并修改配置保留天数没有标准答案但我的判断原则是恢复时间窗口 主从延迟余量 全量备份频率。假如你的全量备份是每天凌晨 2 点做一次那么至少要把 binlog 保留时间覆盖到下一天凌晨 2 点否则全量增量恢复会断档。如果业务对恢复时效要求是最多丢 15 分钟那么保留几天的 binlog 都能满足需要考虑的是磁盘成本和恢复演练时间。如果主从链路偶发延迟我会在保留天数上再加 1 到 2 天的余量防止从库临时追不上导致断链。修改配置有两种方式临时生效使用SET GLOBALSET GLOBAL binlog_expire_logs_seconds 604800;永久生效要写进配置文件MySQL 8.0 在[mysqld]段下添加binlog_expire_logs_seconds 604800 max_binlog_size 1GMySQL 5.7 则写成expire_logs_days 7改完配置后重启 MySQL 不重启都行SET GLOBAL是动态参数但永久配置还是建议写进my.cnf。这里提醒一句SET GLOBAL设置的参数在重启后会丢失只适合临时应急验证生产环境一定要同步修改配置文件不然下次重启又回到默认值。4.3 第三步手动清理 binlog 的正确姿势改完保留策略后旧日志不会立即清除需要手动清理或在写入新日志时由 MySQL 自动触发。手删或rm文件是绝对禁止的正确做法是用PURGE BINARY LOGS。按文件名清理PURGE BINARY LOGS TO mysql-bin.000120;意思是清理掉mysql-bin.000120之前的所有 binlog保留000120及之后的内容。按时间清理PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;清理 3 天之前的 binlog。注意BEFORE是按文件中的事件时间判断的不是按文件创建时间部分文件可能跨过时间边界MySQL 会把整个文件一起处理。如果启用了 GTID最好用PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY这种时间方式或者确保清理位点不与从库的已执行 GTID 集合冲突。更稳妥的姿势是先看从库状态再决定清到哪个位点我放到第 5 部分详细说。4.4 第四步空库场景验证清理效果清理之后立刻验证空间有没有释放看文件列表是否干净SHOW BINARY LOGS;再看当前 binlog 索引文件是否正确cat /data/mysql/mysql-bin.index如果索引文件里还有不存在的文件指向说明清理方式不对或索引文件与磁盘没同步。正常执行PURGE后索引文件会同步更新。还可以用SHOW MASTER STATUS确认当前写入文件没有受影响不需要重启 MySQL。我习惯在清理前后各执行一次SHOW BINARY LOGS并把文件数量和总大小记录下来方便后续对比。磁盘释放量可以直接看目录大小变化du -sh /data/mysql/5. 主从环境下的保留策略不要让从库断粮5.1 从库拉取进度如何影响清理位点单机环境下binlog 清理相对简单只看恢复需求和磁盘空间。但主从架构下清理策略必须考虑从库已经拉取到哪个位点否则主库把从库还没消费的 binlog 清掉就会导致从库报错并停止同步。从库的SHOW SLAVE STATUSMySQL 8.0 叫SHOW REPLICA STATUS里有两个关键字段Master_Log_File表示 IO 线程正在读取的主库 binlog 文件Read_Master_Log_Pos表示读取到的位置Relay_Master_Log_File和Exec_Master_Log_Pos表示 SQL 线程正在执行的位点。大多数情况下IO 线程读取的位点会领先于执行位点真正需要关注的是Exec_Master_Log_File因为这是从库已经重放完成的位置。安全清理原则是不要删除任何从库仍然需要的 binlog 文件。对于单主多从架构取所有从库中最慢的那个Exec_Master_Log_File作为底线。如果 GTID 开启则检查从库的gtid_executed确保清理后不会丢失尚未同步的 GTID 事务。5.2 清理命令的实际加护一个实践中的做法是在从库延迟可接受的范围内用时间方式清理但保留足够的余量。比如从库偶尔延迟半小时你保留 3 天 binlog理论上从库不大可能延迟超过 3 天所以按时间清理基本安全。假如出现极端情况从库延迟了 4 天而主库保留策略是 3 天主库一旦触发自动清理从库必然会断。我建议监控项里加上主从延迟延迟超过阈值的实例要自动告警并在延迟恢复正常前暂时关闭自动清理或延长保留时间。还有一点容易被忽略max_binlog_size太大时一次大事务会生成超大 binlog 文件从库拉取这个文件如果中断重试需要重复消耗带宽。所以主库 binlog 文件设置不宜过大线上我一般不会超过 1GB。5.3 DELETE 与 reset master 的区别清理 binlog 时PURGE BINARY LOGS和RESET MASTER是两个完全不同的操作但新手经常混用。RESET MASTER会删除所有 binlog 文件并重置索引一般只在初始化新实例或彻底重建复制环境时使用。千万不能在从库正在运行、或主库还有从库依赖时执行否则所有从库都会立即断链。而PURGE BINARY LOGS只是删除指定范围之前的文件当前写入文件和后续新文件不受影响是日常保留策略的首选。如果你的实例开启了 GTIDRESET MASTER还会清空 GTID 执行记录影响面更大。常规的 binlog 保留策略完全不需要用到RESET MASTER看到有人用这个命令做日常清理一定要拦下来。6. 常见问题与排查技巧实录6.1 为什么 binlog 删不完磁盘还是满的最典型的原因是在事务结束后当前正在使用的 binlog 文件不能被删除。PURGE BINARY LOGS TO mysql-bin.000120只能清理000120之前的文件而当前正在写入的000120本身是删不掉的。如果磁盘占用大头恰好是当前文件删完旧文件后空间仍然紧张。另一个原因是 MySQL 触发清理需要时间点或条件修改binlog_expire_logs_seconds后不是立刻自动清理的要等下一次轮转或触发点。如果磁盘已经快满了不要干等自动清理直接手动执行PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY。还有一种常见场景是 binlog 目录里混入了非 binlog 文件比如误拷贝的备份文件也会让du统计看起来很大。先确认目录内容再判断是不是 binlog 的问题。6.2 主从报错 1236 或 1763 怎么办从库报Got fatal error 1236 from master when reading data from binary log通常意味着从库试图读取一个已经不存在或未知的文件。排查步骤一般是第一步在从库执行SHOW SLAVE STATUS确认报错线程是 IO 线程还是 SQL 线程并记录当前需要的日志文件和位点。第二步在主库执行SHOW BINARY LOGS确认该文件是否已经被清理。第三步确认文件是否被手动删除了如果索引文件还在但文件不存在多半是有人rm了文件或执行了错误的清理。第四步根据备份恢复或重建从库。1236一旦发生不要想着跳过一个文件继续同步除非明确知道缺失的部分对业务无影响。安全做法是从最近的备份点重建从库或者找到更早的 binlog 备份做持续追平。防止该类问题最有效的手段是监控从库延迟 监控主库 binlog 列表和清理位点。GTID 环境下的错误可能是1763表示事务已经被gtid_executed包含但当前又重复执行这类问题更复杂往往需要人工介入核对 GTID 集合。6.3 mysql-bin.index 与磁盘文件不一致使用rm直接删除 binlog 文件后磁盘文件和索引文件就会不一致。此时 MySQL 可能在写下一个文件时因为要找索引而报错主从复制也可能定位到不存在的文件。解决办法有两种如果只是删了少量文件先把缺失文件从备份里恢复回去再用PURGE BINARY LOGS正规清理或者直接编辑mysql-bin.index去掉那些已经不存在的文件路径但前提是确认所有从库都不需要这些文件且当前没有活跃事务依赖。编辑索引文件前建议先备份。手动编辑索引文件后建议重启 MySQL 或至少让实例重新加载索引但更稳妥的还是让文件状态恢复正常后再重启。说实话这种局面往往是应急删文件导致的当时看着把空间释放了后续麻烦一堆。真正紧急时我宁可先停业务做备份也不要直接rmbinlog。6.4 Docker 部署 MySQL 时的保留策略特例经常有人问 Docker 装的 MySQL 为什么 binlog 清理无效。大部分原因不是策略配错而是磁盘空间根本不是被 binlog 占的被 Docker 的 overlay 层和日志文件占了。如果你把 binlog 目录挂载到宿主机宿主机空间告急但 MySQL 参数配置没有生效先检查使用的是SET GLOBAL还是配置文件容器重启后是否丢失。另外一个常见坑是容器内的my.cnf可能是只读层文件修改后容器内不生效。正确做法是在宿主机映射配置目录或者在容器启动时通过--binlog-expire-logs-seconds参数传入。还有一点容器被删除重建时如果 binlog 数据卷没有正确挂载新实例的 binlog 索引和旧文件对不上启动会失败。在 Docker 环境下我建议把数据目录、binlog 目录都通过 volume 挂载出来便于排查和备份。保留策略参数写在挂载的配置文件里而不是每次docker exec进容器临时改否则容器重建一次就回到原点。6.5 binlog 日志可以删除吗这个问题被问过太多次了热搜里也有。答案很明确binlog 可以删除但必须用 MySQL 提供的机制删而不是在操作系统层面直接rm。使用PURGE BINARY LOGS删除是安全的因为 MySQL 会同步更新索引文件从库也会根据当前位点判断哪些文件还能安全删除。而rm只删了文件没有通知 MySQL 更新索引也没有考虑从库拉取进度很容易引发一连串故障。所以能不能删的前提是怎么删正确删法和错误删法对应的后果完全不同。7. 延伸思考binlog 保留策略还可以怎么优化7.1 定时任务和监控自动化的落地思路保留参数配置好后不等于一劳永逸。因为流量增长可能导致 binlog 日增量变大原来计划保留 7 天可能不到 3 天磁盘就满了。我建议做一个定时巡检脚本核心逻辑不复杂通过SHOW BINARY LOGS获取文件列表和总大小计算 binlog 目录占用占磁盘总空间的百分比如果超过阈值自动执行PURGE BINARY LOGS BEFORE NOW() - INTERVAL N DAY同时检查主从延迟如果延迟过大就停止自动清理并告警。这个脚本在纯crontab环境里就能跑也可以用 Ansible 批量下发到所有 MySQL 实例。很多团队会拿它当手工执行的运维工具而不是写死在系统里。我个人建议至少在脚本里加两个参数一个是保留多少天一个是磁盘使用率超过多少才触发清理比单纯按天清理更灵活。7.2 binlog 压缩归档和恢复演练如果业务对历史 binlog 有审计或长周期恢复需求磁盘又扛不住可以考虑把过期 binlog 压缩归档到对象存储或独立的备份服务器。mysqlbinlog支持从远程拉取 binlog配合归档文件也能用于恢复但恢复过程会复杂一些。另外一定要定期做恢复演练。保留策略写得再漂亮如果从没验证过用最近的备份binlog能恢复到期望时间点关键时刻大概率手忙脚乱。我自己就遇到过备份脚本一直正常但恢复时发现 binlog 保留时间根本不够覆盖备份周期的情况。恢复演练还能检测出mysql-bin.index损坏、GTID 集合异常这类平时看不出来的问题。8. 最后分享一个经验我在实际维护中慢慢形成的习惯是把 binlog 保留策略当作一份书面约定来管理写明保留天数、磁盘告警阈值、清理命令模板、主从延迟上限以及意外情况的联系人。每次调整都记录变更日期和原因。这个约定不需要很复杂但一定得写下来因为数据库实例一多靠脑子记迟早翻车。还有一个实用的小技巧在从库上配置read_only1并在从库的SHOW SLAVE STATUS里多加一个延迟监控一旦Seconds_Behind_Master超过设定值主库的自动清理就直接暂停等从库追平再继续。这比事后修复简单太多。清理 binlog 这件事看起来一行命令就能搞定但背后涉及日志生成速率、主从复制进度、备份恢复窗口、GTID 集合、索引文件一致性等多个环节。写这篇内容不是要给你一个固定的7 天套餐而是想传递一套判断方法先搞清楚日志在扮演什么角色再决定留多久最后用安全的方式去执行。希望你在下次磁盘告警来临时不用再凭感觉拍脑袋。
返回列表