RSS
菜单
全部文章快讯开发科技深度热点

第 59 章 备份恢复踩坑

内容摘要

备份是 DBA 的底线——平时没人看,出事时它是唯一的救命稻草。但"有备份"和"能恢复"之间差着十万八千里:没做恢复演练、没留 binlog 做定点恢复、备份和数据库放同一块盘、误删后直接全量覆盖。本章梳理备份恢复中的 11 个高频踩坑。示例基于 MySQL 8.0(本机无服务,命令按官方语法书写)。读完你能搭一套真能救命的备份方案。

📖 本篇简介:备份是 DBA 的底线——平时没人看,出事时它是唯一的救命稻草。但"有备份"和"能恢复"之间差着十万八千里:没做恢复演练、没留 binlog 做定点恢复、备份和数据库放同一块盘、误删后直接全量覆盖。本章梳理备份恢复中的 11 个高频踩坑。示例基于 MySQL 8.0(本机无服务,命令按官方语法书写)。读完你能搭一套真能救命的备份方案。

目录

  • 坑 1:只备全量没留 binlog 做不了定点恢复
  • 坑 2:备份从来没做过恢复演练
  • 坑 3:备份文件和数据库放同一块盘
  • 坑 4:binlog 没开或过期太快恢复不了
  • 坑 5:误 DELETE 后直接全量覆盖恢复
  • 坑 6:从库备份其实延迟很大
  • 坑 7:xtrabackup 没 prepare 就还原
  • 坑 8:mysqldump 备份大表没锁一致性
  • 坑 9:备份加密了但恢复时忘记密码
  • 坑 10:以为物理备份不用管 binlog
  • 坑 11:备份任务失败没人发现
  • 速查表:备份恢复速记
  • 注意事项:备份方案 Checklist
  • 自测:你会做备份恢复吗

坑 1:只备全量没留 binlog 做不了定点恢复

场景 / 错误示范:

# 每天凌晨做一次全量 mysqldump,以为这样就够了
mysqldump -uroot -p --single-transaction your_db > full.sql
# 上午 10 点误删了表,只能恢复到凌晨 4 点的全量——4 点到 10 点的数据全丢

为什么是坑:全量备份是"某一时刻的快照",它恢复不了备份之后到故障点之间的增量数据。要做"误删恢复到删之前一刻"(point-in-time recovery),必须靠 binlog 重放全量之后的所有修改。只有全量没有 binlog,等于只能回到昨天。

正确做法:

-- 前提:binlog 必须开着,且保留足够长
SHOW VARIABLES LIKE 'log_bin';            -- ON
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';  -- 保留天数

-- 定点恢复:先还原全量,再用 mysqlbinlog 重放到误删前的位置
-- mysqlbinlog --start-datetime="2026-10-07 04:00:00" \
--            --stop-datetime="2026-10-07 10:00:00" binlog.000123 | mysql
-- 查看 binlog 列表,确认保留情况
SHOW BINARY LOGS;                 -- 列出所有 binlog 文件和大小
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';
-- 应急时先记录当前 binlog 位置,误删后从这里开始裁剪重放
SHOW MASTER STATUS;               -- 记下 File 和 Position

加分点:备份策略的黄金组合——定期全量 + 持续保留 binlog。全量是基线,binlog 是增量,两者配合才能定点恢复。

坑 2:备份从来没做过恢复演练

场景 / 错误示范:

# 备份脚本每天跑,从没出过告警,以为备份很可靠
# 真出事了去恢复,才发现备份文件损坏/不全/版本不兼容,恢复不出来

为什么是坑:"备份成功"不等于"备份可用"。备份文件可能因为磁盘错误、版本不兼容、参数遗漏而损坏,只有真正还原一次才知道。从没演练过的备份,出事时往往变成"没有备份"。

正确做法:

# 定期(如每周)把最近的备份还原到一台测试机,验证:
# 1. 能不能还原成功;2. 表和数据量对不对;3. 关键业务 SQL 能不能跑
# 把恢复耗时也记录下来,真出事时心里有数
# 恢复演练建议节奏:
# - 每周:把最近一次全量备份还原到测试机,验证可还原、数据量对、关键 SQL 能跑
# - 每季度:做一次完整的"误删 PITR 演练",把 binlog 重放也走一遍
# - 每次演练记录:耗时、失败点、修复步骤,沉淀成 runbook

加分点:面试金句——"不能恢复的备份等于没有备份"。恢复演练是备份体系里最容易被省掉、却最关键的一环。

坑 3:备份文件和数据库放同一块盘

场景 / 错误示范:

# 备份文件就放在数据库数据目录的旁边
# /var/lib/mysql/backup/full.sql
# 这块盘坏了,数据库和备份一起没了

