MySQL全量备份实战:从mysqldump到XtraBackup的选型与恢复
这是一份很值得认真对待的题目。做 MySQL 运维的人都知道一句话备份是最后一道防线这句话说了一万遍但真正能把“全量备份”做得滴水不漏的人其实不多。很多人以为全量备份就是mysqldump一把梭或者xtrabackup拷贝一下数据目录跑完就算完事。但“中级”这两个字的分量在于你不仅要会备份还要懂为什么这么备、备份对业务有什么影响、出了故障如何快速恢复、自动化之后如何保证不出幺蛾子。这个层级是日常开发工程师和专职 DBA 的分水岭。这篇内容不会只停在命令层面我会把全量备份的设计思路、工具选型、一致性原理、实操命令、恢复验证、自动化脚本、常见坑位都展开讲透。无论你现在用的是 MySQL 5.7 还是 8.0无论你是刚接手数据库维护的开发者还是想系统补全备份体系的运维这篇文章都应该能给你一套可以落地、可以复盘的完整参考。1. 选型先行逻辑备份还是物理备份别等出事才后悔1.1 备份模式怎么选mysqldump 和 xtrabackup 的适用分界接触全量备份第一个绕不开的选择题就是用mysqldump还是用XtraBackup。很多人觉得“既然都是全量备份哪个顺手用哪个”这种想法在数据量小的时候没问题可一旦数据库进入以 GB 甚至 TB 计量的阶段选错工具会让你在备份窗口和恢复时间上吃尽苦头。mysqldump属于逻辑备份。它做的事情是把表结构、数据、触发器等对象通过 SQL 语句的形式导出来生成一个可读的 .sql 文件。它最大的优点是灵活、通用、跨版本迁移方便你可以只导出某几张表也可以只导出结构不带数据。缺点是备份和恢复速度都慢因为要经过 SQL 层的解析和重放而且对于 InnoDB 大表导出过程会对 CPU 和 IO 造成明显压力。XtraBackup属于物理备份。它直接拷贝 InnoDB 的数据文件中间还会利用 redo log 做一致性处理。它的核心优势是备份速度快、恢复速度快对在线业务的影响相对小尤其适合大容量数据库。缺点是工具本身有一定学习成本备份集文件不像 .sql 那样直观跨大版本恢复时需要更谨慎地评估兼容性。两者的分界我个人给的参考线是单实例数据量在 50GB 以内业务允许在低峰期做逻辑导出且你不需要分钟级恢复mysqldump 完全够用数据量超过 100GB或者业务对恢复时间目标非常敏感XtraBackup 才是正解。如果你处于 50GB 到 100GB 之间的中间地带那就看你对恢复窗口的要求以及服务器 IO 能力是否充裕。1.2 全量备份与增量备份的搭配逻辑“中级”这个概念还体现在你对备份体系的整体理解上不是只盯全量。全量备份是整个备份体系的基座但它通常不会单独存在需要和 binlog 增量备份配合才能实现“恢复到任意时间点”的能力。全量备份解决的是“我有一个可用的基线”binlog 解决的是“从基线到故障时刻之间的所有变更”。举个例子你在凌晨 2 点做了一次全量备份上午 11 点有人误删了一张核心表那么恢复路径就是先恢复凌晨 2 点的全量备份再重放 2 点到 11 点之间的 binlog跳过误删的那条语句。这个链路里全量备份的完整性直接决定了恢复的上限。全量备份如果做得不干净、不一致后面 binlog 重放得再正确也白搭因为根已经是坏的。所以做全量备份时脑子里要有“上下游”的概念上游是数据变更记录binlog下游是恢复演练。备份文件生成之后不光要归档保存还要定期做恢复演练验证备份链路是通的。关于恢复演练后面我会单独用一节来讲那是很多人忽略的重头戏。2. 一致性原理为什么备份时数据不会“半新半旧”2.1 让备份一致性不再抽象从 MVCC 和 redo log 说起中级水平的一个标志是你开始关心一致性问题。全量备份最怕的事情是备份出来的数据“不伦不类”——有的表数据在时间点 A有的表数据在时间点 B中间还夹杂着事务只提交了一半的状态。这种备份即使能恢复数据也是错乱的而且很难被察觉等到业务跑起来发现问题已经晚了。InnoDB 解决这个问题的机制简单说可以归纳成两条线。一条线是 MVCC多版本并发控制。事务在读取数据时通过 undo log 可以构建出某个历史版本的数据快照。另一条线是 redo log它记录了所有物理变更用于崩溃恢复时重放。mysqldump在做 InnoDB 备份时如果你指定了--single-transaction它会启动一个一致性的读事务利用 MVCC 机制读取到的是一个固定快照不受其他事务提交的影响。而XtraBackup走的是另一条路它在拷贝数据文件的过程中同时持续跟踪 redo log等数据文件拷贝完成再用 redo log 把备份集恢复到某个一致的时间点。这个原理听起来有点绕但你可以用一个生活化的类比来理解MVCC 像是给整个数据库拍了一张“定格照片”拍照瞬间所有事物都静止了之后再发生什么变动跟这张照片无关redo log 则像是一个录像机XtraBackup 一边拍照一边录像最后把录像回放一遍确保所有画面的时间线对齐。2.2 锁策略对比FTWRL 和 --single-transaction 的真实代价不同备份方式的一致性保障手段不同对业务的影响也不同这是选型时必须权衡的点。mysqldump如果不加--single-transaction默认会对表执行LOCK TABLES也就是在备份过程中锁住所有表业务写入会全部阻塞。在低峰期、数据量小的情况下这种方案还勉强可以用。加了--single-transaction之后InnoDB 表走的是 MVCC 快照也就是非锁定读业务照常写入。但是这里有两个坑第一这个参数只对 InnoDB 表生效对于 MyISAM 表依然会加锁第二也是更隐蔽的——即使有--single-transactionmysqldump依然会在开始时执行FLUSH TABLES WITH READ LOCKFTWRL来获取一个全局一致的 binlog 位置信息虽然这个锁持有时间很短但如果此时存在长事务或大查询FTWRL 会排队等待进而阻塞所有写入。XtraBackup的锁处理更加克制。它通过后台线程复制 InnoDB 数据文件加上 redo log 跟踪机制全程不需要对 InnoDB 表加全局锁只在最后短暂加锁获取 binlog 坐标信息。这就是为什么它能在 TB 级数据库上做到对业务影响最小。实操中的经验是无论用哪种工具都要选择业务低峰期执行备份并提前排查是否存在长时间未提交的事务。否则一个看起来无伤大雅的--single-transaction参数也可能因为长事务让备份期间所有写入卡死。这个问题在故障复盘里很常见。3. 实操落地一套可以抄作业的 MySQL 全量备份方案3.1 基于 mysqldump 的全量逻辑备份标准命令与参数拆解我给出一套在生产实践中验证过比较稳妥的逻辑备份命令适用于 5.7 和 8.0关键在于参数的取舍。mysqldump -h127.0.0.1 -uBackupUser -p --set-gtid-purgedOFF \ --single-transaction --master-data2 \ --routines --triggers --events \ --databases db_core db_order db_log \ --gtid-modeOFF | gzip backup_$(date %F_%H-%M).sql.gz逐个参数解释一下我为什么这么写--single-transaction是必须加的刚才已经说了保证 InnoDB 一致性快照。--master-data2会在备份文件中记录 binlog 文件名和偏移量注释形式写入方便恢复时确定从哪个 binlog 位置继续回放增量。注意如果你开启了 GTID建议改成--set-gtid-purgedON否则恢复时 GTID 信息会丢失。上面命令里我用了OFF只是因为我做了显式兼容处理。--routines、--triggers、--events这三个参数分别是存储过程、触发器、定时事件很多人会忘记导出。如果漏掉它们恢复出来的数据库表面上看表和数量都一致但应用一跑就报错因为缺了后台逻辑。--databases指定导出哪些库好处是恢复时目标库可以灵活选择。--gtid-modeOFF是为了避免 8.0 上 GTID 相关告警日志刷屏如果你已经全局开启 GTID就不需要这个参数直接依赖--set-gtid-purgedON即可。还有一个细节-h127.0.0.1建议写成具体的 IP 而不是localhost因为 localhost 在某些系统上会走 socket权限验证方式会变。备份账号建议单独创建最小权限账号不要直接用 root例如CREATE USER backup_user127.0.0.1 IDENTIFIED BY your_strong_password; GRANT SELECT, RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT, SHOW VIEW, EVENT ON *.* TO backup_user127.0.0.1;注意PROCESS权限在 8.0 里是执行 FTWRL 必需的少了它备份会直接报权限错误。3.2 基于 XtraBackup 的物理全量备份详细步骤与参数调优当你的数据库体量上来了逻辑导出时长让人无法接受就该切到物理备份。我以当前主流的 XtraBackup 8.0 版本为例演示一套完整的全量备份命令序列。第一步做一次全量备份。这一步我们称为 prepare 前的拷贝阶段xtrabackup --defaults-file/etc/my.cnf \ --host127.0.0.1 --port3306 \ --userbackup_user --passwordyour_password \ --backup --target-dir/data/backup/full_$(date %F) \ --parallel4 --compress --compress-threads4--parallel4表示拷贝数据文件时的并行线程数一般建议设置为物理 CPU 核数的一半到全部。--compress是采用压缩模式可以大幅减小磁盘占用代价是备份时会多一些 CPU 开销。如果服务器 CPU 资源不宽裕可以去掉压缩改用 Linux 层面的tar zstd事后压缩。第二步备份完成后需要执行 prepare 阶段让数据文件恢复到一致状态xtrabackup --defaults-file/etc/my.cnf \ --prepare --target-dir/data/backup/full_$(date %F) \ --apply-log-only这一步非常关键它做的事情是应用 redo log把备份文件里的数据推进到一个一致的时间点。注意prepare 阶段不要省略也尽量不要在拷贝完数据文件之后就立刻用这些文件来启动数据库否则大概率会因为 redo log 不一致导致启动失败。第三步就是恢复。恢复时先把目标实例的数据目录清空或者移走再把备份集拷贝回去最后设置好权限xtrabackup --defaults-file/etc/my.cnf \ --copy-back --target-dir/data/backup/full_$(date %F)然后执行chown -R mysql:mysql /var/lib/mysql就可以启动 MySQL 了。实操中我会在备份脚本里额外把 binlog 坐标信息记录下来方便以后结合 binlog 做时间点恢复。XtraBackup 会在备份目录下生成xtrabackup_binlog_info文件里面记录了备份时刻的 binlog 文件名和坐标一定要把这个文件保存好最好随着备份集一起归档恢复时先读它。4. 容易翻车的细节与自动化实践4.1 备份账号、空间估算与定期清理策略所谓“中级”很多工夫花在细节上。备份账号的权限前面已经提到这里补充一个坑MySQL 8.0 默认开启了caching_sha2_password认证插件老版本的 mysqldump/xtrabackup 可能无法正常连接此时需要在创建账号时显式指定mysql_native_password否则你会看到一堆 authentication 相关的报错。磁盘空间永远是备份方案的第一风险点。建议在备份脚本里增加前置和后置的空间检查空间剩余不足全量备份预估大小的 1.5 倍时报警而不是直接执行。同时计算备份集增长趋势设置合理的保留周期。例如保留最近 7 天的每日全量加上每月一份归档全量避免备份目录无限膨胀后把磁盘写满导致数据库本身出问题。压缩格式选择上我个人推荐.sql.gz或.xb.gz这种传统格式因为解压工具到处都有碰到紧急恢复时不用为了找一个解压器浪费时间。zstd的压缩比高且速度快但要在所有可能执行恢复的机器上都装好zstd工具否则恢复现场会手足无措。4.2 自动化备份脚本的骨架与告警通知手动执行备份偶尔一次没问题但作为规范化体系自动化是必须的。下面给一个简化但可用的脚本骨架里面包含告警通知和状态记录#!/bin/bash # MySQL full backup script BACKUP_DIR/data/backup TARGET_DIR${BACKUP_DIR}/full_$(date %F_%H-%M) LOG_FILE${BACKUP_DIR}/backup.log mkdir -p $TARGET_DIR # pre-check disk DISK_AVAIL$(df -m $BACKUP_DIR | awk NR2 {print $4}) DATA_SIZE$(du -sm /var/lib/mysql | awk {print $1}) MIN_NEED$((DATA_SIZE * 2)) if [ $DISK_AVAIL -lt $MIN_NEED ]; then echo [$(date %F %T)] ERROR: Not enough disk space. $LOG_FILE exit 1 fi # run backup xtrabackup --defaults-file/etc/my.cnf \ --host127.0.0.1 --userbackup_user --password... \ --backup --target-dir$TARGET_DIR \ --parallel4 --compress --compress-threads4 \ $LOG_FILE 21 # check backup result if [ $? -eq 0 ]; then echo [$(date %F %T)] INFO: Backup succeeded to $TARGET_DIR $LOG_FILE # send success notification (webhook/email/sms) else echo [$(date %F %T)] ERROR: Backup failed to $TARGET_DIR $LOG_FILE # send alert notification fi # retain 7 daily full backups, delete older find $BACKUP_DIR -maxdepth 1 -type d -name full_* -mtime 7 -exec rm -rf {} \;注意脚本中几个关键点前置空间检查必须做失败时要有告警而不是仅仅写日志因为半夜备份失败如果没通知到人相当于这晚的备份是白做的。备份的日志要定期清理避免日志文件撑爆磁盘。告警通知的方式公司有统一监控平台就接 webhook没有的话可以用邮件的mailx或者企业微信机器人、钉钉机器人。技术选型不是重点重点是“失败必须第一时间大声喊出来”。4.3 脚本中加入 binlog 坐标记录打通增量恢复链路全量备份如果不和 binlog 增量形成链路恢复点就只能停留在备份时刻。所以脚本中应当额外解析并保存 binlog 坐标信息。使用mysql命令获取当前 binlog 坐标mysql -h127.0.0.1 -uBackupUser -p... -e SHOW MASTER STATUS\G | grep -E File|Position注意SHOW MASTER STATUS在 MySQL 8.0 中依然可用但在未来的新版本中该语法可能被调整建议改用SHOW BINARY LOG STATUS。获取到坐标后写入一个专门的状态文件echo binlog_file$log_file $TARGET_DIR/binlog_position.txt echo binlog_pos$log_pos $TARGET_DIR/binlog_position.txt这个文件非常重要恢复时先读它再根据备份点之后的 binlog 做增量重放。我在实际恢复演练中有好几次都是先找这个文件如果哪天发现脚本漏写了这一步恢复难度会明显上升。5. 恢复验证与常见故障排查5.1 每个月做一次恢复演练备份不等于安全备份做得再勤没验证过就是“薛定谔的备份”——你永远不知道它到底能不能用直到出事故那一刻。正确的是至少每个月做一次完整的恢复演练恢复到一个独立的实例上校验表数量、行数、关键业务数据是否与主库一致。恢复演练的自动化可以比备份更轻量。比如用脚本自动拉起一个临时实例恢复最近一次全量备份执行几条简单的数据校验 SQL然后销毁实例。下面是简化版的恢复校验命令示例# 解压并 copy-back 到一个临时目录 cd /data/backup/full_$(date %F) xtrabackup --prepare --target-dir/data/backup/full_$(date %F) # 然后复制到临时实例的 datadir xtrabackup --copy-back --target-dir/data/backup/full_$(date %F) # 启动临时实例指定端口和 socket mysqld --defaults-file/tmp/restore_test.cnf --port3307 sleep 10 # 执行一致性检查 mysql -h127.0.0.1 -P3307 -uroot -e SELECT COUNT(*) FROM db_core.orders;注意每次启动临时实例前要清空临时 datadir避免初始化冲突。如果全量备份是压缩的prepare 时会自动解压但要求磁盘空间足够这个也要在演练脚本里检查。5.2 高频故障场景速查表命令失败与恢复失败的对症处理为了便于排查我总结了以下高频故障场景和处理方案场景典型报错根因与对策mysqldump 执行时报权限不足Access denied; you need (at least one of) the PROCESS privilege(s)备份账号缺少 PROCESS 权限执行GRANT PROCESS ON *.* TO backup_user127.0.0.1;XtraBackup 备份时找不到 redo logFailed to find redo log files数据目录的 redo log 被清理或路径不一致检查 datadir 配置确认 redo log 文件完整FTWRL 等待时间过长-- For more information ... FLUSH TABLES WITH READ LOCK blocked存在长事务备份前用SELECT * FROM information_schema.innodb_trx\G排查并 Kill 长时间事务恢复后表数据不一致数据条数对不上、部分表为空备份时混合了 MyISAM 和 InnoDB 表--single-transaction未覆盖 MyISAM。建议强制所有表改为 InnoDB8.0 上备份工具连接失败Authentication plugin caching_sha2_password cannot be loaded备份工具版本过老或创建账号时未指定mysql_native_password磁盘空间不足导致备份中断No space left on device备份前必须检查空间建立保留策略并定期清理恢复后无法启动数据库Unable to find the redo log未执行 prepare 或 copy-back 后没设置正确的 datadir 权限5.3 锁等待与长事务备份期间最隐蔽的坑这是一个值得单独拿出来讲的坑因为在低峰期也会遇到。假设你的备份任务在凌晨 2 点执行正常情况下两分钟跑完偏偏某一天备份跑了 20 分钟还没结束业务第二天反馈说凌晨有段时间数据库写入特别慢。排查后发现备份工具执行 FTWRL 时恰好有一个客户端连接里存在一个未提交的事务虽然它处于 sleep 状态但事务没有结束导致 FTWRL 等待事务释放资源进而堵住了所有写入。解决办法有几个层面。第一靠监控提前发现长事务除了看information_schema.innodb_trx还可以关注SHOW ENGINE INNODB STATUS\G中 TRANSACTION 部分的输出第二对连接执行KILL thread_id清理长时间 idle 事务第三对于已知的报表连接或开发客户端设置合理的wait_timeout和lock_wait_timeout避免它们长时间占用事务资源。我自己的习惯是在备份前脚本里先执行一次长事务检查超过一定时长的连接直接告警而不是默默让备份卡在那里。5.4 binlog 增量衔接的排错思路全量备份恢复成功只是第一步要恢复到故障前的状态还必须重放 binlog。常见的问题有几个一是全量备份的 binlog 坐标没记录导致不知道从哪个 binlog 开始回放二是 binlog 文件被过早清理增量链路断裂三是 GTID 环境下没有正确导入 GTID 信息导致回放冲突。针对第一点前面已经强调过要记录 binlog 坐标。针对第二点必须把binlog_expire_logs_seconds8.0或expire_logs_days5.7设置成大于全量备份周期的时间比如每天一次全量备份binlog 至少要保留 3 天以上建议 7 天。针对第三点恢复 binlog 时使用--skip-gtid-mode或者在 SQL 文件中SET GLOBAL.GTID_PURGED...要谨慎。这里不展开讲 GTID 的全部细节但你需要记住GTID 环境下做恢复多花时间搞懂 GTID 的语义远比踩坑之后再补救更划算。6. 备份体系的中级进阶从能用到好用做到这一步你的全量备份已经不是“会跑脚本”的层次了。但如果你对备份体系还有更高的追求可以继续做三件事。一件事是备份集中管理。当数据库实例多了以后每个实例各自为政的备份脚本会变成运维噩梦。可以用跳板机集中接收所有实例上报的备份结果或者统一从堡垒机下发备份任务。规划上可以让备份文件落地到一个集中的备份存储节点按实例名和日期分目录避免备份文件散落在各个生产服务器上——那样既消耗生产磁盘出故障时又难以统一找回。第二件事是制定分级备份策略。核心业务库每天全量 实时 binlog外围业务库每日全量日志或临时库只做定期归档。分级的意义在于让成本花在刀刃上不要所有库都按同样的频率和保留周期来做那样资源浪费严重尤其在云数据库环境下备份存储是要计入成本的。第三件事是建立备份健康度报表。每天备份任务结束后汇总成功/失败信息统计备份时长、备份集大小、存储消耗趋势。做报表不是为了好看而是让你能敏锐地发现异常某天备份时长突然翻倍说明有表数据量暴涨或系统 IO 出现瓶颈某天备份集大小异常缩小说明数据可能被意外大规模删除。这些东西都是事故的前兆。三件事做完你会发现备份真正变成了一个“体系”而不是一个个孤立的脚本。7. 最后给几点实操经验和建议做备份越久越会发现它是一项“枯燥但绝不能出错”的工作。我可以分享几个直接用的建议备份账号的密码不要写在命令行里用my.cnf的独立配置文件并限制权限为 600防止进程列表泄露密码。备份脚本要加入set -euo pipefail避免管道中某个环节静默失败。恢复演练尽量提前约定好要校验哪些表。不要只验证“实例能启动、数据库能查询”那只是最低要求。把核心业务表挑出来做行数对比时间允许的话再抽查几张表的数据内容与主库对比。00 全量备份的校验工作做在平时真正故障时你才有底气说“我有把握恢复”。在云环境或者容器化环境里做备份还要额外关注底层存储的快照能力。云盘快照和逻辑备份可以互补不过云盘快照的一致性取决于云厂商的实现机制不能想当然地认为快照就是一致性备份。如果要靠云盘快照做 MySQL 备份先搞清楚它和存储层、数据库层的配合关系再决定是否纳入备份体系。用 XtraBackup 做物理备份的时候我习惯在备份集目录里同时生成一个RELEASE_NOTES文件记录 MySQL 版本、XtraBackup 版本、备份时间、binlog 坐标、恢复方式说明。因为半年后当你或同事面对这个备份集时光靠文件名称根本想不起当初是怎么处理的。多写几行说明恢复时可以省半小时的思考时间。备份链路的状态监控也不可忽视。哪怕只是简单地给备份脚本出口加一个返回码判断、失败时发一条通知整体可靠性就能提升一大截。很多事故的起因不是没做备份而是备份在半夜悄悄失败、没人知道等到要用了才发现。还记得一位经验丰富的前辈和我说过的话备份做得好不好不是看备份的时候有多顺利而是看恢复的时候能有多从容。这句话一直是我的准则。关于全量备份最容易犯的错不是命令写错而是备份完就撒手不管、从不验证、从不演练。如果你看完这篇文章愿意在下个月做一次完整的恢复演练那这篇内容的价值就真正体现出来了。