能恢复,但必须立刻停写、确认binlog状态且误删已提交;若仍在事务中可直接rollback秒级回滚,否则需验证log_bin=on、binlog_format=row、日志未过期,并通过mysqlbinlog定位事务起始position截断回放。

能恢复,但必须立刻停写、确认 binlog 状态、且误删已提交——如果还在事务里,直接 ROLLBACK 就行,根本不用走日志流程。
确认是否还在事务中(最简单路径)
很多人删完就慌,其实第一件事不是翻 binlog,而是查当前有没有未提交的事务。InnoDB 下,DELETE 如果没执行 COMMIT,数据还在 undo log 里,回滚是秒级的。
- 执行
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_started 查看是否有活跃的长事务,重点关注 <code>trx_query字段是否包含刚执行的DELETE - 如果找到对应事务,记下
trx_id,直接运行ROLLBACK;(注意:必须在同一个会话里执行) - 如果已执行
COMMIT或连接已断开,这条路径失效,跳到下一节
检查 binlog 是否可用(恢复前提)
MySQL 恢复误删的核心依赖不是“有没有日志”,而是 log_bin = ON 且 binlog_format = ROW。缺一不可。STATEMENT 模式下,DELETE FROM t 只记语句本身,无法还原被删的行内容。
- 运行
SHOW VARIABLES LIKE 'log_bin';—— 若返回OFF,放弃闪回,只能靠备份 - 运行
SHOW VARIABLES LIKE 'binlog_format';—— 若不是ROW,mysqlbinlog解析出的只是空壳,无法提取原始值 - 检查 binlog 文件是否还存在:
SHOW MASTER LOGS;看最新文件名,再确认磁盘上该文件是否被PURGE或清理(尤其注意expire_logs_days设置)
用 mysqlbinlog 定位并截断回放(实操关键)
mysqlbinlog 不是“闪回工具”,它只输出可读日志,你得自己控制起点和终点。重点不是找到 DELETE 那一行,而是找到它所属事务的 BEGIN 位置(# at XXXXX),然后用 --stop-position 停在事务开始前。
- 先粗筛:
mysqlbinlog --base64-output=DECODE-ROWS -v /var/lib/mysql/binlog.000012 | grep -A 3 -B 3 "DELETE FROM \`db\`.\`table\`" - 从输出里找紧邻的上一个
# at行,比如# at 8900,这就是事务起始点;别选DELETE语句所在位置(如# at 9200),否则会漏掉前面的 INSERT/UPDATE - 回放命令必须加
--stop-position=8900,而不是--start-position:mysqlbinlog --stop-position=8900 /var/lib/mysql/binlog.000012 | mysql -u root -p db - 严禁在生产库直接执行!先导入到临时库验证结果,再同步回原表(避免主键冲突或重复插入)
为什么不能直接反向生成 INSERT?
很多工具(如 binlog2sql)标榜“一键闪回”,但实际容易翻车。ROW 格式日志里的 Delete_rows_event 只存了被删行的字段值(@1=100, @2='abc'),不带字段名、类型、NULL 标志位。一旦表结构近期改过(比如新增列、调整顺序、修改默认值),自动生成的 INSERT 很可能字段错位或类型报错。
- 真正安全的做法是:用
mysqlbinlog提取误删前最近一次对该表的Write_rows_event(即全量插入/更新),从中复制字段顺序和类型逻辑 - 若表无历史写入事件,必须手动对照
SHOW CREATE TABLE结果,逐字段映射@n到列名 - 遇到
@3=NULL或@4=(空字符串)要区分处理,MySQL 对 NULL 和空字符串的约束行为完全不同
最常被忽略的一点:恢复操作本身也是写入。如果你在主库上边解析边执行,新产生的 binlog 可能覆盖旧日志,导致二次丢失。务必先锁表或停应用,再操作。