为什么是坑:备份的意义是"原始数据出事时还有副本"。备份和数据库放同一块盘、同一台机,盘坏/机毁时两边一起丢,备份就白做了。这是备份最基本的灾难隔离原则。

正确做法:

# 备份要异地/异机:先写本机临时盘,再传到对象存储或另一台机
# xtrabackup --target-dir=/local/tmp
# 然后 scp / ossutil cp 到异地存储
# 3-2-1 原则:至少 3 份副本、2 种介质、1 份异地
# 3-2-1 原则落地:
# 3 份:原数据 + 本地备份 + 异地备份
# 2 种介质:磁盘 + 对象存储(OSS/S3)
# 1 份异地:异地机房/云对象存储,本地机房整体挂了也能救
# 备份脚本结尾:上传异地后再删本地临时文件

加分点:3-2-1 备份原则是行业共识。备份至少要有一份在物理上远离数据库的地方。

坑 4:binlog 没开或过期太快恢复不了

场景 / 错误示范:

-- 库里 log_bin 是 OFF,或 expire_logs 设置成 1 天
SHOW VARIABLES LIKE 'log_bin';   -- OFF
-- 想定点恢复,发现根本没有 binlog 可用

为什么是坑:binlog 是增量恢复的唯一依据。没开 binlog,全量之间的增量就救不回来;binlog 过期太快(只留 1 天),三天前的全量想恢复到昨天,中间两天的 binlog 早被清了。

正确做法:

-- 生产库必须开 binlog,且保留足够久(如 7 天以上,配合备份周期)
SET GLOBAL binlog_expire_logs_seconds = 604800;   -- 7 天
-- my.cnf 里:
-- server_id=1
-- log_bin=/var/lib/mysql/binlog
-- binlog_format=ROW

加分点:binlog_format 推荐 ROW,恢复时能精确重放每行变更,也便于主从一致。

坑 5:误 DELETE 后直接全量覆盖恢复

场景 / 错误示范:

-- 上午误 DELETE 了 1 万行,开发者直接把凌晨的全量备份恢复上去
-- 结果:凌晨到现在 6 小时的所有新数据,一起被覆盖没了

为什么是坑:误删是"局部时间点的错误操作",正确做法是"恢复全量 + 重放 binlog 到误删前一刻",只丢掉误删那一下。直接全量覆盖,等于把备份后所有正常业务数据也回退了,二次事故。

正确做法:

# 正确流程:
# 1. 先记录误删操作在 binlog 里的位置/时间点
# 2. 还原最近一次全量备份
# 3. 用 mysqlbinlog 重放 binlog,重放到"误删之前"为止(--stop-datetime 卡到误删前)
# 4. 误删那条 DELETE 之后的 binlog 也别漏,继续重放
# 这样只丢误删那一下,6 小时正常数据都在
-- 用 mysqlbinlog 先定位误删那条语句在哪个 binlog、什么位置
mysqlbinlog binlog.000456 | grep -n "DELETE FROM important_table"
-- 找到误删语句的起止位置(如 12345 到 12400)
-- 重放时跳过这段:先重放到 12345,再从 12400 继续
-- mysqlbinlog --stop-position=12345 binlog.000456 | mysql
-- mysqlbinlog --start-position=12400 binlog.000456 | mysql

加分点:误删恢复的核心是"用 binlog 精确跳过错误操作",不是粗暴全量覆盖。先定位误删在 binlog 的位置,再做时间点裁剪。

坑 6:从库备份其实延迟很大

场景 / 错误示范:

# 为了不影响主库,在从库上做备份
mysqldump --single-transaction --host=从库 ... > full.sql
# 但从库延迟了 1 小时!备份出来的数据是 1 小时前的,主从切换时才发现数据旧

为什么是坑:在从库备份能减轻主库压力,但前提是从库数据是新的。如果从库延迟严重,备份出来的就是过期数据,恢复时以为是最新,实际差了一截。

正确做法:

-- 备份前先确认从库延迟
SHOW REPLICA STATUS\G    -- 看 Seconds_Behind_Source / Seconds_Behind_Master
-- 延迟为 0 或可接受时才开始备份
-- 延迟大就等追平,或干脆在主库用 --single-transaction 不锁表备
-- 8.0 查看从库复制状态(老命令 SHOW SLAVE STATUS 仍兼容)
SHOW REPLICA STATUS\G
-- 重点看两行:
-- Replica_IO_Running: Yes
-- Replica_SQL_Running: Yes
-- Seconds_Behind_Source: 0     -- 延迟秒数,NULL 表示没在跑
-- 两个 Yes 且延迟 0,才适合在它上面做一致性备份

加分点:从库备份前必查复制延迟。Seconds_Behind_Master 不为 0,备份的"新鲜度"就要打问号。

坑 7:xtrabackup 没 prepare 就还原

场景 / 错误示范:

