实战:使用 MyFlash 从 ROW 格式 Binlog 恢复被多次删除的表数据
2026/5/2 17:33:21 网站建设 项目流程

实战:使用 MyFlash 从 ROW 格式 Binlog 恢复被多次删除的表数据背景

背景

当一张表中的数据在一个月内被多次误删,且没有定期逻辑备份时,只要 MySQL 开启了ROW​ 格式的 Binlog,且日志文件未被清理,我们就有可能通过闪回工具(MyFlash)DELETE​ 操作反转为INSERT,从而安全地恢复数据。

本文将手把手教你如何用 MyFlash 还原目标表的所有被删数据,并给出在真实生产环境下可执行的安全恢复流程。


一、核心前提检查

在开始前,请逐项确认以下条件是否满足。任何一项不满足都可能导致恢复失败。

检查项命令 / 说明期望值
Binlog 已开启SHOW VARIABLES LIKE 'log_bin';ON
Binlog 格式为 ROWSHOW VARIABLES LIKE 'binlog_format';ROW
Binlog 行镜像为 FULLSHOW VARIABLES LIKE 'binlog_row_image';FULL(否则非 UPDATE 的列可能缺失)
误删时间范围内的 Binlog 文件未被清理检查 MySQL 数据目录下的日志文件列表,或使用SHOW BINARY LOGS;查看物理文件存在,且跨越你需要的时间段
有独立的测试数据库环境绝对禁止在生产库上直接反复试错

注意binlog_row_image = MINIMAL​ 时,DELETE 事件的前镜像可能只包含主键,其他列的值会丢失。如果确认所有列都需要恢复,请先紧急修改为FULL并等待下一次备份后生效(但历史日志已无法改变)。


二、场景假设

  • 数据库名:prod_db
  • 表名:orders
  • 第一次误删时间:2026-03-15 10:00:00
  • 最后一次误删时间:2026-03-20 18:00:00
  • 涉及 Binlog 文件:mysql-bin.000010​ ~mysql-bin.000025

三、安装 MyFlash 闪回工具

MyFlash 是美团点评开源的一款轻量级 Binlog 反向解析工具,能够直接将 ROW 格式事件反转,生成新的 Binlog 文件。

# 1. 安装编译依赖(CentOS 示例)yuminstall-ygcc glib2-devel# 2. 下载源码gitclone https://github.com/Meituan-Dianping/MyFlash.gitcdMyFlash# 3. 编译gcc-w`pkg-config--cflags--libsglib-2.0`source/binlogParseGlib.c-oflashback# 4. 验证安装./flashback--help

若出现帮助信息,说明安装成功。


四、定位需要处理的 Binlog 文件与时间范围

如果不确定哪些 Binlog 文件覆盖了误删时间段,可逐一查看文件的起止时间。

查看单个 Binlog 文件的事件时间范围:

mysqlbinlog --start-datetime="2026-03-15 00:00:00"\--stop-datetime="2026-03-20 23:59:59"\/var/lib/mysql/mysql-bin.000010|head-30

输出中会包含类似#2026-03-15 08:30:02的时间戳,据此判断该文件是否包含目标操作。

最终我们确认需要处理的文件列表为:

mysql-bin.000010 mysql-bin.000011 ... mysql-bin.000025

五、使用 MyFlash 生成反向 Binlog(DELETE → INSERT)

核心思路:只反转DELETE​ 操作,将其变成对应的INSERT(在 Binlog 内部体现为 Write_rows 事件),输出到新的闪回 Binlog 文件中。

./flashback\--databaseNames="prod_db"\--tableNames="orders"\--start-datetime="2026-03-15 10:00:00"\--stop-datetime="2026-03-20 18:00:00"\--sqlTypes="DELETE"\--binlogFileNames=/var/lib/mysql/mysql-bin.000010,/var/lib/mysql/mysql-bin.000011,/var/lib/mysql/mysql-bin.000012,/var/lib/mysql/mysql-bin.000013,/var/lib/mysql/mysql-bin.000014,/var/lib/mysql/mysql-bin.000015,/var/lib/mysql/mysql-bin.000016,/var/lib/mysql/mysql-bin.000017,/var/lib/mysql/mysql-bin.000018,/var/lib/mysql/mysql-bin.000019,/var/lib/mysql/mysql-bin.000020,/var/lib/mysql/mysql-bin.000021,/var/lib/mysql/mysql-bin.000022,/var/lib/mysql/mysql-bin.000023,/var/lib/mysql/mysql-bin.000024,/var/lib/mysql/mysql-bin.000025\--outBinlogFileNameBase=rollback

参数详解:

参数含义
--databaseNames要恢复的数据库名,大小写务必与 Binlog 中记录一致
--tableNames要恢复的表名,多个表用逗号分隔
--start-datetime​ /--stop-datetime时间过滤范围,格式YYYY-MM-DD HH:MM:SS
--sqlTypes指定需要反转的 SQL 类型(DELETE​、INSERT​、UPDATE可选)。本例只反转删除操作
--binlogFileNames完整路径的原始 Binlog 文件,用逗号连接
--outBinlogFileNameBase输出文件名的前缀

执行成功后,当前目录会生成类似下面的文件:

rollback.flashback.000001 rollback.flashback.000002

