2.3 备份回档与数据迁移:误删整库后我靠它活下来

这是全书我最想讲的一节,因为备份回档真的救过我的命。如果你从这篇只带走一句话,那就记这句:上线前一定确认自动备份已开启、保留天数够用,并且提前想清楚出事之后第一步要做什么——而不是出了事才第一次研究手册。备份这事平时毫无存在感,出事时就是唯一的底牌。

一、事故:一条没带条件的删除语句

那是个周六下午,我在清理一批测试数据。本来要执行的语句是带条件的,只删某个测试账号的记录。但那台数据库终端同时开着两个会话,我切错了会话,一条删除语句直接拍了下去——没有条件,整张用户表瞬间清空。

三秒后我反应过来,后背一阵冷汗。社区刚起步,用户数据虽然不多,但每一位都是真实注册的人,关系着我的全部口碑。我的第一反应是:别乱动。绝对不再往库里写任何东西,怕破坏可用于恢复的日志链;随即将应用切到维护页,让全站挂出只读牌子,然后深呼吸,开始按备份回档的思路操作。这一步要是换作自建时期的我,多半已经先重启服务、再到处翻脚本瞎试了——服务器和服务层面那些手忙脚乱的教训,我后来整理进了1Panel 常见问题,这次总算是没再重演。

冷静下来复盘根因,事故源于三点:终端多会话切换没核对、删除语句没先 SELECT 确认影响范围、操作时心里还在想别的事。三点叠加,一条清空语句就发射出去了。这也说明一个常被忽视的事实:大多数数据事故不是黑客攻击,而是管理员自己的手滑——防系统性的容错,比防外敌更重要。如果当时我养成习惯,执行前先把语句复制到查询窗口里 SELECT 一遍影响范围,这次事故压根不会发生。

事故发生的头十分钟是最容易毁掉恢复机会的十分钟。人在惊慌之下会做很多蠢事:反复刷新页面看数据回来没有、随手敲一条恢复命令试试、甚至重启实例碰运气——每一下都可能破坏日志链或覆盖现场。我当时的处理顺序事后看是做对了的:先停写、再挂维护页、再确认备份机制、最后才动手恢复。这套顺序建议你也提前在脑子里演练一遍,变成肌肉记忆。

整个事故我事后理过一条时间线,当反面教材收藏了:

时间 发生了什么 关键动作
15:02 切错会话,清空语句执行 事故发生
15:05 反应过来,停止一切写入 最关键的五分钟
15:10 应用切维护页,全站只读挂牌 保护现场
15:20 控制台确认备份与日志覆盖事故时段 确认数据可救
15:40 发起克隆回档,时间点定在 15:01 回到事故前一分钟
16:15 新实例验收通过,应用切流量 恢复完成

从清空到完全恢复约一个多小时,其中大半时间花在等克隆和逐项验证上。要是在自建环境,光导备份、起临时实例、追日志,一下午都未必够,中间还得祈祷备份文件没坏。

处理顺序建议你也提前在脑子里演练一遍,变成肌肉记忆。一个成熟的容错体系里,备份机制是底牌,定期演练是熟练度——两者缺一不可。

二、先懂备份机制,才知道能回到哪里

云数据库 MySQL 的备份由两类组成,出事前必须分清这两类的区别:数据备份决定你能回到哪一天,日志备份决定你能回到哪一分哪一秒。两者叠加起来,才构成完整的恢复能力:

备份类型 内容 保留方式
数据备份 全量数据快照,按天自动生成 保留天数可配置
日志备份 Binlog,记录所有写操作 保留天数与数据备份联动

保留天数在实例的备份设置里调整,具体可选范围以云数据库 MySQL 官方文档为准,不同实例版本和规格支持的取值可能略有差别,配置时留意页面上的提示。我的策略如下,供参考:

场景 建议保留天数 理由
测试或练手库 默认即可 反正是测试数据,够回滚误操作就行
生产业务库 尽量往长了配 误操作可能几天后才被发现
有合规要求 按要求执行 该留多久就留多久

备份的存储成本其实很小,但关键时刻就是救命的底牌。回档能回到哪个时间点,完全取决于全量备份加日志覆盖的范围,保留天数本质上就是这条退路的长度。配长一点,是给粗心和大意留缓冲——谁都可能手滑一次,但不是谁都有机会重来。另外要注意:在控制台改保留天数之前,先确认实例的备份设置里日志备份开关也是开着的,只有数据备份没有日志备份的话,回档精度会大打折扣。

自己验证一下机制是否生效也很有必要:偶尔在测试库上做一次回档演练,走完整流程,确认备份真的可用、自己真的会操作。演练这件事成本很低,但真出事时它决定了你是在三分钟内开始恢复,还是手忙脚乱查半小时文档。

三、回档:把时间拨回事故前一分钟