# 用 xtrabackup 备了份,直接把备份文件拷回数据目录启动
xtrabackup --copy-back
# 报错:备份里还有未提交事务没回滚,数据不一致起不来

为什么是坑:xtrabackup 备份时数据库还在跑,备份出来的文件是"不一致"的(有未完成事务)。必须先 --prepare(apply log)把已提交事务前滚、未提交回滚,得到一致的数据,才能用来还原。

正确做法:

# 两步:先 prepare,再 copy-back
xtrabackup --prepare --target-dir=/backup       -- 1. 预处理成一致状态
xtrabackup --copy-back --target-dir=/backup    -- 2. 还原到数据目录
# 跳过 prepare 直接还原,数据库起不来

加分点:物理备份的恢复必须 prepare。这一步不能省,省了就是数据不一致事故。

坑 8:mysqldump 备份大表没锁一致性

场景 / 错误示范:

# 备份大库,没加 --single-transaction
mysqldump -uroot -p big_db > full.sql
# 备份期间业务还在写,导出来的数据内部不一致(A 表是新的、B 表是旧的)

为什么是坑:备份期间业务在写,如果没有一致性保证,导出来的各表数据可能不在同一个时间点——恢复出来逻辑就错了(外键对不上、统计错位)。

正确做法:

# InnoDB 用 --single-transaction 拿一致性快照
mysqldump -uroot -p --single-transaction --quick big_db > full.sql
# 备份开始时打快照,后续写不影响这个快照,导出的各表时间点一致
# 一致性备份的检查点:--single-transaction 开始时记录的 binlog 位点
# mysqldump 输出开头会有:
# CHANGE MASTER TO MASTER_LOG_FILE='binlog.000123', MASTER_LOG_POS=456;
# 这个位点就是备份的一致性点,做 PITR 时从这里开始重放

加分点:一致性备份的本质是"所有表在同一个快照点"。--single-transaction 靠 MVCC 实现这一点。

坑 9:备份加密了但恢复时忘记密码

场景 / 错误示范:

# 合规要求备份加密,做了加密备份
# 真要恢复时,加密密码散落在某个人的密码本里,找不到了

为什么是坑:加密是安全要求,但加密的密钥管理不好,等于把备份锁起来自己也打不开。这是安全和可用性的平衡问题。

正确做法:

# 加密密钥要进公司统一的密钥管理系统(KMS/密码保险库)
# 不要只存在某个人的电脑/脑子里
# 恢复演练时连"解密"一起演练,确认密钥真的能用

加分点:加密备份必须配套"密钥可追溯"。演练时把解密步骤走一遍,别等到恢复当天才发现密码丢了。

坑 10:以为物理备份不用管 binlog

场景 / 错误示范:

# 用 xtrabackup 做了物理全量,以为万事大吉,binlog 开始清理
# 想从 3 天前的物理备份恢复到昨天,发现中间 binlog 被清了,恢复不了那么远

为什么是坑:物理备份也是"某一时刻的全量快照",和逻辑全量一样,恢复它之后到现在的增量仍然要靠 binlog。以为物理备份"自带增量"是错的。

正确做法:

# 物理全量 + binlog,组合做定点恢复
# 1. xtrabackup 还原到 3 天前;2. mysqlbinlog 重放这 3 天的 binlog 到故障点
# 物理备份不替代 binlog,binlog 保留策略照样要做好
# 物理备份 + binlog 恢复流程:
# 1. xtrabackup --prepare 后 --copy-back 还原到数据目录
# 2. 记录备份时的 binlog 位点(xtrabackup_binlog_info 文件里有)
# 3. mysqlbinlog 从该位点重放到故障前一刻
# 缺了第 3 步,就只能恢复到备份那一刻,之后的增量全丢

加分点:无论逻辑还是物理全量,都是基线;增量永远靠 binlog。这个认知要统一。

坑 11:备份任务失败没人发现

场景 / 错误示范:

# crontab 里的备份脚本,某天磁盘满了,备份写失败
# 没有任何告警,连续失败一周,出事时才发现最近一周都没备份

为什么是坑:备份是"沉默的任务"——平时跑成功没人注意,一旦失败如果没告警,就悄悄断了。等到出事才发现备份链早就断了,最绝望。

正确做法:

# 备份脚本结束后必须上报成功/失败
# 成功:写一条监控打点;失败:立即告警到值班群
# 监控系统加一条"最近一次备份距今超过 26 小时"的告警
# 没新备份 = 告警,比"备份失败"本身更早暴露问题
# 备份脚本结尾示例:成功打打点,失败告警
# ... 备份命令 ...
if [ $? -eq 0 ]; then
    echo "backup ok at $(date)" >> /backup/backup.log
    # 上报监控成功打点
else
    # 发告警到值班群(webhook)
    curl -X POST "$ALERT_WEBHOOK" -d "备份失败: $(hostname) $(date)"