这些文件已经是反转后的 Binlog,格式与原生 Binlog 完全一致。


六、把反向 Binlog 应用到测试环境

我们绝不直接把闪回 Binlog 导入生产库。正确做法是:

  1. 测试库中创建一张与原表结构相同的临时表;
  2. 将反转后的 Binlog 中的 INSERT 操作重定向到临时表;
  3. 验证数据无误后,再以安全方式合并到生产表。

6.1 在测试库创建临时表

-- 先导出生产表的结构(若无则直接从生产获取)CREATETABLEtest_db.orders_restoreLIKEprod_db.orders;

6.2 解析闪回 Binlog 并替换表名后执行

MyFlash 生成的反向 Binlog 中,表名依然是prod_db.orders​。我们需要在解析时把它替换为临时表test_db.orders_restore

# 解析成 SQL 并替换表名,结果保存为 restore.sqlmysqlbinlog --base64-output=decode-rows-vvrollback.flashback.*|\grep-E'^(### INSERT INTO|### @)'|\sed's/^### //'|\sed"s/INSERT INTO \`prod_db\`\.\`orders\`/INSERT INTO \`test_db\`.\`orders_restore\`/g">restore.sql

命令解析:

  • mysqlbinlog --base64-output=decode-rows -vv​:解码 rows 事件,显示详细的伪 SQL(以###开头)。
  • grep​:只保留INSERT INTO​ 行和字段值行(### @)。
  • 第一个sed​:去掉行首的###前缀。
  • 第二个sed:将目标表名替换为临时表名,防止误导入生产表。

提示:如果数据量巨大,也可用mysqlbinlog rollback.flashback.* | mysql -u用户名 -p -h测试库 test_db​ 直接执行,同时配合--rewrite-db='from_db->to_db'来改写库名。但直接用文本 SQL 更直观,方便人工抽查。

6.3 导入数据到临时表

mysql -u用户名-p-h测试库 test_db<restore.sql

等待导入完成,然后检查临时表中的记录数。


七、数据校验

在合并到生产前,务必验证临时表中数据的正确性。

-- 检查记录总数(可对比你估算的误删行数)SELECTCOUNT(*)FROMtest_db.orders_restore;-- 抽查几条数据的内容SELECT*FROMtest_db.orders_restoreLIMIT10;

还可以抽样匹配主键,借助导出的原始备份(如果有)进行比对。


八、安全地将恢复数据合并回生产表

临时表数据验证无误后,用NOT EXISTS​ 的方式将不存在的行插入生产表,避免主键冲突。

-- 只补回那些生产表中已经丢失的行INSERTINTOprod_db.ordersSELECT*FROMtest_db.orders_restoreASt1WHERENOTEXISTS(SELECT1FROMprod_db.ordersASt2WHEREt2.id=t1.id-- 请替换为真实主键列);

为什么不用REPLACE​ 或INSERT IGNORE

  • REPLACE会先删除已有行再插入,可能导致某些中间状态(如关联数据)丢失。
  • INSERT IGNORE会静默忽略主键冲突,但不会更新已有数据,如果误删后又插入过相同主键的新数据,旧数据不会被覆盖,这通常符合预期。
  • NOT EXISTS逻辑最清晰,仅补回真正缺失的行,安全可控。

九、常见问题

Q1:表结构在误删期间发生过变更怎么办?

Binlog 中记录的是误删时刻的表结构信息(列数、列类型)。如果事后表结构增加了字段,直接解析回的 INSERT 会失败。

解决方法

  1. 在测试库中重演出误删时间点的表结构(可利用老备份或手动还原)。
  2. 先将数据恢复到这张旧结构临时表中。
  3. 再通过INSERT INTO 新表(列1,列2...) SELECT 列1,列2... FROM 旧表把数据补回新表。

Q2:恢复出来的中文是乱码?

mysqlbinlog解析时指定正确的字符集:

mysqlbinlog --base64-output=decode-rows-vv--set-charset=utf8mb4 rollback.flashback.*

如果原始 Binlog 是其他字符集,请相应调整。

Q3:DELETE 操作被多次反转,导致重复数据?

如果一条数据被删除多次,MyFlash 会生成多条相同的 INSERT 语句。我们通过NOT EXISTS检查,只会插入第一条,后续语句自动忽略,不会造成问题。

Q4:Binlog 中binlog_row_image​ 是MINIMAL怎么办?

可惜,历史日志无法变更。DELETE 事件会缺失除主键外的其他列。你只能恢复出主键,而失去其他字段的值。这是为何我们极力推荐生产环境设置binlog_row_image = FULL


十、总结

整个恢复流程可用下图概括:

[确定时间范围与日志文件] → [MyFlash 生成反向 Binlog] ↓ [测试库创建临时表] ← [mysqlbinlog 解析并替换表名] ↓ [导入临时表并验证数据] ↓ [通过 NOT EXISTS 安全合并到生产表]
  • MyFlash极大简化了闪回操作,不用费力手写大量反解析脚本。
  • 牢记:“一切操作先上测试环境” 是数据库恢复的黄金法则。
  • 恢复结束后,应立即排查误删根因,同时加强备份与 Binlog 保留策略,考虑部署延时从库来争取应急时间。

希望这份指南能帮助你在关键时刻快速找回宝贵数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询