# 一次 MySQL DELETE 误操作的数据恢复尝试实录

📅 2026/7/4 3:03:05
# 一次 MySQL DELETE 误操作的数据恢复尝试实录
## 背景2026年1月20日中午有人在生产环境执行了一条 DELETE 清理脚本想删除 xxx 表中属于2026年订单的数据。结果删了 206,689 行。---## 第一步还原操作过程查生产sql日志11:51:12CREATE TABLE xxx_2026 ... 11:51:27INSERT INTO xxx_2026 SELECT * FROM ... (备份完成) 11:53:00DELETE FROM xxx WHERE orderno IN (SELECT orderno FROM tb_order_2026) (执行耗时: 116.287s)**操作逻辑**tb_order.sh 脚本的标准流程是——先 INSERT 到年度备份表再 DELETE 主表。备份已完成DELETE 也执行了。**删了多少**_2026 备份表记录了 206,689 行这基本上就是被 DELETE 掉的行数。但精确数字因为日志被截断始终没能确认。## 第二步发现第二个 DELETE 没执行脚本本意是删两批- 第一批2026-01-01 ~ 2026-01-06 的订单 ✅ 已执行- 第二批2026-01-06 ~ 2026-01-11 的订单 ❌ 未执行查日志发现第二次 DELETE 报了语法错误SQL 语法错误: DELETE FROM ... WHERE orderno IN (...)**原因**Navicat 传参时 SQL 被双引号包住导致语法错误第二次 DELETE 根本没有执行。这意味着 2026-01-06~11 的数据还在主表里没丢。## 第三步停止定时任务防止再次触发crontab 显示每月 15/16/23 号凌晨 3 点会执行 tb_order.sh每月15/16/23号凌晨跑清理脚本0 3 15,16,23 * * /opt/tb_order.sh 0 4 15,16,23 * * /opt/tb_pay_info.sh紧急执行bashsystemctl stop crond暂停定时任务防止23号凌晨脚本再次执行。第四步评估恢复方案方案一binlog 闪回mysqlbinlog --start-datetime2026-01-20 11:50:00 \--stop-datetime2026-01-20 12:00:00 \/var/lib/mysql/mysql-bin.000001 | grep DELETE结果无 binlog。该 数据库为自建的 实例没有开启 binlog无法用 mysqlbinlog 恢复。方案二HBR 全量备份# 查 HBR 备份日志grep 2026-01-19 /usr/local/aegis/hbrclient/logs/backup_job-*.log# 结果# backup_job-14940.log: 2026-01-19 02:02:00 开始全盘备份# backup_job-14940.log: 2026-01-19 03:00:06 全盘备份完成HBR 每天凌晨 2 点有全盘备份包含 /var/lib/mysql。理论上可以从 HBR 恢复一个副本再导出被删数据后插回主表。但 HBR 恢复操作复杂需要申请副本 → 挂载 → 启动临时实例 → 导出数据 → 清理副本。步骤多风险高暂时没有执行。方案三INSERT IGNORE 回滚最终方案_2026 备份表有 206,689 行结构已知id 主键 orderno 索引。回滚 SQLsql-- 分批插回每批1000行INSERT IGNORE INTO xxxSELECT * FROM xxx_2026WHERE id BETWEEN 1 AND 1000;INSERT IGNORE INTO xxxSELECT * FROM xxx_2026WHERE id BETWEEN 1001 AND 2000;-- ... 以此类推INSERT IGNORE 的好处如果 2026-01-06~11 的数据没被删的那批和 _2026 里有主键冲突不会报错直接跳过。但回滚最终没有执行完——因为 SHOW CREATE TABLE 结果没拿到无法精确确认主键列名回滚 SQL 暂停。踩的坑坑一SQL 被双引号包住导致语法错误Navicat 传参习惯会把变量用双引号包住但 MySQL 里字符串应该用单引号双引号是用于表名列名的。这导致第二次 DELETE 失败数据没有按预期删除干净。教训Navicat 传参时确认 SQL 语法不要依赖 GUI 自动加引号。坑二日志里的 COUNT 结果被截断11:51:27 INSERT INTO xxx_2026 SELECT * FROM ...11:51:27 SELECT COUNT(*) FROM ...查询执行了 11.733s日志里能看到语句发出但结果数字被截断。始终不知道精确删了多少行只能靠 _2026 表的行数反推。教训生产执行重要 SQL 前先在测试库跑一遍确认结果。坑三没有 binlog该 RDS 实例未开启 binlog导致无法用 mysqlbinlog 做增量恢复。教训生产数据库务必开启 binlog是数据安全的基本保障HBR 是全盘备份包含 /var/lib/mysql但不包含 binlog 日志文件binlog 是独立的。即使恢复了 HBR 副本也只能恢复到快照点的全量数据无法做增量。教训HBR binlog 双重保障才是完整的生产数据库备份策略。总结DELETE 删了多少约 206,689 行从备份表反推2026-01-01~06 数据已备份到 _2025 表未回滚2026-01-06~11 数据因语法错误未删除仍在主表定时任务已停止 crondbinlog未开启无法 mysqlbinlog 闪回HBR 备份2TB包含 MySQL 全量数据INSERT IGNORE 回滚因主键列名未确认未执行完经验总结1. 生产执行 DELETE 前必做检查清单□ 在测试库跑一遍确认影响行数□ 确认备份已就位INSERT SELECT 完成□ 确认 crontab 已暂停或该时段无定时任务□ 确认 binlog 已开启□ 确认 WHERE 条件无误特别检查引号是单引号□ 预估执行时间设置合适的 max_execution_time2. 备份表命名规范备份表加时间戳后缀xxx_2025 → xxx_20250623_1151这样即使多次操作备份不会覆盖。-- 检查是否开启SHOW VARIABLES LIKE log_bin;SHOW VARIABLES LIKE expire_logs_days;4. 脚本 DELETE 改为 UPDATE 标志位tb_order.sh 逻辑是 INSERT DELETE更安全的做法是-- 不删除只标记删除状态UPDATE xxxSET deleted 1, delete_time NOW()WHERE orderno IN (SELECT orderno FROM tb_order_2025);这样数据不丢失随时可以回滚。相关命令速查# 查 binlog 是否开启mysql -e SHOW VARIABLES LIKE log_bin;# 查 HBR 备份日志grep backup_job /usr/local/aegis/hbrclient/logs/backup_job-*.log | tail -10# 查 crontabcrontab -lcat /etc/cron.d/*# 暂停定时任务systemctl stop crond# 查备份表行数SELECT COUNT(*) FROM xxx_2025;# 分批 INSERT IGNORE 回滚示例INSERT IGNORE INTO xxxSELECT * FROM xxx_2025WHERE id BETWEEN 1 AND 1000;