很多人对 MySQL 的 binlog(二进制日志)有个模糊印象:"好像是用来做主从复制的",但真问到"误删了一张表怎么用 binlog 恢复""为什么主从会延迟""binlog 占了这么多磁盘能不能清",就答不上来了。binlog 是 MySQL 里少数几个"平时不显眼、出事了全靠它"的组件之一:它记录所有会修改数据的语句或行变更,既是主从复制的基石,也是数据恢复的最后手段。删库跑路的经典救场,靠的就是它。
这篇文章要解决的是三件事:binlog 到底是什么、记录了什么;怎么用它做数据恢复(误删数据之后的找回流程);怎么用它搭建和排查主从复制。读者读完应该能回答"我的库现在有没有开 binlog""误删了数据还能不能救""主从延迟怎么查"。
文章会先讲 binlog 的格式(STATEMENT/ROW/MIXED)和它与 redo log 的区别,再给出一套可执行的"误删恢复"流程,然后讲主从复制的原理和搭建要点,最后讲日志清理和常见坑。涉及的参数、路径以 MySQL 5.7/8.0 为主,命令都可在真实环境执行。
一、binlog 是什么,记了什么
binlog 是 MySQL Server 层的日志(不是存储引擎层的,这点和 redo log 不同),它按顺序记录所有会修改数据或可能修改数据的操作,包括 DML(INSERT/UPDATE/DELETE)、DDL(CREATE/DROP/ALTER)以及事务提交记录。查询类的 SELECT 不进 binlog。
它的核心用途有两个:
- 主从复制:从库拉取主库的 binlog,重放其中的操作,从而和主库保持一致。
- 数据恢复:结合全量备份(如 mysqldump 或 XtraBackup),把备份恢复后,重放备份点之后的 binlog,找回数据。
开启方式是在配置文件里(通常 /etc/my.cnf 或 /etc/mysql/my.cnf)设置:
[mysqld]
server-id = 1
log_bin = /var/lib/mysql/mysql-bin # 开启 binlog 并指定前缀
binlog_format = ROW # 日志格式,生产推荐 ROW
binlog_expire_logs_seconds = 604800 # 8.0:保留 7 天(秒)
# expire_logs_days = 7 # 5.7:保留 7 天(天)
max_binlog_size = 512M # 单个文件大小
sync_binlog = 1 # 每次提交刷盘,最安全
查看是否开启:
-- 登录 MySQL 后
SHOW VARIABLES LIKE 'log_bin'; -- ON 表示已开启
SHOW VARIABLES LIKE 'binlog_format';
SHOW BINARY LOGS; -- 列出所有 binlog 文件及大小
SHOW MASTER STATUS; -- 当前写入位置
对应文件位置通过 SHOW VARIABLES LIKE 'log_bin_basename'; 查到,常见在 /var/lib/mysql/ 下,文件名形如 mysql-bin.000001。
二、三种 binlog 格式:STATEMENT、ROW、MIXED
binlog 记录内容的方式由 binlog_format 决定,这也是理解 binlog 的关键。
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | 记录原始 SQL 语句 | 日志小、可读 | 含 NOW()、UUID()、RAND() 等不确定函数时主从不一致 |
| ROW | 记录每一行数据的前后变化 | 主从一致性强、可靠 | 日志体积大,如全表 UPDATE 会写大量行 |
| MIXED | 普通语句用 STATEMENT,不确定的自动转 ROW | 折中 | 行为依赖 MySQL 判断,不如 ROW 直观 |
生产环境推荐 ROW。原因:STATEMENT 格式在许多场景下会主从不一致(比如用了 LIMIT 不带 ORDER BY、用了随机函数、触发器);ROW 记录的是"哪一行被改成了什么",从库重放的结果和主库严格一致,可靠性最高。代价是日志膨胀——一条 UPDATE t SET status=1 更新 100 万行,ROW 格式会记录 100 万行的变更,binlog 可能几百 MB;而 STATEMENT 只记一行 SQL。所以选 ROW 时要留意磁盘和写入量。
MySQL 8.0 默认就是 ROW,5.7 默认是 ROW(部分版本为 MIXED),但仍建议显式指定。
三、binlog 与 redo log、undo log 的区别
这是面试和实际排障都高频的问题。
- redo log(重做日志):InnoDB 引擎层的物理日志,记录"某页做了什么修改",用于崩溃恢复。MySQL 宕机重启后,用它把已提交但未落盘的数据重做出来。它是循环写的,空间固定。
- undo log(回滚日志):也属 InnoDB,记录数据的旧版本,用于事务回滚和 MVCC 一致性读。
- binlog(二进制日志):Server 层的逻辑日志,记录所有变更操作,用于主从复制和时间点恢复。它是追加写的,文件可累积。
一句话区分:redo log 保证"宕机后数据不丢",undo log 保证"事务能回滚、读能快照",binlog 保证"能复制到别的库、能在事后重放恢复"。
四、用 binlog 做数据恢复
核心场景:有人误删了数据(DELETE、DROP TABLE),怎么救回来。前提是有全量备份 + binlog 没被删。
整体流程是:先用最近的备份恢复出一个数据库,再用 binlog 把"备份时刻之后到误操作之前"的变更重放上去。
步骤一:确认可用的备份和 binlog 边界
# 看有哪些全量备份
ls -lh /data/backup/
# 看 binlog 文件范围
mysql -e 'SHOW BINARY LOGS;'
假设最近一次全量备份是 backup-20261012.sql,备份时刻对应的 binlog 位置是 mysql-bin.000005 的第 12345 字节(备份工具通常会记录位置),误删操作发生在 mysql-bin.000007。那么需要重放 000005 的后半段到 000007 的误操作之前。
步骤二:把 binlog 解析成可读的 SQL
# 按时间范围导出为 SQL
mysqlbinlog --start-datetime='2026-10-12 00:00:00' \
--stop-datetime='2026-10-12 09:30:00' \
/var/lib/mysql/mysql-bin.000005 \
/var/lib/mysql/mysql-bin.000006 \
/var/lib/mysql/mysql-bin.000007 > /data/recover.sql
# 或按 position 精确控制
mysqlbinlog --start-position=12345 --stop-position=67890 \
/var/lib/mysql/mysql-bin.000007 > /data/recover.sql
# ROW 格式加 -v 或 -vv,把行变更解析成可读的伪 SQL
mysqlbinlog -vv --base64-output=DECODE-ROWS /var/lib/mysql/mysql-bin.000007 > /data/recover_decoded.sql
步骤三:去掉误操作,重放
在 recover.sql 里找到误删的语句(如 DROP TABLE 或 DELETE FROM),把它以及之后的、你不想重放的部分删掉,或者用 --stop-position / --stop-datetime 精确卡在误操作之前。然后把恢复出来的数据导入。
# 先用备份恢复(注意:这会覆盖现有数据,务必在测试库或临时实例上做)
mysql -uroot -p < /data/backup/backup-20261012.sql
# 再重放增量 binlog
mysql -uroot -p < /data/recover.sql
风险提示:数据恢复是高风险操作。直接在线上库执行备份恢复会覆盖当前数据,可能造成二次损失。标准做法是:先在独立的临时实例或测试库上把整个恢复流程跑通、确认结果正确,再决定最终的挪回方案。此外,重放 binlog 可能重复执行已成功的变更,ROW 格式配合 --skip-gtids(5.6+)或正确设置 GTID 尤为重要。操作前先 cp 一份原始数据目录做保底,并确认磁盘有足够空间。
回滚方式:在临时实例验证通过前,绝不碰生产库;对生产库任何操作前先做一次物理快照(如 xtrabackup 或存储层快照),出问题时从快照恢复。
五、用 binlog 做主从复制
主从复制的基本原理:主库把变更写进 binlog,从库的 I/O 线程拉取 binlog 写到本地 relay log(中继日志),从库的 SQL 线程读取 relay log 并重放,从而保持数据一致。
主库配置
[mysqld]
server-id = 1
log_bin = /var/lib/mysql/mysql-bin
binlog_format = ROW
binlog_row_image = FULL # ROW 格式记录完整前后镜像
gtid_mode = ON # 8.0 推荐开启 GTID
enforce_gtid_consistency = ON
从库配置
[mysqld]
server-id = 2 # 必须全局唯一
relay_log = /var/lib/mysql/relay-bin
read_only = ON # 从库只读,防误写
gtid_mode = ON
enforce_gtid_consistency = ON
建立复制关系
-- 主库上创建复制账号并授权
CREATE USER 'repl'@'10.0.0.%' IDENTIFIED BY 'StrongPass!2026';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.0.%';
FLUSH PRIVILEGES;
-- 从库上(8.0 GTID 方式)
CHANGE MASTER TO
MASTER_HOST='10.0.0.10',
MASTER_USER='repl',
MASTER_PASSWORD='StrongPass!2026',
MASTER_AUTO_POSITION=1;
START SLAVE;
SHOW SLAVE STATUS\G
(MySQL 8.0.22+ 使用 CHANGE REPLICATION SOURCE TO / START REPLICA,旧写法仍兼容。)
关注 SHOW SLAVE STATUS 里的关键字段:
Slave_IO_Running和Slave_SQL_Running:都应为Yes。Seconds_Behind_Master:主从延迟秒数,为NULL通常表示复制中断。Last_IO_Error/Last_SQL_Error:复制报错信息。
六、主从延迟怎么排查
复制延迟是最常见的复制问题,表现为 Seconds_Behind_Master 持续偏高。
常见原因和应对:
| 原因 | 现象 | 应对 |
|---|---|---|
| 从库单线程重放跟不上主库并发写 | SQL 线程忙、延迟持续涨 | 开启并行复制(slave_parallel_workers,8.0 用 replica_parallel_workers) |
| 大事务 | 某条大 SQL 导致延迟尖峰 | 拆分大事务、避免大表 DDL |
| 从库硬件弱于主库 | 长期慢 | 提升从库配置 |
| 从库承担大量读压力 | 延迟与查询量正相关 | 分担读流量、加从库 |
| 网络延迟 | IO 线程慢 | 检查网络 |
-- 查看并行复制相关配置
SHOW VARIABLES LIKE 'slave_parallel_workers';
SHOW VARIABLES LIKE 'slave_parallel_type';
开启并行复制(8.0):
SET GLOBAL slave_parallel_workers = 4;
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
七、日志清理与磁盘管理
binlog 会一直追加,不清会撑爆磁盘。
-- 设置自动过期(8.0)
SET GLOBAL binlog_expire_logs_seconds = 604800; -- 7 天
-- 手动清理指定文件之前的所有日志(危险操作)
PURGE BINARY LOGS TO 'mysql-bin.000010';
-- 或按时间
PURGE BINARY LOGS BEFORE '2026-10-05 00:00:00';
# 也可以用 mysqladmin
mysqladmin -uroot -p flush-logs # 轮转一个新 binlog 文件
风险提示:PURGE BINARY LOGS 会永久删除 binlog 文件,删掉的日志无法用于恢复,也可能导致尚未同步的从库复制中断。清理前务必确认:所有从库都已同步过这些日志、最近的备份点之后的 binlog 仍保留、磁盘清理是唯一目的。回滚方式:binlog 一旦删除无法找回,只能在操作前做好备份(把要删的文件先 cp 到别处)。
保留多久合适?取决于备份策略和可接受的数据丢失窗口。一般做法是"至少保留到下一次全量备份完成之后",常见的是一周。有的团队还会把 binlog 归档到对象存储长期保留。
常见问题
Q:我的库没开 binlog,误删数据还能恢复吗?
很难。没有 binlog 就没有增量记录,只能依赖全量备份恢复,备份之后到误删之间的数据基本找不回。所以生产库务必开启 binlog 并定期备份。
Q:ROW 格式 binlog 太大,磁盘吃不消怎么办?
可以缩短保留时间、把 binlog 放到独立磁盘、归档到对象存储;同时优化业务,避免无谓的大批量更新。不要为了省空间改回 STATEMENT,一致性风险更大。
Q:sync_binlog=1 会不会拖慢写入?
会略微降低写入性能(每次提交都刷盘),但这是保证"提交即落盘、崩溃不丢 binlog"的关键。对数据安全要求高的库建议保持 1;极致性能场景可评估设 0 或 N 的取舍。
Q:从库 Seconds_Behind_Master 显示 0 就一定没延迟吗?
不一定。它是估算值,在复制线程空闲或停止时可能不可靠,且不反映网络层尚未拉取的量。要结合 Master_Log_File/Read_Master_Log_Pos 与主库 SHOW MASTER STATUS 的位置差综合判断。
Q:GTID 和传统基于 position 的复制该用哪个?
新搭建优先 GTID(MASTER_AUTO_POSITION=1),它让故障切换时的位置管理更简单、更不容易错。已有基于 position 的环境如果稳定,可以不改。
总结
binlog 是 MySQL Server 层的逻辑日志,记录所有数据变更,核心用途是主从复制和时间点数据恢复,与 InnoDB 层的 redo log(崩溃恢复)、undo log(回滚与 MVCC)分工不同。
生产环境推荐 binlog_format=ROW,一致性最强,代价是日志体积可能较大;配 sync_binlog=1 保证提交即落盘。
数据恢复的流程是"全量备份恢复 + 重放备份点之后的 binlog(用 mysqlbinlog 解析,卡在误操作之前)",务必先在临时实例验证,绝不在生产库直接覆盖,操作前做好快照。
主从复制靠"主库写 binlog → 从库拉取 relay log → 重放"实现;SHOW SLAVE STATUS 的 Slave_IO_Running、Slave_SQL_Running、Seconds_Behind_Master 是判断复制健康的核心字段,延迟高优先查大事务、单线程重放和从库负载。
binlog 要设过期时间或用 PURGE BINARY LOGS 清理,但清理前必须确认从库已同步、备份可用,删掉就再也找不回。
企业QQ咨询




