误删数据之后:一次真实的恢复演练

米阳 2024-12-14 4 12/14

演练比事故便宜。这篇记录一次「假设有人执行了不带 WHERE 的 DELETE」的完整恢复过程,全程在测试库做。

场景设定

凌晨 2 点,有人执行:

DELETE FROM orders;   -- 忘了 WHERE

3 万条订单消失。备份是每天 3 点的全量 mysqldump,binlog 保留 7 天。

恢复思路

全量备份(昨天 3 点) + binlog 回放(昨天 3 点 → 误删前一刻) = 恢复

关键是找到误删的那条 binlog 位置,回放到它之前。

第一步:确认 binlog 开着

SHOW VARIABLES LIKE 'log_bin';        -- 必须是 ON
SHOW VARIABLES LIKE 'binlog_format';  -- 必须是 ROW
SHOW BINARY LOGS;

binlog_format=ROW 很重要,它记录每行数据的变更前后值,能精确恢复;STATEMENT 格式记录 SQL,遇到 NOW()、RAND() 这类函数会不一致。

第二步:找出误删位置

mysqlbinlog --base64-output=decode-rows -v mysql-bin.000123 \
  | grep -B5 -A20 "DELETE FROM orders"

输出里能看到 DELETE FROM orders 所在的 at 位置,以及它是从哪个位置开始的。记下误删语句之前的 position。

更快的办法是按时间过滤:

mysqlbinlog --start-datetime="2024-12-14 01:50:00" \
            --stop-datetime="2024-12-14 02:10:00" \
            mysql-bin.000123 | less

第三步:恢复到临时库

不要直接往生产库灌,先恢复到临时实例验证:

# 1. 恢复昨天的全量
mysql -u root -p temp_restore < backup/wordpress-2024-12-13.sql

# 2. 回放到误删之前
mysqlbinlog --start-position=4 --stop-position=1234567 \
  mysql-bin.000123 | mysql -u root -p temp_restore

第四步:校验

SELECT COUNT(*) FROM temp_restore.orders;
SELECT * FROM temp_restore.orders ORDER BY id DESC LIMIT 10;

和业务方核对最后几条记录,确认数据完整。

第五步:只补回缺的部分

全量覆盖生产库意味着这段时间的新数据也会被回滚。更稳的是只把缺失的行插回去:

-- 找出生产库缺的 id
SELECT o.* FROM temp_restore.orders o
LEFT JOIN prod.orders p ON o.id = p.id
WHERE p.id IS NULL;

-- 用 INSERT ... SELECT 补回

如果误删之后还有更新,情况会更复杂,要按 binlog 逐条重放。这时候通常建议停服、整库恢复、再补增量。

预防

  • 权限:业务账号不给 DELETE,删除走软删(is_deleted 标记)
  • SQL 审核:DELETE/UPDATE 必须带 WHERE,工具层拦截
  • 延迟从库:CHANGE MASTER TO MASTER_DELAY = 3600,误删后一小时内可以从延迟从库拿到原始数据
  • binlog 保留至少 7 天,而且要异机备份——这次演练特意检查过,binlog 原本只留 3 天

结论

恢复能成功的前提是:binlog 开着、备份可用、知道怎么找 position。这三样平时不起眼,出事时决定生死。建议每个管数据库的人都真跑一次这个演练。

- THE END -

米阳

12月14日21:20

最后修改:2024年12月14日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论