mysql 8.0 默认开启 binlog,但必须为 row 格式且 binlog_row_image=full,才能通过 delete_rows_event 获取被删行完整前镜像;需用 mysqlbinlog 定位事件、提取 @n 值构造 insert,并跳过原 delete 重放,同时确认 binlog 未过期。

确认 binlog 是否开启且格式为 ROW
MySQL 8.0 默认启用 binlog,但必须是 ROW 格式才能解析出被删行的完整前镜像。如果用的是 STATEMENT 或 MIXED,mysqlbinlog 无法还原单条 DELETE 的具体内容。
- 执行
SHOW VARIABLES LIKE 'binlog_format';,输出必须为ROW - 检查
my.cnf中是否含binlog_format = ROW,并确认服务重启后生效 - 若格式不符,即使有 binlog 文件,也无法定位到被删那行的原始字段值
定位误删语句对应的 binlog 事件位置
关键不是找“DELETE 语句”,而是找到该 DELETE 对应的 Write_rows_event 后紧邻的 Delete_rows_event——后者自带被删行的完整快照(前提是 binlog_row_image = FULL,MySQL 8.0 默认即此值)。
- 用
mysqlbinlog --base64-output=DECODE-ROWS -v /path/to/mysql-bin.000001查看日志,搜索 WHERE 条件或主键值(如WHERE id = 123) - 注意时间范围:用
--start-datetime和--stop-datetime缩小范围,避免扫描全量日志 - 别依赖 SQL 文本输出:
mysqlbinlog在ROW模式下默认不打印可执行 SQL,需加--verbose才显示伪 SQL(仅供阅读,不能直接执行)
从 Delete_rows_event 提取原始数据并构造 INSERT
mysqlbinlog 输出中的 ### DELETE FROM `db`.`table` 块下方,会列出每列的旧值(标记为 @1=... @2=...)。这些就是被删行的原始内容,可直接映射为 INSERT 语句。
- 复制对应块内的所有
@n=...行,按表字段顺序整理成 VALUES 列表 - 注意类型转换:比如
@1=1是整数,@2='abc'要加引号,@3=NULL保持原样 - 若字段含自增主键,INSERT 时显式写出值(不要依赖 AUTO_INCREMENT),否则可能插入新 ID
- 示例片段:
### DELETE FROM `test`.`users` ### WHERE ### @1=42 ### @2='alice' ### @3='alice@example.com'
→ 对应INSERT INTO users VALUES (42, 'alice', 'alice@example.com');
跳过原 DELETE 事件重放 binlog
不能只执行刚构造的 INSERT——因为后续其他事务可能已变更该表,直接插可能冲突或覆盖新数据。稳妥做法是:在从库或临时实例上,用 mysqlbinlog --exclude-gtids=... | mysql 跳过误删事件,再把原始数据补回去。
- 先用
mysqlbinlog --skip-gtids --exclude-gtids="uuid:12345678-..." ...排除掉那个 DELETE 事件所在的 GTID - 若未启用 GTID,改用
--start-position和--stop-position精确跳过该事件区间 - 重放前务必在测试环境验证:导出当前表快照,对比重放后是否仅修复目标行、不影响其余数据
真正难的不是解析 binlog,而是确认那条 DELETE 事件有没有被 PURGE 或覆盖——binlog_expire_logs_seconds 默认 30 天,超时就彻底没救了。