fi

加分点:备份监控的核心指标是"最近成功备份距今多久",超阈值就告警,而不是等备份任务本身报错。

速查表:备份恢复速记

| 坑点 | 错误做法 | 正确做法 |

|---|---|---|

| 恢复能力 | 只全量 | 全量+binlog 定点恢复 |

| 可用性 | 从不演练 | 定期还原测试 |

| 存放位置 | 和库同盘 | 异地异机 3-2-1 |

| binlog | 没开/过期快 | 开着,保留≥备份周期 |

| 误删 | 全量覆盖 | 全量+binlog 跳过错误点 |

| 从库备份 | 不看延迟 | 先查 Seconds_Behind |

| xtrabackup | 直接 copy-back | 先 prepare |

| 一致性 | 不锁快照 | --single-transaction |

| 加密 | 密码只在个人手里 | 进 KMS,演练解密 |

| 物理备份 | 以为自带增量 | 仍靠 binlog |

| 失败告警 | 静默失败 | 最近备份超时告警 |

注意事项:备份方案 Checklist

  • [ ] 全量 + binlog 都有吗?能定点恢复吗?
  • [ ] 做过恢复演练吗?恢复耗时心里有数吗?
  • [ ] 备份异地异机了吗?
  • [ ] binlog 开着且保留够久吗?
  • [ ] 误删恢复知道怎么跳过错误操作吗?
  • [ ] 从库备份前查过延迟吗?
  • [ ] xtrabackup 备份会 prepare 吗?
  • [ ] 备份失败/超时配了告警吗?
  • [ ] 加密密钥进 KMS 了吗?

一句话:备份的 KPI 不是"备了",是"真能在需要时恢复出来"。演练、异地、binlog、告警,缺一不可。

自测:你会做备份恢复吗

题 1:为什么只有全量备份,误删后还是会丢大量数据?

<details>

<summary>参考答案</summary>

全量是某时刻快照,恢复不了备份到故障点的增量。要配 binlog 重放才能定点恢复,只全量只能回到备份那一刻。

</details>

题 2:为什么备份一定要做恢复演练?

<details>

<summary>参考答案</summary>

"备份成功"不等于"备份可用",可能损坏/版本不兼容。只有真还原一次才知道能不能用。不能恢复的备份等于没有备份。

</details>

题 3:备份文件和数据库放同一块盘,为什么不行?

<details>

<summary>参考答案</summary>

盘坏时数据库和备份一起丢。备份要异地异机,遵循 3-2-1:3 份副本、2 种介质、1 份异地。

</details>

题 4:误 DELETE 后为什么不能直接全量覆盖恢复?

<details>

<summary>参考答案</summary>

会把备份后所有正常业务数据一起回退。正确做法是还原全量后,用 binlog 重放到误删前一刻、跳过误删操作,只丢那一下。

</details>

题 5:在从库做备份,备份前要先确认什么?

<details>

<summary>参考答案</summary>

从库复制延迟(Seconds_Behind_Master)。延迟大时备份出来是过期数据,要等追平或在主库备。

</details>

题 6:xtrabackup 备份后直接拷回数据目录为什么起不来?

<details>

<summary>参考答案</summary>

备份是不一致状态,必须先 --prepare 前滚已提交事务、回滚未提交事务,得到一致数据才能 --copy-back。

</details>

题 7:mysqldump 备份大表怎么保证导出的数据内部一致?

<details>

<summary>参考答案</summary>

加 --single-transaction,备份开始时打一致性 MVCC 快照,各表在同一时间点,导出后业务写不影响快照。

</details>

题 8:binlog 的保留策略和备份周期什么关系?

<details>

<summary>参考答案</summary>

binlog 保留要覆盖全量备份周期。比如每周全量,binlog 至少留 7 天以上,才能从任意一次全量 + binlog 恢复到任何时刻。

</details>

题 9:怎么最早发现"备份已经断了好几天"?

<details>

<summary>参考答案</summary>

监控"最近一次成功备份距今多久",超阈值(如 26 小时)就告警。比等备份脚本本身报错更早暴露问题。

</details>

题 10:物理备份(xtrabackup)能代替 binlog 吗?

<details>

<summary>参考答案</summary>

不能。物理备份也是某时刻全量快照,它之后的增量仍要靠 binlog 重放。无论逻辑还是物理,基线+binlog 的组合不变。

</details>

题 11:应急恢复时,第一步为什么要先 SHOW MASTER STATUS?

<details>

<summary>参考答案</summary>

先记录当前 binlog 文件和位置,作为"恢复到这里为止"的基准点。误删后才能基于这个位置裁剪重放,避免恢复时又覆盖掉新数据。

</details>

下一篇预告:第 60 章 主从复制踩坑

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 59 章 备份恢复踩坑》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。