必须确认binlog_format=row且binlog_row_image=full,否则无法获取旧值实现精准回滚;需用my2sql解析binlog生成带where条件的反向sql,并人工验证条件有效性,最后在从库或临时实例分批执行。

确认 binlog_format 是 ROW 且 binlog_row_image 是 FULL
这是整个回滚能成立的前提。如果 binlog_format 是 STATEMENT 或 MIXED,日志里只有 UPDATE t SET status = 0 WHERE user_id = 123456 这种语句,没有旧值,根本没法还原每行原始数据。binlog_row_image = FULL 同样关键:它保证每一行变更前后的所有字段值都被记录下来,否则像 @3=NULL 这种字段缺失,生成的反向 SQL 就会漏数据。
登录 MySQL 执行这三条命令,缺一不可:
-
SHOW VARIABLES LIKE 'binlog_format';—— 必须返回ROW -
SHOW VARIABLES LIKE 'binlog_row_image';—— 必须返回FULL -
SHOW BINARY LOGS;—— 确认你要查的日志文件还在,没被expire_logs_days清掉
用 my2sql 解析 binlog 并生成反向 SQL(推荐 MySQL 8.0)
binlog2sql 不支持 MySQL 8.0,MyFlash 只能输出 binlog 文件不能直接生成可读 SQL,而 my2sql 支持 5.7 和 8.0、离线解析、输出带注释的回滚语句,是当前最稳的选择。它不连数据库,直接读磁盘上的 binlog 文件,避免对线上库造成压力。
安装后执行类似下面的命令(注意替换参数):
my2sql -user admin -password 'secret' \ -host 127.0.0.1 -port 3306 \ -database mydb -table order_info \ -basetime "2026-10-13 14:22:00" \ -endtime "2026-10-13 14:23:30" \ -work-type flashback \ -output-dir ./output/
关键点:
-
-basetime和-endtime要尽量窄,只包住误操作那几十秒,减少噪音 -
-work-type flashback表示生成反向 SQL(比如把 UPDATE 变成用旧值覆盖回去) - 输出目录里会生成
rollback.sql,内容是带WHERE条件的精确 UPDATE,不是全表扫
手动验证 rollback.sql 里的 WHERE 条件是否真能定位到那批行
很多人直接执行 rollback.sql,结果发现部分行没改回来——问题出在 WHERE 条件上。ROW 格式 binlog 记录的是「变更前的主键或唯一键值」,但如果原表没主键、或者你误更新时用了非唯一条件(比如 WHERE user_id = 123456),my2sql 生成的 WHERE 就可能依赖 @1, @2 这类位置变量,而这些变量在多列联合主键或无主键表里容易错位。
打开 rollback.sql,找几条典型语句,例如:
UPDATE `mydb`.`order_info` SET `status` = 1, `update_time` = '2026-10-12 10:05:22' WHERE `id` = 999 AND `user_id` = 123456;
拿其中一条的 WHERE 去生产库查一下:SELECT COUNT(*) FROM order_info WHERE id = 999 AND user_id = 123456;。如果返回 0,说明这条回滚语句实际不会生效,得人工补逻辑或换条件。
执行前必须在从库或临时实例上跑一遍
千万行级别的反向 UPDATE 不是“执行完就完事”。它会产生大量 redo log、binlog,还可能触发锁等待甚至死锁。更危险的是:如果你用的是 GTID 模式,直接在主库执行回滚 SQL,会导致 GTID 集合膨胀,后续主从同步出问题。
稳妥做法:
- 先停掉主库写入(
SET GLOBAL super_read_only = ON;) - 把
rollback.sql导入一个同结构的从库(或临时搭建的测试实例) - 观察执行耗时、慢日志、是否有锁超时
- 抽样比对几条关键行,确认状态真的回到了误操作前
真正上线时,建议分批次执行,比如按 id 分段加 LIMIT,避免单次事务过大。别迷信“一键回滚”,最容易被忽略的是:回滚本身也是 DML,它同样可能出错。











