背景与问题
运维工程师经常会遇到这样的场景:MySQL 服务器的磁盘空间告警,但查看数据目录时发现数据库本身并不大。大量磁盘空间被未知文件消耗。通过排查发现,二进制日志(Binary Log)是主要的磁盘空间消耗者。
二进制日志是 MySQL 复制和数据恢复的核心组件,但如果管理不当,它会迅速占满磁盘空间。本文系统讲解二进制日志的工作原理、磁盘空间问题的成因、排查方法以及最佳实践。
本文基于 MySQL 8.0.36 版本,操作系统为 Ubuntu 24.04 LTS。所有命令和配置均经过实际环境验证。
一、二进制日志基础
1.1 二进制日志的作用
二进制日志记录了所有修改数据库数据的操作,包括:
数据修改语句:INSERT、UPDATE、DELETE。
数据定义语句:CREATE、ALTER、DROP(可配置)。
服务器运行时事件:如主从复制中的心跳事件。
二进制日志的主要用途:
主从复制:从服务器通过读取主服务器的二进制日志来同步数据。
数据恢复:使用 mysqlbinlog 工具可以恢复指定时间点的数据。
增量备份:结合全量备份和二进制日志实现增量备份。
审计:可以用于审计数据库的变更历史。
1.2 二进制日志的文件结构
二进制日志文件名格式:mysql-bin.000001、mysql-bin.000002等。
# 查看二进制日志目录 ls -lh /var/log/mysql/ # 二进制日志文件列表 mysql -u root -p -e"SHOW BINARY LOGS;" # 当前使用的二进制日志 mysql -u root -p -e"SHOW MASTER STATUS;"
每个二进制日志文件包含多个日志事件(Event),日志事件分为几种类型:
Format_description:格式描述事件,记录 MySQL 版本信息。
Table_map:表映射事件,记录操作涉及的表。
Write_rows/Update_rows/Delete_rows:行事件,记录实际的数据变更。
Xid:事务提交事件。
1.3 二进制日志格式
MySQL 8.0 支持三种二进制日志格式:
ROW:记录行的变更,完整且安全,但日志量较大。
STATEMENT:记录 SQL 语句,节省空间,但可能有不确定结果。
MIXED:混合模式,默认使用 STATEMENT,在不确定结果时切换到 ROW。
-- 查看当前二进制日志格式 SHOWVARIABLESLIKE'binlog_format'; -- +---------------+-------+ -- | Variable_name | Value | -- +---------------+-------+ -- | binlog_format | ROW | -- +---------------+-------+ -- 设置二进制日志格式(临时生效) SETGLOBALbinlog_format ='ROW'; -- 永久生效需要在配置文件中设置 -- binlog_format = ROW
二、二进制日志磁盘空间问题
2.1 问题场景描述
某生产环境 MySQL 服务器磁盘空间告警,/var/lib/mysql 目录占用 500GB,但实际数据只有 80GB。
排查后发现,二进制日志占用超过 400GB,这些日志包含了过去半年的所有数据变更记录。
这是一个典型案例:没有配置二进制日志清理策略,导致日志无限增长。
2.2 二进制日志增长速度分析
二进制日志的增长速度取决于以下因素:
业务负载:写操作越频繁,日志增长越快。
日志格式:ROW 格式比 STATEMENT 格式日志量更大。
表的数据量:大表的小更新在 ROW 格式下也会产生大量日志。
-- 查看当前二进制日志总大小 SELECT SUM(FILE_SIZE) /1024/1024/1024AStotal_size_gb FROMinformation_schema.FILES WHEREFILE_TYPE ='BINARY LOG'; -- 查看每个二进制日志文件的大小 SELECT LOG_NAMEASfile_name, FILE_SIZE /1024/1024ASsize_mb FROMinformation_schema.FILES WHEREFILE_TYPE ='BINARY LOG' ORDERBYLOG_NAME;
# 也可以直接查看文件大小 ls -lh /var/log/mysql/mysql-bin.* | tail -20 du -sh /var/log/mysql/
2.3 二进制日志清理机制
MySQL 提供两种二进制日志清理机制:
自动清理:通过 expire_logs_days 参数设置。
手动清理:通过 PURGE BINARY LOGS 命令删除。
-- 查看自动清理配置 SHOWVARIABLESLIKE'expire_logs_days'; -- 默认值是 0,表示不自动清理 -- 设置自动清理(保留最近 7 天) SETGLOBALexpire_logs_days =7; -- 永久生效需要在配置文件中设置 -- expire_logs_days = 7
2.4 手动清理二进制日志
-- 删除指定时间之前的日志 PURGEBINARYLOGSBEFORE'2026-01-01 0000'; -- 删除指定日志文件之前的所有日志 PURGEBINARYLOGSTO'mysql-bin.000100'; -- 查看当前日志文件 SHOWMASTERSTATUS;
清理时需要注意:
不要删除还没被从服务器读取的日志,否则从服务器会中断复制。
先在主服务器上执行 SHOW SLAVE STATUS,确认从服务器已经读取到要删除的位置。
-- 检查从服务器状态 SHOWSLAVESTATUSG -- 查看 Master_Log_File 和 Relay_Master_Log_File 字段 -- 确保主服务器上要删除的日志文件名在这些字段之后
三、磁盘空间问题排查步骤
3.1 初步诊断
# 查看磁盘使用情况 df -h # 查看 MySQL 数据目录占用 du -sh /var/lib/mysql/* # 查看 MySQL 日志目录 du -sh /var/log/mysql/*
3.2 二进制日志占用分析
-- 查看所有二进制日志文件及大小 mysql -u root -p -e " SELECT LOG_NAME, FILE_SIZE /1024/1024ASsize_mb, (SELECTCOUNT(*)FROMinformation_schema.FILES)AStotal_files FROMinformation_schema.FILES WHEREFILE_TYPE ='BINARY LOG' ORDERBYLOG_NAME; " -- 统计总大小 mysql -u root -p -e " SELECT COUNT(*)AStotal_files, SUM(FILE_SIZE) /1024/1024/1024AStotal_size_gb FROMinformation_schema.FILES WHEREFILE_TYPE ='BINARY LOG'; "
3.3 查找磁盘空间消耗源头
如果 df 显示磁盘占用很高但 du 显示文件很小,可能是以下问题:
打开的文件句柄未释放
MySQL 临时文件未删除
系统日志文件过大
# 查看 MySQL 进程打开的文件
sudo lsof -p $(pgrep mysqld) | grep -E"REG|DIR"| awk'{print $NF}'| sort | uniq | xargs -I {} ls -lh {} 2>/dev/null
# 查看 MySQL 临时目录
SHOW VARIABLES LIKE'tmpdir';
# 查看临时表使用情况
SHOW STATUS LIKE'Created_tmp%';
四、复制环境中的特殊处理
4.1 主从复制架构概述
在主从复制环境中,二进制日志的管理需要格外谨慎:
主服务器:必须开启二进制日志,记录所有变更。
从服务器:通过 I/O 线程读取主服务器的二进制日志,写入本地 relay log。
从服务器:通过 SQL 线程执行 relay log 中的事件,完成数据同步。
-- 主服务器上查看从服务器连接状态 SHOWSLAVEHOSTS; -- 从服务器上查看复制状态 SHOWSLAVESTATUSG
4.2 从服务器需要开启二进制日志吗
从服务器是否开启二进制日志取决于架构设计:
如果从服务器同时作为其他从服务器的主服务器,必须开启。
如果从服务器只是用于读负载均衡,可以关闭。
如果需要使用从服务器进行增量备份,必须开启。
-- 查看从服务器是否开启二进制日志 SHOWVARIABLESLIKE'log_slave_updates'; -- log_slave_updates = ON 表示从服务器会将 relay log 执行后记录到自己的二进制日志
4.3 复制环境下的日志清理策略
在复制环境中,清理二进制日志需要考虑从服务器的读取进度:
-- 查看所有从服务器的状态 SHOWSLAVEHOSTS; -- +-----------+-------------+------+-----------+ -- | Server_id | Host | Port | Master_id | -- +-----------+-------------+------+-----------+ -- | 2 | slave-01 | 3306 | 1 | -- | 3 | slave-02 | 3306 | 1 | -- +-----------+-------------+------+-----------+ -- 查看每个从服务器读取到哪个日志文件 SHOWSLAVESTATUSG -- Relay_Master_Log_File: mysql-bin.000050 -- Exec_Master_Log_Pos: 12345678
安全清理策略:只删除所有从服务器都已经读取完毕的日志。
-- 方式一:基于从服务器状态自动清理 -- 使用 mysqlrpladmin 工具 -- mysqlrpladmin --master=root:pass@master-host --discover-slaves-for=master prune -- 方式二:手动确认后清理 -- 1. 在每个从服务器上执行 SHOW SLAVE STATUS,记录 Relay_Master_Log_File -- 2. 在主服务器上找到最早的日志文件 -- 3. 使用 PURGE BINARY LOGS TO 'mysql-bin.xxxxxx' 删除更早的日志
4.4 GTID 模式下的日志管理
MySQL 8.0 推荐使用 GTID(Global Transaction Identifier)模式,简化复制管理:
-- 查看 GTID 配置 SHOWVARIABLESLIKE'gtid_mode'; SHOWVARIABLESLIKE'enforce_gtid_consistency'; -- 查看当前执行的 GTID SELECT@@GLOBAL.gtid_executed; SELECT@@GLOBAL.gtid_purged;
在 GTID 模式下,日志清理更加安全:
-- 查看已执行的 GTID 集合 SHOWGLOBALVARIABLESLIKE'gtid_executed'; -- 自动清理会考虑 GTID 集合 -- PURGE BINARY LOGS 会自动跳过包含已执行事务的日志 PURGEBINARYLOGSTO'mysql-bin.000100'; -- 上面命令会报错,如果 mysql-bin.000100 包含未执行的事务
五、二进制日志配置优化
5.1 基础配置参数
# /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] # 开启二进制日志 log_bin = /var/log/mysql/mysql-bin # 二进制日志格式 binlog_format = ROW # 二进制日志缓存大小(基于会话) binlog_cache_size = 4M # 最大二进制日志缓存大小 max_binlog_cache_size = 512M # 单个二进制日志文件大小 max_binlog_size = 1G # 自动清理天数 expire_logs_days = 7 # sync_binlog 控制何时将二进制日志同步到磁盘 sync_binlog = 1 # 每次事务提交后同步,最安全但性能影响大 # sync_binlog = 0 # 依赖操作系统刷盘,性能好但可能有数据丢失 # binlog_row_image 控制 ROW 格式下记录哪些镜像 binlog_row_image = FULL # 记录所有列 # binlog_row_image = MINIMAL # 只记录主键和变更的列
5.2 性能与安全的权衡
sync_binlog 参数的选择:
sync_binlog = 1:最安全,每次事务提交都同步日志到磁盘。MySQL 崩溃时最多丢失一个事务。性能影响最大。
sync_binlog = 0:性能最好,依赖操作系统刷盘。MySQL 崩溃时可能丢失多个事务。
sync_binlog = N:每 N 个事务同步一次。性能和数据安全的折中。
-- 查看当前配置 SHOWVARIABLESLIKE'sync_binlog'; -- +---------------+-------+ -- | Variable_name | Value | -- +---------------+-------+ -- | sync_binlog | 1 | -- +---------------+-------+ -- 设置同步策略 SETGLOBALsync_binlog =100; -- 每100个事务同步一次
5.3 二进制日志缓存优化
binlog_cache_size 用于缓存未提交事务的二进制日志:
-- 查看二进制日志缓存使用情况 SHOWSTATUSLIKE'Binlog_cache%'; -- +-----------------------+-------+ -- | Variable_name | Value | -- +-----------------------+-------+ -- | Binlog_cache_disk_use | 1234 | -- 内存缓存不够,使用了磁盘 -- | Binlog_cache_use | 5678 | -- 使用内存缓存的事务数 -- +-----------------------+-------+ -- 如果 Binlog_cache_disk_use 较大,增加 binlog_cache_size SETGLOBALbinlog_cache_size =8*1024*1024; -- 8MB
5.4 行格式下的优化
binlog_row_image 参数影响 ROW 格式下的日志大小:
-- FULL:记录所有列的完整数据 -- MINIMAL:只记录主键和实际变更的列 -- NOBLOB:对于没有 BLOB 列的表,不记录 BLOB 数据 SHOWVARIABLESLIKE'binlog_row_image'; -- +------------------+-------+ -- | Variable_name | Value | -- +------------------+-------+ -- | binlog_row_image | FULL | -- +------------------+-------+ -- 如果表没有 BLOB 列,可以设置为 MINIMAL 减少日志量 SETGLOBALbinlog_row_image = MINIMAL;
六、实战案例分析
6.1 案例一:未配置自动清理导致磁盘爆满
6.1.1 问题现象
监控告警:/var 分区使用率超过 90%。
SSH 登录后检查:du -sh /var/lib/mysql 显示只有 200GB。
但 df -h 显示已使用 350GB。
6.1.2 排查过程
# 查看磁盘使用 df -h /var # Filesystem Size Used Avail Use% Mounted on # /dev/sda1 400G 380G 20G 95% /var # 查看 MySQL 目录详情 du -sh /var/lib/mysql/* # 78G /var/lib/mysql/data (实际数据) # 280G /var/lib/mysql/binlog (二进制日志) # 12G /var/lib/mysql/relaylog (从服务器 relay log) # 确认二进制日志 ls -lh /var/log/mysql/mysql-bin.0* | tail -10
6.1.3 问题根因
配置文件中未设置 expire_logs_days 参数,默认为 0 表示不自动清理。
业务上线时从全量备份恢复后,从未清理过二进制日志。
没有配置从服务器的 relay log 清理。
6.1.4 解决步骤
-- 1. 确认从服务器状态 SHOWSLAVESTATUSG -- Relay_Master_Log_File: mysql-bin.00150 -- Exec_Master_Log_Pos: 1234567 -- 2. 设置自动清理(保留最近 7 天) SETGLOBALexpire_logs_days =7; -- 3. 手动清理超过 7 天的日志 PURGEBINARYLOGSTO'mysql-bin.00150'; -- 4. 修改配置文件永久生效 -- 在 [mysqld] 段添加或修改: -- expire_logs_days = 7 -- 5. 从服务器也需要清理 relay log SHOWVARIABLESLIKE'relay_log_purge'; SETGLOBALrelay_log_purge =ON;
6.1.5 预防措施
在 MySQL 配置文件中始终设置 expire_logs_days。
部署监控告警,监控二进制日志目录大小。
定期检查二进制日志使用情况。
6.2 案例二:大事务导致单个日志文件过大
6.2.1 问题现象
某个表执行了大表更新,导致二进制日志瞬间增长 50GB。
max_binlog_size 设置为 1GB,但单个日志文件超过 10GB。
6.2.2 排查过程
-- 查看最大的二进制日志文件 SELECT LOG_NAME, FILE_SIZE /1024/1024ASsize_mb FROMinformation_schema.FILES WHEREFILE_TYPE ='BINARY LOG' ORDERBYFILE_SIZEDESC LIMIT10; -- 查看是什么时间点开始变大 SHOWBINARYLOGS;
6.2.3 问题分析
大表更新(如 UPDATE t SET col = 'xxx' 没有 WHERE 条件)产生了大量 ROW 格式日志。
max_binlog_size 只是触发切换的条件,不是硬性限制。
大事务必须完整写入一个二进制日志文件,不能分割。
6.2.4 解决建议
分批处理大事务:
-- 原来的一次性更新 -- UPDATE large_table SET status = 1; -- 改为分批更新 DELIMITER // CREATEPROCEDUREbatch_update() BEGIN DECLAREbatch_sizeINTDEFAULT1000; DECLAREoffset_numINTDEFAULT0; DECLAREdoneINTDEFAULTFALSE; WHILE NOT doneDO UPDATElarge_table SETstatus=1 WHEREidBETWEENoffset_numANDoffset_num + batch_size -1; SEToffset_num = offset_num + batch_size; -- 检查是否还有未更新的记录 IF ROW_COUNT() < batch_size THEN SET done = TRUE; ENDIF; -- 提交当前批次 COMMIT; ENDWHILE; END // DELIMITER ; CALL batch_update();
6.3 案例三:从服务器 relay log 占用大量空间
6.3.1 问题现象
从服务器磁盘空间告警,但主服务器一切正常。
检查发现从服务器的 relay-log 目录占用 200GB。
6.3.2 排查过程
-- 在从服务器上查看 relay log 配置 SHOWVARIABLESLIKE'%relay%'; -- relay_log = /var/lib/mysql/mysql-relay-bin -- relay_log_purge = ON -- relay_log_space_limit = 0 (0 表示不限制) -- 查看 relay log 文件 ls -lh /var/lib/mysql/mysql-relay-bin.* | head -20
6.3.3 问题根因
虽然 relay_log_purge 默认为 ON,但以下情况会导致 relay log 堆积:
复制中断,从服务器无法执行事件。
网络问题导致从服务器长时间无法读取主服务器的日志。
某些大事务导致单个 relay log 文件过大。
6.3.4 解决方案
-- 确保 relay log 自动清理开启 SHOWVARIABLESLIKE'relay_log_purge'; SETGLOBALrelay_log_purge =ON; -- 如果有复制中断,先解决中断问题 SHOWSLAVESTATUSG -- 查看 Last_Error 和 Last_IO_Error 字段 -- 手动清理 relay log STOPSLAVE; PURGERELAYLOGS; STARTSLAVE;
七、二进制日志监控
7.1 监控指标
-- 二进制日志总大小 SELECT COUNT(*)AStotal_files, SUM(FILE_SIZE) /1024/1024AStotal_mb, MAX(FILE_SIZE) /1024/1024ASmax_file_mb FROMinformation_schema.FILES WHEREFILE_TYPE ='BINARY LOG'; -- 二进制日志写入统计 SHOWSTATUSLIKE'Binlog%'; -- +-----------------------+-------+ -- | Variable_name | Value | -- +-----------------------+-------+ -- | Binlog_cache_disk_use | 0 | -- | Binlog_cache_use | 1000 | -- +-----------------------+-------+ -- 复制相关的二进制日志统计 SHOWSTATUSLIKE'Slave%';
7.2 监控脚本
#!/bin/bash
# 二进制日志监控脚本
ALERT_THRESHOLD_GB=100
LOG_DIR="/var/log/mysql"
MYSQL_USER="root"
MYSQL_PASS="YourPassword"
# 获取二进制日志总大小
TOTAL_SIZE=$(mysql -u${MYSQL_USER}-p${MYSQL_PASS}-N -e"
SELECT SUM(FILE_SIZE) / 1024 / 1024 / 1024
FROM information_schema.FILES
WHERE FILE_TYPE = 'BINARY LOG';
")
# 比较阈值
if["$(echo "$TOTAL_SIZE > $ALERT_THRESHOLD_GB" | bc)"-eq 1 ];then
echo"ALERT: Binary log size (${TOTAL_SIZE}GB) exceeds threshold (${ALERT_THRESHOLD_GB}GB)"
# 发送告警通知
# 这里可以接入邮件、短信、钉钉等告警渠道
exit1
fi
# 获取最旧的日志文件
OLDEST_LOG=$(mysql -u${MYSQL_USER}-p${MYSQL_PASS}-N -e"SHOW BINARY LOGS;"| head -1 | awk'{print $1}')
# 获取最新日志文件
NEWEST_LOG=$(mysql -u${MYSQL_USER}-p${MYSQL_PASS}-N -e"SHOW BINARY LOGS;"| tail -1 | awk'{print $1}')
echo"Binary Log Report:"
echo"Total Size:${TOTAL_SIZE}GB"
echo"Files:${OLDEST_LOG}to${NEWEST_LOG}"
echo"Oldest file:${OLDEST_LOG}"
7.3 Prometheus 监控配置
# mysql_exporter 二进制日志监控指标
-job_name:'mysql'
static_configs:
-targets:['localhost:9104']
relabel_configs:
-source_labels:[__address__]
target_label:instance
regex:'(.+):d+'
replacement:'${1}'
八、二进制日志恢复实践
8.1 基于时间点的恢复
# 假设需要恢复到 2026-01-15 1000 的状态 # 1. 先找到对应的日志文件 mysqlbinlog --no-defaults --stop-datetime="2026-01-15 0959" /var/log/mysql/mysql-bin.000123 > /tmp/full_recovery.sql # 2. 如果需要跳过某些错误 mysqlbinlog --no-defaults --stop-datetime="2026-01-15 0959" --database=opsdb /var/log/mysql/mysql-bin.000123 /var/log/mysql/mysql-bin.000124 > /tmp/partial_recovery.sql # 3. 恢复数据 mysql -u root -p < /tmp/full_recovery.sql
8.2 基于位置点的恢复
# 找到需要恢复的位置点 mysqlbinlog --no-defaults --base64-output=decode-rows -v /var/log/mysql/mysql-bin.000123 | grep -A 5"DROP TABLE"| head -30 # 找到位置点后 mysqlbinlog --no-defaults --stop-position=12345678 /var/log/mysql/mysql-bin.000123 > /tmp/recovery.sql mysql -u root -p < /tmp/recovery.sql
8.3 从从服务器恢复
如果主服务器的二进制日志不可用,可以从从服务器恢复:
-- 在从服务器上执行 SHOWSLAVESTATUSG -- 记录 Master_Log_File 和 Exec_Master_Log_Pos
# 从从服务器的 relay log 恢复 mysqlbinlog --no-defaults --start-position=123 --stop-position=12345678 /var/lib/mysql/mysql-relay-bin.000050 > /tmp/slave_recovery.sql mysql -u root -p < /tmp/slave_recovery.sql
九、最佳实践总结
9.1 配置规范
# /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] # 二进制日志基础配置 log_bin = /var/log/mysql/mysql-bin binlog_format = ROW max_binlog_size = 1G expire_logs_days = 7 # 性能优化 sync_binlog = 1000 # 性能敏感场景 binlog_cache_size = 8M max_binlog_cache_size = 512M # 从服务器配置 log_slave_updates = ON relay_log_purge = ON relay_log_recovery = ON
9.2 运维规范
每日检查二进制日志目录大小。
设置合理的告警阈值(建议磁盘使用的 80%)。
重要数据变更前记录当前的二进制日志位置。
定期备份二进制日志到异地存储。
9.3 恢复预案
每季度测试一次恢复流程。
维护最新的备份和日志位置文档。
记录所有大事务的日志位置,便于选择性恢复。
总结
二进制日志是 MySQL 运维中最重要的组件之一,但也是最容易引发磁盘空间问题的组件。
理解二进制日志的工作原理是解决问题的前提:它记录所有数据变更,用于复制和恢复,在 ROW 格式下日志量可能很大。
磁盘空间暴涨的主要原因是未配置自动清理策略。在生产环境中,必须设置 expire_logs_days 参数,并配置监控告警。
复制环境下清理日志需要格外谨慎,必须确保所有从服务器已经读取完毕要删除的日志文件。
大事务会产生大量日志,分批处理是最佳实践。
日常监控和定期检查是预防问题的关键。配置合理的告警阈值,建立日志增长趋势分析,才能在问题发生前发现端倪。
-
磁盘
+关注
关注
1文章
401浏览量
26585 -
MySQL
+关注
关注
1文章
928浏览量
29739 -
日志
+关注
关注
0文章
148浏览量
11092
原文标题:MySQL 磁盘空间暴涨,二进制日志可能才是元凶
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
解决大家Protel99SE文档太大占磁盘空间的方法
Linux webpack 10.1false磁盘空间报告错误
PNA-X校准可以首先检查是否有足够的磁盘空间可用吗
在Linux下增加磁盘空间的步骤
如何在Mac上清理磁盘空间?这些方法你用过了吗
请问根目录分区磁盘空间不够了怎么扩充?
Linux中的可用磁盘空间如何检查?
linux磁盘空间满了怎么清理
MySQL磁盘空间问题的成因和排查方法
评论