云数据库支持回档到指定时间点,原理是拿最近一次全量备份打底,再重放 Binlog 把变更补齐到目标时刻。我的操作思路如下:

  1. 先确认备份和日志覆盖了事故时间段,否则回档无从谈起
  2. 选择目标时间点时,特意定在事故发生前一分钟,宁可少恢复一两条正常写入,也不带到任何残留
  3. 在直接回档和克隆到新实例之间,我选了克隆新实例

整个过程在控制台完成,不需要连命令行,这也是云数据库相比自建的一个直观优势:同样的事故在自建环境里,我得导备份、起临时实例、追日志,一套组合拳下来大半天就没了。而在云数据库这边,点几次鼠标的事情,最多的时间花在等克隆和做验证上。

为什么选克隆?因为数据先落在新实例上,验证没问题再切流量,全程原库保持不动——万一回档结果不对,我还能从头再来一次。排查灾难时最怕二次事故,稳妥比快更重要,慌乱中的一步错可能把唯一的退路也堵死。

回档完成后也别急着切流量,先做验收,我逐项检查了这些:

验收项 检查方法
表数量 与备份记录逐表对
关键表行数 用户表等重点表的行数核对
抽样数据 抽若干条记录核对字段内容
业务联动 查最近几条注册记录与日志交叉验证

我那次对完用户数和最新注册时间,确认无误才把应用指向新实例,老实例还多留了一天观察才清理,防止切换后又冒出什么意外。

事后复盘,我给自己定了三条操作纪律:写删改语句前先 SELECT 确认影响范围;高危操作只在维护窗口做,且同一时间不开第二个会话;不管处于什么阶段,永远对备份保留天数心里有数。三条纪律看起来朴素,胜在可执行——每一条都能在下一次动手前花十秒钟自检一遍,成本几乎为零,比事后写一万字检讨有用得多。

四、数据迁移:把老库搬到云数据库

另一个常见场景是把数据从自建 MySQL 搬到云数据库,或者把自建时期的数据迁入新买的实例,基本两条路。

小数据量用 mysqldump。先在源库导出:

mysqldump -h 源库地址 -u 用户名 -p --single-transaction \
  --default-character-set=utf8mb4 pic_db > pic_db.sql

mysqldump 导出和按时间点回档经常被放在一起比较,其实两者用途完全不同:

维度 mysqldump 导出导入 按时间点回档
主要用途 搬库、归档、迁移 误删误改后的恢复
数据来源 导出那一刻的静态文件 全量备份打底加 Binlog 重放
时间粒度 只有导出的那一刻 任意时间点,受日志保留约束
切换代价 需要停写窗口配合 回档期间原库停写保护
操作方式 命令行手工完成 控制台向导点几下

一句话总结:搬家和归档用 dump,救命和Backspace用回档。

把导出文件传到同地域的 CVM 上,再走内网导入云数据库:

mysql -h 云数据库内网地址 -u 应用账号 -p pic_db < pic_db.sql

有两个坑值得记住:字符集两端必须一致,否则中文存进去乱码(我在自建时期踩过,排查到半夜);导出时加 --single-transaction 是为了避免锁表影响在线业务写入。另外导出文件传到 CVM 再走内网导入,比从本地公网直连数据库导入快得多,也安全得多。

大数据量或不能停机就用 DTS。数据迁移服务支持全量加增量同步:先搬历史数据,再追实时增量,最后在业务低峰期切一次流量,对线上服务几乎无感。适合库已经好几个 G、或者白天完全不能停服的场景。使用时注意先在目标实例建好相同结构的库和账号,源库的增量日志在迁移期间不要清理,否则追不上变更。具体链路规格和操作步骤以官方文档为准。

无论哪条路,迁移完成后都要做数据校验:表行数逐表比对、关键记录抽样比对,确认一致再切流量。迁移是少数允许放慢脚步的环节——宁可多花一小时校验,也别省这一小时去赌数据库没问题。最省事的校验是两边各跑一遍行数汇总,十几秒就能覆盖大部分问题:

-- 迁移前后各跑一遍,对比行数
SELECT table_name, table_rows
FROM information_schema.tables
WHERE table_schema = 'pic_db';

行数对不上再深入抽查具体记录。迁移过程出问题不可怕,可怕的是切完流量才发现少了几千行。写迁移和校验脚本时我经常让 AI 先打个底,省掉不少重复劳动,相关实践见DeepSeek 工具链实战。

五、小结

  • 自动备份是底线:生产库保留天数尽量往长了配,别省钱
  • 出事第一动作是停写保护现场,而不是手忙脚乱乱试
  • 回档优先克隆到新实例,逐项验证通过再切流量
  • 迁移小数据用 mysqldump,不停机搬家用 DTS,迁完必做数据校验
  • 平时做一次回档演练,出事时才不会手生

下一篇换个节奏,从救命回到性能:首页接口从 2 秒优化到 200 毫秒的完整过程。备份决定了数据的下限,性能决定的上限,两者都得抓,缺了哪一头产品都立不住。

延伸阅读