Mysql数据库的备份与恢复
做开发或运维的朋友应该都有过这种经历:深夜十二点,手机突然响起,电话那头业务方急得声音发抖——“刚才那张表的数据被误删了,能恢复吗?”先别慌,能不能恢复、能恢复到什么程度,取决于你平时有没有做好备份,以及备份方式选得对不对。我做了几年数据库相关工作,踩过不少坑,也总结出了一套从备份策略设计、工具选择到恢复演练都比较完整的流程。这篇就围绕 Mysql 数据库的备份与恢复,把核心思路、常用工具、实操步骤和避坑经验一次讲透,适合后端开发、运维人员,以及正在做数据库课程设计的在校同学参考。
1. 先理清备份方案:逻辑备份、物理备份和增量日志
1.1 逻辑备份与物理备份的选择逻辑
Mysql 的备份方式,按“备份出来的东西长什么样”来分,主要是两大类:逻辑备份和物理备份。
逻辑备份,最典型的工具就是 mysqldump。它会把你数据库里的表结构、数据记录、触发器等转换成一条条 SQL 语句,导出成一个 .sql 文本文件。恢复的时候把这份 SQL 文件重新执行一遍,数据就回来了。打个比方,逻辑备份就像你把自己家的家当列了一份详细的物品清单,清单上不仅写了每件东西在哪、长什么样,还写清楚了如何原样摆回去。好处是这份“清单”拿到哪台新电脑上都能照着还原,跨版本、跨平台都能用;坏处是数据量一大,导出和导入都慢,而且生成 SQL 和执行 SQL 的过程本身都会占用不少资源。
物理备份则完全不同,它是直接拷贝 Mysql 的数据目录,也就是那些底层的 ibdata1、.ibd、frm 之类的物理文件。你可以先停掉数据库做冷备份,也可以用工具做在线热备。物理备份相当于把你整个家连家具带装修整体拍照、打包搬走,恢复的时候直接放到原位置。它的优势是速度极快,数据量达到几十 GB、几百 GB 级别时,逻辑备份可能导出要几个小时,而物理备份可能十几分钟就完成了;缺点是你拷贝下来的物理文件对数据库版本、存储引擎甚至操作系统都有依赖,跨版本迁移时兼容性要求比较严格。
那到底怎么选?我的经验是:数据量小、偶尔做一次备份、希望备份文件能方便地在不同环境间迁移,优先考虑逻辑备份;数据量大、对备份和恢复时间敏感、生产环境每天要做全量备份的,物理备份更可靠。当然,很多团队的最终方案是两者结合——日常用物理备份保证恢复速度,关键小表再做逻辑备份方便精细化操作。
1.2 全量备份、增量备份与 binlog 的分工
备份策略里还有一个绕不开的概念:全量备份和增量备份。
全量备份,就是把 Mysql 里所有数据完整导出一份。优点是做完之后你手里有了一份无法再完整的“底稿”,恢复时只需要恢复这一份;缺点也很明显——如果每天都做全量备份,不仅备份耗时长,生成的备份文件也会占用大量磁盘空间。比如一个 100 GB 的库,每天导一次,一周就是 700 GB 的历史备份文件。
增量备份则聪明一些,它只记录从上次备份之后发生变化的数据。Mysql 的增量备份核心依赖就是 binlog(二进制日志)。你可以把 binlog 理解成数据库的“行车记录仪”,每一个会改变数据的操作(INSERT、UPDATE、DELETE 等)都会被记录下来。所以只要你有了一次全量备份,再加上之后产生的所有 binlog,理论上你就可以把数据库恢复到任意一个时间点——只要这段时间的 binlog 没有丢、没有损坏。
实践中,最常见的组合拳是:每天凌晨做一次全量备份,平时开启 binlog 并定期归档,等到要恢复的时候,先恢复最近一次全量备份,再重放从那次备份以来产生的 binlog 增量,就能把数据推到故障发生前的状态。这套方案兼顾了备份成本与恢复粒度,是目前大多数生产环境采用的标准做法。
另外提一个概念叫差异备份,它介于全量和增量之间,备份的是“自上次全量备份以来所有变化的数据”。和增量备份的区别在于:增量备份是基于上次任意类型的备份(全量或增量)做的,而差异备份永远基于最近一次全量。差异备份的恢复比增量简单(只需全量+最近一次差异),但备份文件往往比增量大。在我实际接触过的团队里,日志型业务多用“全量+增量”,金融类对恢复时间敏感的系统偶尔会加差异备份来缩短重放日志的时间。
2. mysqldump 逻辑备份实操:参数、命令与细节
2.1 一条靠谱的 mysqldump 命令长什么样
mysqldump 是 Mysql 自带的逻辑备份工具,几乎所有 Mysql 发行版都包含了它。它的用法本身不算复杂,难的是把参数组合用对,否则备份出来的数据可能不一致,甚至恢复时报错。
先看我平时最常用的一条命令:
mysqldump -u root -p --single-transaction --master-data=2 \ --routines --triggers --events --set-gtid-purged=OFF \ your_db_name > /backup/your_db_$(date +%Y%m%d_%H%M%S).sql这里每个参数都有讲究。
--single-transaction 是最关键的一个参数,它利用 InnoDB 的 MVCC(多版本并发控制)机制,在备份开始时开启一个一致性的读事务。这样备份过程中,其他连接依然可以正常读写数据,不会被锁住,而且导出的数据是一致性快照,不会出现“备份到一半数据变了导致逻辑矛盾”的情况。注意,这个参数只对 InnoDB 表有效,MyISAM 表不支持事务,想要一致性还是得加 --lock-tables 把表锁住。
--master-data=2 会在导出的 SQL 文件头部注释掉一行 CHANGE MASTER TO 语句,里面记录了备份时刻的 binlog 文件名和位置(比如 MASTER_LOG_FILE='mysql-bin.000014', MASTER_LOG_POS=2564)。这行信息是后续做时间点恢复的关键线索,相当于备份文件的“时间坐标”。值设为 2 表示以注释形式记录,如果你设成 1,恢复时可能会触发从库同步的额外行为,所以日常备份我建议用 2。
--routines、--triggers、--events 分别表示把存储过程、触发器、事件调度器一起备份出来。很多新手只导了表数据,恢复后才发现存储过程全丢了,再补导非常麻烦,所以这三个参数我每次必加。
--set-gtid-purged=OFF 是在 GTID 模式下(5.6 以后引入的主从复制模式)用来控制是否导出 GTID 信息。如果你只是做单机备份恢复,不涉及主从环境,加上这个参数可以避免恢复时报 GTID 相关的错误。如果你的备份要用来搭建新的从库,那就需要保留 GTID 信息,这个参数就要根据实际情况调整了。
备份文件的路径和命名也建议规范。我用的是按日期时间命名的方式,这样备份文件自然形成了时间序列,后面做定期清理也很方便。
2.2 单库、多库、单表的备份与恢复差异
mysqldump 备份作用范围越大,命令选项也越不同。日常工作中最常见的三种粒度是:整库、多个指定库、指定表。
整库备份,备份的是某个库里的所有对象。命令很直接:
mysqldump -u root -p --single-transaction --routines --triggers --events db1 > db1.sql如果要备份多个库,可以加 --databases 参数,比如:
mysqldump -u root -p --single-transaction --databases db1 db2 > dbs.sql这里有个坑要提醒新手:加了 --databases 之后,导出的 SQL 文件里会自动包含 CREATE DATABASE 和 USE 语句。恢复时哪怕目标库里没有这个 database,也会自动创建。如果不加 --databases,导出的文件里只有 CREATE TABLE 和 INSERT,默认恢复到当前 USE 的库中。
只备份指定表就更灵活了:
mysqldump -u root -p --single-transaction db1 t_user t_order > tables.sql这种方式的典型场景是:某个表误删了数据,但整个库恢复成本太高,于是单独导出这张表(如果之前有这张表的单独备份)来恢复。不过说实话,指定表的备份一般只在做数据归档或调试时用,作为生产备份策略的主力还是有点单薄。
恢复操作本身不复杂,比如把备份文件导回数据库:
mysql -u root -p db1 < /backup/db1.sql或者先登录 mysql 再用 source 命令:
SOURCE /backup/db1.sql;需要注意的是:如果备份文件包含 CREATE DATABASE 语句,恢复时不需要指定库名直接执行即可;如果备份时没加 --databases,恢复前要先确保目标库存在,否则会报 Unknown database 的错误。
2.3 大表备份太慢?试试压缩与分库分表导出
单表数据量大到一定程度,mysqldump 的导出速度会明显下降,生成的 SQL 文件动不动就是几个 GB,传输、存储都是问题。这时候两个简单有效的优化手段:管道压缩和并行备份。
管道压缩很简单,就是把 mysqldump 的标准输出直接交给 gzip 处理:
mysqldump -u root -p --single-transaction your_db | gzip > /backup/your_db.sql.gz恢复时先解压再导入:
gunzip -c /backup/your_db.sql.gz | mysql -u root -p your_db实测下来,压缩比通常在 5:1 到 10:1 之间,对以字符串为主的表效果尤其明显。代价是备份和恢复时 CPU 会升高一些,但和磁盘 I/O 节省的收益相比完全值得。
并行备份则是把一个大库拆成多个库、多张表分别同时导出。严格来说 mysqldump 本身不支持并发导出同一个库,但你可以写脚本把多个库分给多个后台进程:
for db in db1 db2 db3 db4; do mysqldump -u root -p --single-transaction "$db" > "/backup/${db}.sql" & done wait如果你用的是 Percona 分支的 Mysql,自带了一个 xtrabackup 工具,做物理热备的效率远高于 mysqldump,大数据量场景我更推荐你优先了解它。后面我会专门讲物理备份方案。
3. binlog 增量恢复:从全量备份精确恢复到任意时间点
3.1 先搞懂 binlog 的三种格式
binlog 是 Mysql 实现数据复制和增量恢复的核心,它记录了对数据库有变更的操作。很多朋友都听说过 binlog,但对其三种格式的区别不一定清楚:STATEMENT、ROW 和 MIXED。
STATEMENT 格式记录的是 SQL 语句本身,比如 DELETE FROM t_user WHERE age < 18。优点是日志量小、可读性强;缺点是在某些场景下回放结果可能和原始执行不一致,比如语句里用了 NOW() 函数,或者依赖了当前会话的变量,重放时得到的数据就会走样。
ROW 格式则记录每一行数据的具体变化。比如你 UPDATE 了 1000 行,binlog 里会记录这 1000 行每一行的旧值和新值。优点是恢复结果绝对精确,任何函数调用、并发环境都不会影响回放结果;代价是日志量暴涨,尤其是大批量 UPDATE 或 DELETE 的场景。MIXED 格式则是 Mysql 自动判断:默认用 STATEMENT,遇到可能产生歧义的语句时自动切换为 ROW。
从恢复角度,我强烈建议在生产环境把 binlog 格式设为 ROW:
binlog_format = row理由很简单:备份恢复和误操作回滚,最重要的就是“精确”二字。ROW 格式虽然日志文件大一些,但它能让你知道某条数据事前长什么样、事后长什么样,配合 mysqlbinlog 做闪回分析时异常好用。
检查当前 binlog 配置:
SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';如果 log_bin 是 OFF,那 binlog 压根没开启,增量恢复也就无从谈起了。开启 binlog 需要在配置文件 my.cnf 的 [mysqld] 段加一行:
log_bin = /var/log/mysql/mysql-bin改完后重启 Mysql 服务生效。
3.2 mysqlbinlog 工具实操:基于时间和位置的精确回放
mysqlbinlog 是 Mysql 自带的 binlog 解析工具,它能把二进制日志翻译成可读的 SQL 语句,还能按时间范围或日志位置提取特定片段。
先要看懂当前有哪些 binlog 文件:
SHOW BINARY LOGS;查询某个 binlog 文件中的事件列表:
SHOW BINLOG EVENTS IN 'mysql-bin.000014' LIMIT 20;这个命令输出里会有每个事件的 Log_name、Pos(位置)、Event_type、Info 等列,Info 列会显示具体执行的 SQL。恢复操作中,我们通常用 mysqlbinlog 工具把 binlog 内容重放给 mysql 客户端:
基于时间点恢复,比如回放 2024-06-20 09:00:00 到 10:00:00 之间的日志:
mysqlbinlog --start-datetime="2024-06-20 09:00:00" \ --stop-datetime="2024-06-20 10:00:00" \ mysql-bin.000014 | mysql -u root -p基于日志位置恢复更精确,不会因为同一秒内多条语句而误伤:
mysqlbinlog --start-position=2564 --stop-position=89732 mysql-bin.000014 | mysql -u root -p为什么要按位置而不是完全按时间?因为时间过滤其实比较“粗”,同一秒内可能有多条操作,你只想恢复某一条之前的数据时,用位置是最可靠的。位置信息的查询方式就是前面讲的 SHOW BINLOG EVENTS。
实战中一个更常见的场景是:某条 DELETE 误删了大量数据,你想跳过这条 DELETE,只回放它之前的 binlog。这时你先把 binlog 导成文本,找到那条 DELETE 语句的位置,然后:
mysqlbinlog --stop-position=误删语句的起始位置 mysql-bin.000014 | mysql -u root -p这样就恢复了删除前的所有状态,那条误删语句及其之后的操作暂时不执行,然后再根据业务情况把后续正常操作筛选出来重新执行。
3.3 全量备份加 binlog 的组合恢复全流程
真正到位的恢复,是把全量备份和 binlog 增量串成一条链。假设每天凌晨 2:00 做全量备份,今天上午 10:00 有人误删了核心表数据。恢复步骤如下:
先恢复最近一次全量备份文件:
mysql -u root -p your_db < /backup/your_db_前一天.sql找到全量备份文件头部记录的 binlog 文件名和位置。因为我们之前备份用了 --master-data=2,直接看备份文件的头部注释即可:
grep "CHANGE MASTER TO" /backup/your_db_前一天.sql假设输出显示 MASTER_LOG_FILE='mysql-bin.000020', MASTER_LOG_POS=154。这意味着从 binlog 的 154 位置开始,就是备份完成之后产生的新变更。接下来确认误删操作发生在哪个 binlog、哪个位置,然后把从 154 到误删操作之前的日志重放进去:
mysqlbinlog --start-position=154 --stop-position=误删操作的起始位置 \ mysql-bin.000020 mysql-bin.000021 | mysql -u root -p your_db执行完这一串命令,你的数据库就已经恢复到了误删操作前的那一刻。注意,如果 binlog 有多个文件,把它们按顺序都传给 mysqlbinlog 即可,工具会自动衔接。
这套流程我在面试和实际故障处理中碰到过无数次,核心要点就两个:一是全量备份文件的 binlog 位置要清晰记录,二是误删操作在 binlog 里的位置要精确定位。位置定错了,恢复出来的数据就会多走一步或少走一步。
4. 物理备份、冷备份与跨服务器迁移
4.1 冷备份实操与适用场景
冷备份,简单说就是停库、拷贝数据文件、再启库。虽然听起来“土”,但在特定场景下它是非常高效的方案,尤其是数据量特别大、或者你只想快速做一次服务器迁移时。
冷备份的大致步骤:
# 1. 优雅关闭数据库服务 mysqladmin -u root -p shutdown # 或者 systemctl stop mysqld # 2. 确认进程已停止 ps -ef | grep mysqld # 3. 拷贝整个数据目录 cp -a /var/lib/mysql /backup/mysql_cold_backup_$(date +%Y%m%d) # 4. 重启数据库服务(如果需要) systemctl start mysqld恢复时更加暴力:停库,把原来的数据目录备份一份(防止操作失误),再把之前拷贝的数据目录整个放回去,启动数据库就完事。
冷备份最大的优点是简单、可靠、恢复极快。缺点也好理解:停机时间内的数据无法写入,业务必须接受一段时间的不可用。所以冷备份一般适合维护窗口期内的数据迁移、测试环境初始化、或者作为“最后一张底牌”定期做一次。线上核心业务不太可能用冷备份作为唯一的备份策略,大多还是和热备工具配合。
4.2 用 Percona XtraBackup 做物理热备
如果既想走物理备份的速度,又不希望停机,Percona XtraBackup 是绕不开的工具。它能在数据库运行状态下完成 InnoDB 表的物理备份,是目前很多团队做大数据量备份的首选。
XtraBackup 备份的核心步骤:
# 1. 执行完整备份 xtrabackup --backup --target-dir=/backup/xtra_full_$(date +%Y%m%d) \ --user=root --password=your_password # 2. 对备份文件进行恢复准备 xtrabackup --prepare --target-dir=/backup/xtra_full_20240620第一步是把当前数据文件复制到备份目录;第二步是让备份文件进入一致可用的状态。这个过程会把备份期间产生的事务日志也合并到数据文件中,所以 prepare 是必须的。恢复到目标实例时,先停库,把目标数据目录清空(或者移到临时位置),然后把备份文件内容全部拷贝回数据目录,再启动服务。
和 mysqldump 的对比很直观:
| 对比项 | mysqldump 逻辑备份 | XtraBackup 物理备份 |
|---|---|---|
| 备份速度 | 慢,逐条生成 SQL | 快,直接复制文件 |
| 恢复速度 | 慢,逐条执行 SQL | 快,整体文件就位 |
| 备份文件大小 | 小(压缩后更小) | 大,基本等同数据量 |
| 在线备份 | 支持(InnoDB) | 支持 |
| 跨版本兼容 | 较灵活 | 有限制 |
| 适用数据量 | 适合小中型库 | 适合大中大型库 |
数据量在 50 GB 以下,mysqldump 压力不大;超过 100 GB,我一般直接上 XtraBackup。
4.3 使用 Navicat 和 dbx 工具做图形化备份
写完命令行工具,再提两个图形化工具。Navicat 是很流行的数据库管理客户端,连接上 Mysql 后,右键数据库,选择“备份”或“转储 SQL 文件”,就能完成逻辑备份。它的优点是操作门槛低,适合不熟悉命令行的同学;缺点是自动化能力有限,难以纳入定时任务和脚本体系。另外它本身的备份功能在不同版本之间差异不小,建议优先用 mysqldump 而非依赖 Navicat 的备份模块。
dbx 是一款新兴的数据库桌面工具,因为轻量、跨平台,在开发者圈子里热度上升很快。它同样支持连接 Mysql 并执行导出导入操作。这类工具我更愿意定位成“日常调试和临时备份的补充”,真正定时、批量、核心的备份任务,还是交给命令行脚本更踏实。图形化工具偶尔会碰到大表导出超时、内存溢出之类的问题,命令行工具反而没这些毛病。
5. 备份策略规划与自动化:别让备份停在“口头承诺”上
5.1 备份周期、保留策略与 RTO/RPO 评估
备份策略设计的核心,其实不是技术,而是两个业务指标:RTO(恢复时间目标)和 RPO(恢复点目标)。RTO 指故障发生后你需要多久把业务拉起来;RPO 指你能容忍丢失多长时间的数据。这俩数值直接决定你的备份频率和恢复方式。
举个例子:某交易系统要求 RPO 不超过 10 分钟,那就意味着 binlog 归档周期不能超过 10 分钟,否则一旦故障,最多会丢 10 分钟的数据;如果要求 RTO 不超过 1 小时,那全量备份的恢复和 binlog 重放速度必须在 1 小时内完成,备份存储在本地磁盘还是云对象存储、网络带宽多少,都会成为瓶颈。
制定备份周期时,我一般给团队建议这样组合:
- 每日凌晨 01:00 做一次全量备份(XtraBackup 或 mysqldump,根据数据量选择)。
- 每日凌晨 01:30 把备份文件同步到异地存储(OSS、云盘、另一台服务器均可)。
- binlog 实时开启,每 10 分钟或每 100 MB 切换一次并自动归档。
- 全量备份保留最近 7 天,异地备份保留最近 30 天,月度归档保留一年。
备份文件保留策略要结合磁盘成本和合规要求权衡,不是越多越好。但有一条铁律:备份文件至少有一个副本存放在和源数据库不同的物理位置上,防止机房断电、磁盘阵列损坏这类单点故障。
5.2 写一个自动备份清理的 Shell 脚本
自动化是备份落地的基础,手动备份注定会漏。我提供一个可以“拿来就改”的 Shell 脚本骨架:
#!/bin/bash # 功能:Mysql 全量备份 + binlog 归档 + 过期清理 # 建议配合 crontab 使用 BACKUP_DIR="/backup/mysql" DATE=$(date +%Y%m%d_%H%M%S) DB_USER="backup_user" DB_PASS="backup_password" DB_NAME="your_db" MYSQL_CMD="mysql -u${DB_USER} -p${DB_PASS}" MYSQLDUMP_CMD="mysqldump -u${DB_USER} -p${DB_PASS} --single-transaction --master-data=2 --routines --triggers --events" # 1. 创建当日备份目录 mkdir -p ${BACKUP_DIR}/${DATE} # 2. 逻辑备份并压缩 ${MYSQLDUMP_CMD} ${DB_NAME} | gzip > ${BACKUP_DIR}/${DATE}/${DB_NAME}.sql.gz # 3. 检查备份文件大小,为 0 则报警退出 if [ ! -s ${BACKUP_DIR}/${DATE}/${DB_NAME}.sql.gz ]; then echo "Backup file is empty!" | mail -s "Mysql backup failed" your@email.com exit 1 fi # 4. 记录备份后的 binlog 位置 cat ${BACKUP_DIR}/${DATE}/${DB_NAME}.sql.gz | gunzip | grep "CHANGE MASTER TO" >> ${BACKUP_DIR}/backup_position.log # 5. 删除 7 天前的本地备份 find ${BACKUP_DIR} -type d -mtime +7 -name "20*" -exec rm -rf {} \; echo "Backup done at $(date)" >> ${BACKUP_DIR}/backup.log然后配置 crontab:
0 1 * * * /usr/local/bin/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1脚本里的邮件报警只是示意,生产环境我更推荐接入钉钉、企业微信或短信告警。一个报警机制缺失的备份系统,和没有备份在本质上差别不大。
5.3 恢复演练:备份是否可用,只有试过才知道
说句不好听的,不少团队做了多年备份,一次都没恢复过,直到故障来了才发现备份文件一直在报错或者压根不完整。备份做得好不好,检验的唯一标准是“能不能顺利恢复”。
恢复演练应该定期做,建议每季度至少演练一次。演练方式有两种:一种是临时搭建一个测试实例,把最新的全量备份和部分 binlog 恢复到测试库,占用的资源不多,但能覆盖 90% 的流程;另一种是模拟故障,把生产库拉到备机上做完整恢复,顺便评估 RTO 是否达标。
演练时需要重点验证的事项:
- 备份文件是否完整、非空、文件大小在正常范围。
- 恢复到新实例后,数据行数是否和源库一致。
- 存储过程、触发器、事件是否完整恢复。
- binlog 路径和时间点恢复是否可用,能否精确恢复到最后一次操作。
- 备份脚本中的报警机制是否真的能触发。
我自己的习惯是把演练结果记录成文档,包括恢复耗时、遇到的问题、调整项,逐步优化备份脚本和恢复流程。这比故障发生时再来翻资料高效得多。
5.4 常见恢复报错速查表
最后分享一份踩坑汇总,都是实操中容易碰到的问题,直接对照排查会快很多:
| 报错现象 | 可能原因 | 处理建议 |
|---|---|---|
| ERROR 1049 (42000): Unknown database 'xxx' | 恢复时目标库不存在 | 先执行 CREATE DATABASE xxx,或备份时加 --databases |
| ERROR 1449: The user specified as a definer does not exist | 备份文件里包含视图/存储过程,definer 账户在目标库不存在 | 重建相应账号,或恢复时用 sed 替换 definer 再导入 |
| ERROR 1064: You have an error in your SQL syntax | 备份或 binlog 文件在传输、解压中被截断损坏 | 检查源文件 md5,重新传输;用 mysqlbinlog 时注意版本匹配 |
| ERROR 1418: This function has none of DETERMINISTIC... | 导入自定义函数时未满足二进制日志安全条件 | 设置 log_bin_trust_function_creators=1 |
| mysqldump: Got error: 1045: Access denied | 备份账号权限不足 | 至少赋予 SELECT、LOCK TABLES、SHOW VIEW、TRIGGER 权限 |
| mysqlbinlog: unknown option '--ssl' | 工具版本和服务端不匹配 | 统一 Mysql 客户端和数据库大版本 |
关于权限,我习惯单独建一个备份专用账号,不使用 root 备份,既安全又方便权限收敛:
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'strong_password'; GRANT SELECT, LOCK TABLES, SHOW VIEW, TRIGGER, PROCESS, RELOAD, EVENT ON *.* TO 'backup_user'@'localhost'; FLUSH PRIVILEGES;6. 真实故障复现:一次误删表后的完整恢复
纸上谈兵再多,不如亲手跑一遍。我模拟一个常见的故障场景:业务表 t_order 在上午 10:32 被误执行了 DELETE 操作,整表数据被清空,好在当天凌晨 01:00 有过全量备份,binlog 也完整。下面是从零开始恢复的全过程。
先看全量备份文件里记录的 binlog 位置:
grep "CHANGE MASTER TO" /backup/mysql/20240620/t_order.sql输出:CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000028', MASTER_LOG_POS=1284。
登录数据库,确认 binlog 文件和错误位置:
SHOW BINARY LOGS; SHOW BINLOG EVENTS IN 'mysql-bin.000028';假设最后一条 DELETE 语句的开始位置是 45912。那么恢复命令分两步走:
第一步,恢复全量备份:
mysql -u root -p business_db < /backup/mysql/20240620/t_order.sql第二步,回放从 1284 到 45912 的所有 binlog:
mysqlbinlog --start-position=1284 --stop-position=45912 \ mysql-bin.000028 mysql-bin.000029 | mysql -u root -p business_db等待执行完毕,查询表数据:
SELECT COUNT(*) FROM t_order;如果数字和业务方给出的正常值一致,表示恢复成功。如果只差了一点点,多半是 stop-position 定位偏了,再往上调整一下重新回放即可。
整个过程看起来不复杂,但真正考验人的是在压力下冷静定位 binlog 位置。所以我强烈建议每个团队都自己动手做几遍模拟演练,把常用命令熟记于心,真出问题时才能从容应对。另外,回放 binlog 前务必先备份一份当前状态,防止回放出错造成二次破坏。
写到这里,我想起自己刚开始维护数据库时,也是第一次遇到误删数据的情况,当时手忙脚乱地翻文档、试命令,最后用了快两个小时才恢复成功。后来我养成了两个习惯,一个是备份脚本里每次备份后自动记录 binlog 位置,另一个是每季度拉一个测试实例做恢复演练。这两个习惯让我在后面再遇到类似故障时,基本二十分钟内就能完成恢复。数据库备份与恢复看起来是偏底层的运维工作,但它直接决定了业务出问题时的“逃生通道”有多宽。希望这篇内容能帮你把这条通道修得又宽又稳。