快速修复
# 1. 定位误删事务的起点(记下 DELETE 那行的 Pos)
SHOW BINLOG EVENTS IN "mysql-bin.000001";
# 2. 导出误删前所有写入(stop-position = DELETE 起点)
mariadb-binlog --start-position=<INSERT起点> --stop-position=<DELETE起点> /var/log/mysql/mysql-bin.000001 > flashback.sql
# 3. 恢复到新库(rewrite-db 换库名,先验证再上生产)
mariadb-binlog --start-position=<INSERT起点> --stop-position=<DELETE起点> --rewrite-db="坏库->恢复库" /var/log/mysql/mysql-bin.000001 | mysql
你会遇到什么
手滑执行了 DELETE FROM <表> 没带 WHERE(或 WHERE 条件写错),数据瞬间全没了;表里可能还插着生产数据,不能直接回滚,也不知道从哪恢复。误删后最忌讳的是继续写入或重启数据库——binlog 里可能还有救,乱操作会让数据彻底丢失。
本文路径:快速修复(30 秒一段命令)→ 一步步排查(可跳读)→ 原理与变体(症状/原因/证据)。赶时间直接跳到「快速修复」。
为什么会这样
MySQL/MariaDB 开启了 binlog(log_bin=ON,binlog_format=ROW)时,每次 DML 都会按事务记录到 binlog 文件。误删的 DELETE 也是一个普通事务,它之前的所有写入(INSERT/UPDATE)都还在 binlog 里。只要在误删事务的起始 position 处截断重放,就能把数据恢复到误删前一刻。
binlog 是 MySQL 的顺序追加日志:所有 DML 按提交顺序写成事件流,每个事务在文件里有确定的起始 position。误删的 DELETE 只是这个事件流里的一个普通事务——它之前的所有写入(INSERT/UPDATE)都还在。flashback 的本质是「把 binlog 当成可回放的时间线」:定位误删事务的起点,重放它之前的事件,数据就回到误删前一刻。
可行性前提:binlog_format=ROW(row 格式才含完整前后镜像,statement 格式无法精确重建);log_bin=ON(没开就没有时间线)。
确定性推论:能不能闪回先查两个开关(SHOW VARIABLES LIKE 'binlog_format' / log_bin),ROW + ON 才可行;恢复要在独立实例上重放(binlog 重放会执行写操作,别在生产库直接跑);定位 position 靠 mysqlbinlog 按时间/关键字找误删语句;
判断路径:报错「无 binlog」= 环境没开,闪回不可用,只能走备份。
一步步来
1. 紧急止损(第一步最重要) — 必做
FLUSH TABLES WITH READ LOCK; # 锁库禁止写入
cp -r /var/log/mysql/ /backup/ # 备份 binlog(防止被清理)
绝对不要重启 MySQL——内存里未落盘的变更可能丢失;也不要继续写数据,新的写入会污染恢复点。
2. 确认 binlog 开启并列出日志 — 必做
SHOW VARIABLES LIKE "log_bin"; # 必须 ON,否则只能靠备份
SHOW BINARY LOGS; # 找到最新的 binlog 文件
3. 用 SHOW BINLOG EVENTS 定位误删事务 — 排查
SHOW BINLOG EVENTS IN "mysql-bin.000001";
关键看两行:INSERT 事务的 Annotate_rows(数据写入)和 DELETE 事务的 Annotate_rows(误删)。DELETE 事务的 Pos 列就是恢复截止点。
4. 闪回导出 + 重放(先恢复到新库验证) — 必做
# 导出误删前区间(含 INSERT 不含 DELETE)
mariadb-binlog --start-position=<INSERT事务Pos> --stop-position=<DELETE事务Pos> /var/log/mysql/mysql-bin.000001 > flashback.sql
# 建恢复库 + 重放(rewrite-db 避免覆盖坏库)
mariadb-binlog --start-position=<INSERT事务Pos> --stop-position=<DELETE事务Pos> --rewrite-db="原库->恢复库" /var/log/mysql/mysql-bin.000001 | mysql
# 验证行数与内容一致后,再决定切回生产
SELECT COUNT(*) FROM 恢复库.表;
怎么确认修好了
SELECT * FROM 恢复库.表;
行数与误删前一致(本机实测:5 行全删后恢复 5 行,内容/顺序完全一致),且闪回 SQL 里 grep DELETE 为 0(没有把误删动作重放进去)。
其它环境的表现
| 环境 | 说明 |
|---|---|
| MySQL 8.x | 命令为 mysqlbinlog,机制相同;binlog 默认开启 |
| MariaDB 11.8 | 命令为 mariadb-binlog(mysqlbinlog 为兼容别名);本机实测环境 |
| DROP TABLE/库 | 需要全量备份 + 备份点之后的 binlog 重放,闪回只覆盖 DML |
| binlog 被清理 | 只能靠物理备份,覆盖部分丢失 |
| 未实测项 | DROP 场景全量恢复、binlog2sql 反向 SQL、超长 binlog 性能 |
症状
手滑 DELETE 不带 WHERE 全表被删(或 UPDATE 误改全表);数据在 binlog 开启的库中仍有痕迹;重启数据库或继续写入会丢失恢复机会。
原因
binlog(ROW 格式)按事务记录全部 DML;误删 DELETE 是独立事务,其起始 position 之前的写入仍完整保存在 binlog。用 mariadb-binlog/mysqlbinlog --start-position --stop-position 精确导出误删前区间并重放,即可恢复到误删前一刻;--rewrite-db 可先恢复到新库验证。
解法
- 止损:
FLUSH TABLES WITH READ LOCK+ 备份 binlog,不重启、不写入; SHOW BINARY LOGS+SHOW BINLOG EVENTS定位误删事务起点 position;mariadb-binlog --start-position=INSERT起点 --stop-position=DELETE起点导出闪回 SQL(只含误删前写入);--rewrite-db重放到恢复库验证,确认后切回生产。
验证证据
- 本地实测(2026-08-30,Ubuntu WSL + MariaDB 11.8.6):开启 binlog(ROW)→ 建表插 5 行 →
DELETE FROM orders全删 →SHOW BINLOG EVENTS定位(INSERT 事务 869~1335,DELETE 事务 1335 起)→--start-position=869 --stop-position=1335闪回导出(grep DELETE = 0)→--rewrite-db重放到恢复库 → 5 行 100% 恢复,内容顺序一致。 - 实测复现日志(2026-08-30)
适用工具 (1)
本问题方案已挂到下列工具的方案页——从工具页也能回到这里,双向可达。
mysql · 外部工具来源与证据
← 构建与部署 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