MySQL 备份与恢复
本文合并自:06-备份与恢复.md、2-5-MySQL索引事务与备份恢复.md
适用环境:CentOS 6 / CentOS 7 + MySQL 5.5/5.6/5.7 或 MariaDB 5.5/10.x 核心内容:备份理论与策略、
mysqldump逻辑备份、mydumper/myloader多线程备份、binlog 与基于时间点恢复(PITR)、xtrabackup/innobackupex物理热备、备份自动化脚本与排错 相关文档:索引、事务、锁、执行计划与状态查询见11-04-索引事务与状态查询.md;Shell 脚本语法见06-01-Shell脚本编程.md;系统级备份工具见03-03-备份与恢复.md
目录
| 章节 | 主题 | 关键内容 |
|---|---|---|
| 一 | 备份基础理论 | 故障类型、物理/逻辑备份、全量/增量/差异、冷热温备、RPO/RTO、策略设计 |
| 二 | mysqldump 逻辑备份与恢复 | 全部选项详解、导出文件结构、恢复方式、分库分表脚本、大库替代方案 |
| 三 | mydumper / myloader 多线程逻辑备份 | 编译安装、参数、正则备份、恢复 |
| 四 | binlog 与基于时间点恢复 | 三种格式、参数配置、mysqlbinlog、误删数据完整恢复演练、GTID |
| 五 | 物理备份 | 冷备、LVM 快照、XtraBackup 全量/增量、表空间传输 |
| 六 | 自动化与运维 | 备份脚本、crontab、校验与恢复演练、异地同步、排错清单、练习 |
一、备份基础理论
1.1 为什么要备份
数据是企业的核心资产。数据库可以重装、服务器可以更换,但数据一旦丢失且无备份,业务基本等于终止。备份的唯一目的是:在下列任意一种故障发生后,把数据恢复到可接受的状态。
| 故障类型 | 典型场景 | 备份能否救 | 说明 |
|---|---|---|---|
| 硬件故障 | 磁盘坏道、RAID 双盘同时损坏、电源击穿主板、内存故障导致数据页损坏 | 能 | RAID 只解决单盘冗余,RAID 不是备份;坏块可能已经写进数据文件 |
| 误操作 | DROP DATABASE、DROP TABLE 打错名、UPDATE/DELETE 忘写 WHERE、误删 datadir |
能 | 运维事故第一大来源。需要「全备 + binlog」才能恢复到误操作前一刻 |
| SQL 注入与攻击 | 注入后 DROP 表、拖库后删库勒索、内部人员恶意删除 |
能 | 备份文件本身也是攻击目标,必须异地 + 权限隔离 |
| 软件缺陷 | MySQL 崩溃导致表损坏、升级失败、文件系统 bug | 能 | InnoDB 崩溃恢复可解决大部分,但页损坏需从备份取 |
| 机房灾难 | 火灾、水淹、断电、地震、机房被整体断网/查封 | 仅异地备份能救 | 同机房备份与生产同生共死,必须异地/异城保存一份 |
运维铁律三条: ① 没有验证过恢复的备份,等于没有备份; ② 备份必须至少两份、且至少一份在异地(3-2-1 原则:3 份数据、2 种介质、1 份异地); ③ 误操作是最高频事故,因此全量备份 + 二进制日志是最低配置,缺任何一半都无法做到时间点恢复。
1.2 备份方法总览
| 方法 | 说明 |
|---|---|
| bin-log | 通过二进制日志备份,主要通过 MySQL 主从复制实现。建议将 bin-log 实时备份至远程服务器,以防机器故障导致日志丢失 |
| mysqldump | MySQL 自带逻辑备份工具,单线程导出 SQL 文本 |
| mydumper | 多线程逻辑备份工具,按表并行导出,速度远快于 mysqldump |
| Xtrabackup | Percona 物理热备工具,支持 InnoDB 热备(不锁表) |
| 冷备(cp/tar) | 停库后直接拷贝数据目录,最简单也最可靠,但需停机 |
| LVM 快照 | 文件系统级快照,秒级完成,锁表时间极短 |
| 主从复制 | 严格说是高可用而非备份(误删会同步到从库),但可用于从库上做备份以免影响主库 |
1.3 物理备份与逻辑备份
| 对比项 | 物理备份 | 逻辑备份 |
|---|---|---|
| 原理 | 直接拷贝数据文件(.ibd、.frm、ibdata1、redo log 等二进制文件) |
把数据导出为 CREATE TABLE + INSERT 等 SQL 文本 |
| 代表工具 | cp/tar 冷备、LVM 快照、XtraBackup/innobackupex |
mysqldump、mysqlpump、mydumper、SELECT ... INTO OUTFILE |
| 备份速度 | 快(顺序读文件,与数据量线性相关) | 慢(需逐行取出、格式化成 SQL) |
| 恢复速度 | 快(拷回去即可) | 慢(要逐条解析执行 SQL、重建索引) |
| 备份体积 | 大(含索引、碎片、预分配空间) | 小(只有数据,压缩比高) |
| 可读性 | 二进制,不可读、不可编辑 | 纯文本,可用 vim/grep/sed 编辑,可只挑一张表恢复 |
| 跨版本 / 跨平台 | 差(版本、字节序、页格式必须兼容) | 好,5.5 导出可导入 8.0、可导入其他数据库 |
| 跨存储引擎 | 不行 | 可以(导出后改 ENGINE= 再导入) |
| 是否需停机 | 冷备需要;XtraBackup 对 InnoDB 不需要 | 不需要,但可能长时间持锁 |
| 一致性保证 | 靠停库、快照或 redo log | 靠 --single-transaction(InnoDB)或锁表(MyISAM) |
| 适用规模 | 大库(几十 GB 以上)首选 | 中小库(几 GB 以内)、迁移、升级、抽取单表 |
选择原则:日常大库用物理备份(XtraBackup)保证 RTO,同时用逻辑备份保留一份可读快照方便单表恢复与跨版本迁移。两者不是二选一。
1.4 全量备份、增量备份与差异备份
| 类型 | 备份内容 | 备份体积/耗时 | 恢复链 | 恢复复杂度 |
|---|---|---|---|---|
| 全量备份(Full) | 备份全部数据 | 最大、最慢 | 只需最后一次全备 | 最简单 |
| 增量备份(Incremental) | 只备份自上一次备份(全备或增备)以来的变化 | 最小、最快 | 全备 + 所有增备,顺序应用 | 最复杂,链条断一环即失败 |
| 差异备份(Differential) | 只备份自最近一次全备以来的全部变化 | 介于两者之间,越往后越大 | 全备 + 最后一次差备 | 中等 |
恢复链示意(周日全备,周一至周六每日一次):
增量备份(Incremental)
Sun全备 → Mon增 → Tue增 → Wed增 → Thu增 → Fri增 → Sat增
周六故障需恢复:Sun全备 + Mon + Tue + Wed + Thu + Fri + Sat(7 步,任一份损坏则后面全废)
差异备份(Differential)
Sun全备 → Mon差(Sun→Mon) → Tue差(Sun→Tue) → ... → Sat差(Sun→Sat)
周六故障需恢复:Sun全备 + Sat差(2 步,最省心)
MySQL 实践中最常见的组合
每周日 全备(mysqldump -F 或 xtrabackup 全量)
+ 每日 binlog 归档(等价于增量备份,可精确到秒)
MySQL 中三种备份的实现载体:
| 类型 | 逻辑备份实现 | 物理备份实现 |
|---|---|---|
| 全量 | mysqldump -A / mydumper |
xtrabackup --backup / 冷备 |
| 增量 | binlog(配合 -F 每次全备后切换日志) |
xtrabackup --incremental --incremental-basedir=上次备份目录(基于 LSN) |
| 差异 | binlog(从全备时刻起的所有日志) | --incremental-basedir 始终指向全备目录 |
1.5 热备、温备与冷备
| 类型 | 数据库状态 | 业务影响 | 实现方式 | 适用场景 |
|---|---|---|---|---|
| 冷备(Cold Backup) | 完全停止服务 | 业务中断 | systemctl stop mysqld 后 cp -a/tar 数据目录 |
允许停机的窗口期、迁移、版本升级前留底、最可靠的一次性备份 |
| 温备(Warm Backup) | 运行中,但加全局读锁,只能读不能写 | 只读降级,写请求全部阻塞 | FLUSH TABLES WITH READ LOCK + 拷贝/快照;mysqldump --lock-all-tables |
MyISAM 引擎(不支持事务只能锁)、LVM 快照(锁 1~2 秒即可) |
| 热备(Hot Backup) | 运行中,读写均正常 | 几乎无影响(仅额外 IO) | xtrabackup(InnoDB redo 跟踪)、mysqldump --single-transaction(InnoDB MVCC 快照) |
7×24 生产库的日常备份,首选 |
引擎决定了能做到哪一级:
| 引擎 | 冷备 | 温备 | 热备 |
|---|---|---|---|
| InnoDB | 支持 | 支持 | 支持(--single-transaction 或 xtrabackup) |
| MyISAM | 支持 | 支持 | 不支持(无事务、无 redo,必须锁表) |
因此「表要用 InnoDB」不只是为了事务和行锁,也是为了能做在线热备。混合引擎的库(例如
mysql系统库在 5.7 及之前是 MyISAM)无法整库热备,这也是mysqldump --single-transaction备份mysql库时一致性不完全可靠的原因。
1.6 RPO 与 RTO
设计任何备份方案前,必须先和业务方确认这两个指标,否则方案无从评价。
| 指标 | 全称 | 含义 | 通俗理解 | 由什么决定 |
|---|---|---|---|---|
| RPO | Recovery Point Objective 恢复点目标 | 允许丢失多少数据(时间跨度) | 「最多能丢几分钟的数据」 | 备份/日志的频率。只有每日全备则 RPO=24h;有 binlog 实时同步到远端则 RPO≈0 |
| RTO | Recovery Time Objective 恢复时间目标 | 允许多久恢复完成(业务中断时长) | 「最多能停机多久」 | 备份方式与数据量。逻辑备份恢复慢,物理备份快;有热备从库可秒级切换 |
不同方案的量级参考:
| 方案 | RPO | RTO |
|---|---|---|
| 每日一次 mysqldump 全备,无 binlog | 最坏 24 小时 | 小时级(100GB 逻辑恢复可能 5~10 小时) |
| 每日全备 + binlog 本地保留 | 秒级(若磁盘未损坏) | 小时级 |
| 每日全备 + binlog 实时传到异地 | 秒级(磁盘损坏也不丢) | 小时级 |
| 每日 xtrabackup 全备 + binlog | 秒级 | 分钟~小时级(拷回文件即可) |
| 主从复制 + 延迟从库 + 每日备份 | ≈0 | 秒级(切换 VIP) |
关键结论:RPO 靠 binlog,RTO 靠物理备份和从库。花钱前先问业务要 RPO/RTO,再选方案,不要一上来就讨论工具。
1.7 备份策略设计
| 维度 | 建议 | 理由 |
|---|---|---|
| 备份频率 | 全备每日或每周一次(数据量决定);binlog 连续归档,每 5~15 分钟同步一次到远端 | 全备频率决定恢复步数,binlog 频率决定 RPO |
| 备份时间 | 业务低峰(一般 02:00~05:00),避开月结、大促、批量任务 | 备份消耗 IO 和连接,可能拖慢业务 |
| 保留周期 | 常见:日备保留 7 天、周备保留 4 周、月备保留 12 个月;binlog 保留至少覆盖到最老的可用全备之后 | 误操作往往数天后才被发现;只保留一天等于没有回退空间 |
| 异地存储 | 至少一份传到另一机房/对象存储(OSS/S3);本地磁盘 + 远程各一份 | 机房级灾难 |
| 权限隔离 | 备份目录 chmod 700,属主为专用备份用户;对象存储用只写不删的凭据 |
防勒索病毒/攻击者连备份一起删 |
| 加密与压缩 | 敏感数据备份文件加密(gpg/openssl enc);压缩节省 60%~80% 空间 |
备份文件明文含全部业务数据,泄露即拖库 |
| 备份验证 | 每次备份后校验文件大小、gzip -t、tail 检查结尾标记、记录 MD5 |
磁盘满导致的截断备份是最常见的「假备份」 |
| 恢复演练 | 每季度至少一次在独立机器上完整恢复并校验行数,形成书面记录 | 没演练过的方案在真出事时 100% 出问题 |
| 减少对业务影响 | 优先在从库上做备份;主库只保留 binlog 归档;备份时用 ionice/nice/--rate-limit 限速 |
备份是顺序大 IO,会挤占 Buffer Pool 和磁盘带宽 |
| 监控告警 | 备份成功/失败、耗时、体积同比、最新备份年龄都要进监控,失败必须告警到人 | 静默失败的备份最危险 |
从库备份的注意点:
# 在从库上备份,需同时记录从库的复制位点,恢复后才能重新接主
mysqldump -uroot -p -A --single-transaction --dump-slave=2 > /myback/all.sql
# --dump-slave=2 以注释形式写入 CHANGE MASTER TO(对应主库位点)
# --apply-slave-statements 备份的是 slave 时,导入前自动停止 slave,完成后自动启动
1.8 备份前的检查清单
执行任何备份(尤其是首次上线或大库)之前逐项确认:
□ 1. 磁盘空间:备份目标目录剩余空间 > 预估备份体积 × 1.5
df -h /myback ; du -sh /var/lib/mysql
□ 2. 备份账号权限:
逻辑备份需 SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, RELOAD, REPLICATION CLIENT, EVENT
物理备份需 RELOAD, LOCK TABLES, REPLICATION CLIENT, PROCESS, SUPER
□ 3. 引擎确认:是否存在 MyISAM 表(决定能否热备)
SELECT table_schema, table_name, engine FROM information_schema.tables
WHERE engine='MyISAM' AND table_schema NOT IN ('mysql','information_schema','performance_schema');
□ 4. binlog 是否已开启(log_bin=ON),server_id 是否已设置
□ 5. 是否存在长事务 / 长查询(会导致 FTWRL 卡死或 --single-transaction 快照过旧)
SELECT * FROM information_schema.innodb_trx WHERE TIMESTAMPDIFF(SECOND,trx_started,NOW())>60;
SHOW FULL PROCESSLIST;
□ 6. 字符集确认:库/表字符集与备份时 --default-character-set 一致
SHOW VARIABLES LIKE 'character_set%';
□ 7. 是否有存储过程、函数、触发器、事件(默认 mysqldump 不导 routines/events)
□ 8. 备份窗口是否与业务批量任务、其他备份任务冲突(避免 IO 打满)
□ 9. 时钟同步(ntpd/chronyd 正常),否则按时间点恢复会找错位置
□ 10. 恢复目标环境已就绪:版本 ≥ 源版本,字符集/排序规则一致,磁盘足够
二、mysqldump 逻辑备份与恢复
mysqldump 是 MySQL/MariaDB 自带的逻辑备份客户端,本质是「连上数据库,把数据查出来,拼成 SQL 文本输出到标准输出」。因此它可以重定向到文件、管道给 gzip、甚至直接管道给另一台数据库。
2.1 常用参数详解
2.1.1 连接与通用参数
| 参数 | 缩写 | 说明 |
|---|---|---|
--user |
-u |
连接数据库用户名 |
--password |
-p |
连接数据库密码。-p 后紧跟密码不能有空格(-p123456),推荐留空交互输入或写进 ~/.my.cnf |
--host |
-h |
连接的主机,默认 localhost |
--port |
-P |
连接数据库端口号,默认 3306 |
--socket |
-S |
指定 mysql socket 文件位置,默认 /tmp/mysql.sock(CentOS 7 的 MariaDB 常为 /var/lib/mysql/mysql.sock) |
--protocol |
使用的连接协议,如 tcp、socket、pipe、memory(原文档亦列出 udp) |
|
--defaults-file |
指定配置文件,避免命令行明文密码 | |
--version |
-V |
输出 mysqldump 版本信息并退出 |
--help |
显示帮助信息并退出 | |
--force |
导出过程中忽略 SQL 错误,继续导出 | |
--log-error |
将导出错误信息写入指定文件 | |
--verbose |
-v |
输出详细过程 |
密码安全写法:命令行带密码会出现在
ps -ef和~/.bash_history中。生产写法:# ~/.my.cnf 权限必须 chmod 600 [mysqldump] user=backup password=YourPass host=127.0.0.1之后直接
mysqldump -A > all.sql即可,无需再写-u -p。
2.1.2 备份范围参数
| 参数 | 缩写 | 说明 |
|---|---|---|
--all-databases |
-A |
导出全部数据库(全备),含 mysql 系统库 |
--databases |
-B |
导出指定数据库,可指定多个;输出中会带 CREATE DATABASE 和 USE 语句 |
| (直接写库名 表名) | mysqldump db t1 t2:只导出 db 库的 t1、t2 表,输出中不含建库和 USE 语句 |
|
--tables |
导出指定表,须配合 -B 使用(-B db --tables t1) |
|
--ignore-table |
不导出指定表,格式 库名.表名;忽略多个表需多次使用该参数 |
|
--where |
-w |
添加条件语句,只导出符合条件的数据 |
--no-data |
-d |
仅导出表结构,不导出数据 |
--no-create-info |
-t |
仅导出数据,不导出表结构(建表语句) |
--no-create-db |
-n |
导出数据但不导出建库语句(CREATE DATABASE) |
-B 与直接写库名的区别,是恢复时最容易踩的坑:
# 写法一:带 -B,dump 文件里有 CREATE DATABASE IF NOT EXISTS `m2`; USE `m2`;
mysqldump -uroot -p -B m2 > /myback/m2.sql
mysql -uroot -p < /myback/m2.sql # 直接恢复,自动建库并进入 m2
# 写法二:不带 -B,dump 文件里只有建表和 INSERT
mysqldump -uroot -p m2 > /myback/m2_tables.sql
mysql -uroot -p m2 < /myback/m2_tables.sql # 必须手动指定目标库,且库要已存在
# 好处:可以恢复到另一个库名,做数据克隆或改名
mysql -uroot -p m2_new < /myback/m2_tables.sql
2.1.3 一致性与锁参数(最关键)
| 参数 | 缩写 | 说明 |
|---|---|---|
--single-transaction |
InnoDB 热备的核心参数。开始时执行 START TRANSACTION WITH CONSISTENT SNAPSHOT,在一个可重复读事务的一致性快照中导出,全程不锁表,业务读写不受影响 |
|
--lock-all-tables |
-x |
导出前对所有库所有表加全局读锁(内部即 FLUSH TABLES WITH READ LOCK)直到导出结束,保证一致性,但全库只读(温备) |
--lock-tables |
-l |
默认开启(--opt 含)。逐库锁表读,只保证单库内一致,库之间不一致;MyISAM 用它 |
--skip-lock-tables |
关闭锁表,无一致性保证,仅用于允许脏读的场景 | |
--add-locks |
导出的 SQL 中在 INSERT 前后加 LOCK TABLES/UNLOCK TABLES,加快导入速度(默认开启,可用 --skip-add-locks 关闭) |
|
--flush-logs |
-F |
导出前刷新(切换)bin-log。一次导出多个库会多次刷新 bin-log;同时使用 --lock-all-tables 或 --master-data 时不会多次刷新 |
--master-data[=1\|2] |
在 dump 文件中记录当前 binlog 文件名与位置。=1 写成可执行的 CHANGE MASTER TO ...;=2 写成注释形式。做时间点恢复和搭从库必加 |
|
--dump-slave[=1\|2] |
备份从库时记录主库的位点(而非从库自身位点) | |
--apply-slave-statements |
备份的是 slave 时,导入前自动 STOP SLAVE,完成后自动 START SLAVE |
|
--safe-slave-backup |
备份前停止 SQL 线程,保证 slave 状态一致 |
一致性原理与引擎差异(面试与生产都必须讲清):
| 参数 | InnoDB | MyISAM | 是否锁业务 | 一致性 |
|---|---|---|---|---|
--single-transaction |
有效,MVCC 快照读,导出期间看到的是同一时刻的数据 | 无效(MyISAM 不支持事务,导出的是各表不同时刻的数据) | 不锁 | InnoDB 完全一致 |
--lock-all-tables |
有效 | 有效 | 全库只读,写请求全部阻塞 | 全库一致 |
--lock-tables(默认) |
有效但降级 | 有效 | 逐库只读 | 仅单库内一致 |
# InnoDB 库标准热备命令(生产模板)
mysqldump -uroot -p --single-transaction --master-data=2 --flush-logs \
--routines --triggers --events --hex-blob \
--default-character-set=utf8 -A > /myback/all_$(date +%F).sql
# 含 MyISAM 表的库只能温备
mysqldump -uroot -p --lock-all-tables --master-data=2 -A > /myback/all.sql
重要陷阱:
--single-transaction与--lock-all-tables互斥,同时写只有后者生效。--single-transaction期间若执行了 DDL(ALTER/TRUNCATE/DROP),会导致备份出错或不一致,因为 DDL 会隐式提交并使快照失效。备份窗口内禁止 DDL。--single-transaction会开启一个长事务,若备份耗时数小时,undo 无法清理、ibdata1会膨胀,历史版本链变长使查询变慢——这是大库不适合用 mysqldump 的重要原因。
2.1.4 对象与内容控制参数
| 参数 | 缩写 | 说明 |
|---|---|---|
--routines |
-R |
导出存储过程和函数(默认不导出,极易遗漏) |
--triggers |
导出触发器(默认开启,可用 --skip-triggers 关闭) |
|
--events |
-E |
导出事件调度器中的事件(默认不导出) |
--hex-blob |
把 BINARY/VARBINARY/BLOB/BIT 列导成十六进制(如 0x1F8B),防止二进制数据在文本传输中损坏,有二进制列时必加 |
|
--default-character-set |
设置导出字符集,默认 utf8。必须与库字符集一致,否则中文乱码 |
|
--set-charset |
在 dump 文件中写入 SET NAMES(默认开启) |
|
--add-drop-database |
导入时先 DROP DATABASE IF EXISTS(危险,会删掉目标库) |
|
--add-drop-table |
导入时先 DROP TABLE IF EXISTS(默认开启,可用 --skip-add-drop-table 关闭) |
|
--add-drop-trigger |
导入时先删除同名触发器 | |
--complete-insert |
-c |
INSERT 语句中列出所有列名,可读性好、列顺序变化也能导入,但文件更大 |
--extended-insert |
-e |
多行合并为一条 INSERT(默认开启),导入速度提升数倍;关闭后每行一条 INSERT,便于 grep 单行修复 |
--compact |
精简输出:去掉注释、SET 语句、结构说明,只留核心 SQL,便于阅读和 diff(不适合直接用于生产恢复) |
|
--compress |
-C |
客户端与服务端之间传输压缩(远程备份省带宽,不是压缩输出文件) |
--quick |
-q |
逐行取数据而非一次性读入内存(默认开启,大表必需) |
--set-gtid-purged=OFF/ON/AUTO |
控制是否在 dump 中写入 SET @@GLOBAL.GTID_PURGED='...'。恢复到已有 GTID 的实例时必须 =OFF,否则报错;搭建从库时用 =ON |
|
--dump-date |
导出文件中添加导出时间,默认开启(可用 --skip-dump-date 关闭,便于 diff 两次备份) |
|
--xml |
-X |
将数据导出为 XML 格式 |
--tab=目录 |
-T |
每表导出为 .sql(结构)+ .txt(TAB 分隔数据),可配合 LOAD DATA INFILE 高速导入 |
--opt |
默认开启的组合选项,等价于 --add-drop-table --add-locks --create-options --disable-keys --extended-insert --lock-tables --quick --set-charset。用 --skip-opt 可一次性全部关闭 |
|
--disable-keys |
-K |
在导入数据前后加 ALTER TABLE ... DISABLE/ENABLE KEYS,导入完成后一次性重建索引,比逐行维护索引快得多(仅对 MyISAM 有效) |
--create-options |
建表语句中保留 ENGINE、AUTO_INCREMENT、CHARSET 等选项(默认开启) |
|
--max-allowed-packet |
客户端单个包上限,大 BLOB 行导出/导入报错时调大,如 --max-allowed-packet=512M |
|
--net-buffer-length |
每条 extended-insert 的目标长度,默认 1M,调大可减少语句条数 |
2.2 命令示例汇总
全备所有数据库:
mysqldump -uroot -p -A > /mysqlback/mysql_all.sql
全备到指定目录:
mysqldump -uroot -p -A > /myback/mysql_all_back.sql
导出指定数据库(可多个):
mysqldump -uroot -p -B m2 test mysql a b
使用 tcp 协议导出:
mysqldump -uroot -p -B m2 --protocol=tcp
仅导出数据不导出表结构:
mysqldump -uroot -p -B m2 -t
仅导出表结构不导出数据:
mysqldump -uroot -p -A -d
导出指定表:
mysqldump -uroot -p -B m2 --tables test
按条件导出:
mysqldump -uroot -p -B m2 --where="user='root'"
# 更多示例:只导最近 7 天数据、只导前 1000 行
mysqldump -uroot -p m2 orders --where="create_time >= '2026-08-01'" > /myback/orders_aug.sql
mysqldump -uroot -p m2 orders --where="1=1 LIMIT 1000" > /myback/orders_sample.sql
导出前刷新 bin-log:
mysqldump -uroot -p -B m2 -F
锁定所有表后导出:
mysqldump -uroot -p -B m2 --lock-all-tables
错误日志写入文件:
mysqldump -uroot -p -B m2 --log-error=/root/mysqldump_err.log
导出为 XML:
mysqldump -uroot -p -B m2 --xml
忽略多张表:
mysqldump -uroot -p -B m2 --ignore-table=m2.a --ignore-table=m2.b
导出时直接压缩(省 70% 空间,生产常用):
mysqldump -uroot -p --single-transaction -A | gzip > /myback/all_$(date +%F).sql.gz
# 恢复
gunzip < /myback/all_2026-08-22.sql.gz | mysql -uroot -p
zcat /myback/all_2026-08-22.sql.gz | mysql -uroot -p
不落地直接迁移到另一台(跨机克隆):
mysqldump -uroot -p --single-transaction -B m2 | mysql -uroot -p -h 192.168.2.20
只导出存储过程与函数(不导数据):
mysqldump -uroot -p -B m2 --routines --no-create-info --no-data --no-create-db --skip-opt > /myback/routines.sql
2.3 导出文件的内容结构解读
打开一个 dump 文件,从头到尾依次是:
-- MySQL dump 10.14 Distrib 5.5.60-MariaDB, for Linux (x86_64)
-- Host: localhost Database: m2
-- ------------------------------------------------------
-- Server version 5.5.60-MariaDB
-- ① 保存客户端环境,恢复完再还原回去
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
-- ② 导入期间关闭唯一性检查与外键检查以提速(mysqldump 自动加)
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- ③ --master-data=2 生成的位点信息(注释形式,这是做 PITR 的起点)
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=245;
-- ④ 建库(仅 -B 或 -A 时出现)
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `m2` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `m2`;
-- ⑤ 逐表:DROP → CREATE → 锁表 → 关索引 → INSERT → 开索引 → 解锁
DROP TABLE IF EXISTS `a`;
CREATE TABLE `a` (
`id` int(2) DEFAULT NULL,
`name` char(10) DEFAULT NULL,
`sex` char(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `a` WRITE;
/*!40000 ALTER TABLE `a` DISABLE KEYS */;
INSERT INTO `a` VALUES (1,'zhangsan','f'),(2,'lisi','m'); -- extended-insert 合并成一条
/*!40000 ALTER TABLE `a` ENABLE KEYS */;
UNLOCK TABLES;
-- ⑥ 还原客户端环境
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
-- Dump completed on 2026-08-22 10:15:32 ← ⑦ 结尾标记
要点:
/*!40101 ... */是版本化注释:只有版本号 ≥ 4.1.1 的服务器才执行,其他数据库当普通注释跳过,因此保证了跨版本兼容。-- Dump completed on ...是备份完整性的标志。校验备份最快的方法:tail -1 /myback/all.sql | grep -q "Dump completed" && echo OK || echo "备份不完整!" # 压缩包 zcat /myback/all.sql.gz | tail -1--master-data生成的CHANGE MASTER TO行记录了全备结束时刻的 binlog 位置,是增量恢复的起点,必须保留。
2.4 恢复方式
2.4.1 四种基本恢复方法
# 方法一:shell 重定向(最常用,适合 -A / -B 的备份)
mysql -uroot -p < /myback/all.sql
mysql < /myback/all.sql # 若 ~/.my.cnf 已配好凭据
# 方法二:指定目标库恢复(备份时未加 -B)
mysql -uroot -p m2 < /myback/m2_tables.sql
mysql -uroot -p -D m2 < /myback/m2_tables.sql
# 方法三:登录后用 source(可看到执行过程,适合交互排错)
mysql -uroot -p
mysql> SET NAMES utf8;
mysql> CREATE DATABASE IF NOT EXISTS m2 DEFAULT CHARSET utf8;
mysql> USE m2;
mysql> SOURCE /myback/m2_tables.sql;
mysql> \. /myback/m2_tables.sql -- source 的简写
# 方法四:压缩包直接恢复(不解压落地,省磁盘)
zcat /myback/all.sql.gz | mysql -uroot -p
gunzip -c /myback/all.sql.gz | mysql -uroot -p
mysql < 与 source 的差异:
| 对比 | mysql < file |
SOURCE file |
|---|---|---|
| 执行位置 | shell 中,客户端逐条读取发送 | 已登录的 mysql 会话内 |
| 出错行为 | 默认遇错即停(可加 -f 强制继续) |
默认报错后继续执行后续语句 |
| 进度可见 | 不显示,安静执行 | 逐条打印 Query OK,海量语句会刷屏拖慢 |
| 大文件性能 | 更快,推荐 | 慢(每条都回显) |
| 路径 | shell 路径,支持管道 | 服务端客户端所在机器的路径,不支持管道 |
2.4.2 恢复单库、单表的技巧
# ① 从全备中提取单个库(全备是 -A 生成的,库之间由 USE 语句分隔)
sed -n '/^-- Current Database: `m2`/,/^-- Current Database: `next_db`/p' all.sql > m2.sql
# ② 更通用:用 awk 按 USE 语句切分
awk '/^USE `m2`/,/^USE `(?!m2)/' all.sql > m2.sql # gawk 正则受限,实际常用下面这条
sed -n "/^-- Current Database: \`m2\`/,/^-- Current Database:/p" all.sql | head -n -1 > m2.sql
# ③ 从全备中提取单张表的结构
sed -n '/^CREATE TABLE `a`/,/^) ENGINE=/p' all.sql
# ④ 从全备中提取单张表的数据(结构 + INSERT)
sed -n '/^-- Table structure for table `a`/,/^-- Table structure for table/p' all.sql > a.sql
# ⑤ 只抓某表的 INSERT 行
grep -n "^INSERT INTO \`a\`" all.sql > a_data.sql
# ⑥ 恢复到临时库后再挑数据(最安全,强烈推荐的生产做法)
mysql -uroot -p -e "CREATE DATABASE recover_tmp DEFAULT CHARSET utf8;"
mysql -uroot -p recover_tmp < m2.sql
mysql -uroot -p -e "INSERT INTO m2.a SELECT * FROM recover_tmp.a WHERE id=3;"
mysql -uroot -p -e "DROP DATABASE recover_tmp;"
生产红线:绝不在生产库上直接
mysql < all.sql做单表恢复。全备里含DROP TABLE IF EXISTS甚至DROP DATABASE,会把误删之后产生的正常数据一起覆盖掉。正确姿势永远是「恢复到临时库 → 校验 → 只搬需要的数据」。
2.4.3 加速恢复:关闭 binlog、外键与唯一性检查
逻辑恢复慢的三大原因:逐条执行 SQL、每行维护索引、每条语句写 binlog。对应的加速手段:
-- 在恢复会话中执行(登录 mysql 后先设置,再 source)
SET SESSION SQL_LOG_BIN = 0; -- 恢复不写 binlog,减少一半 IO(注意:有从库时会导致主从不一致!)
SET FOREIGN_KEY_CHECKS = 0; -- 关闭外键检查,同时解决「父表还没恢复」的报错
SET UNIQUE_CHECKS = 0; -- 关闭唯一性检查(前提是确信数据本身无重复)
SET AUTOCOMMIT = 0; -- 关闭自动提交,批量后统一 COMMIT
SET GLOBAL innodb_flush_log_at_trx_commit = 2; -- 临时降低刷盘频率(恢复完必须改回 1)
SOURCE /myback/all.sql;
COMMIT;
SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;
SET SESSION SQL_LOG_BIN = 1;
SET GLOBAL innodb_flush_log_at_trx_commit = 1; -- 务必还原,否则宕机丢数据
命令行一次性写法:
( echo "SET SQL_LOG_BIN=0; SET FOREIGN_KEY_CHECKS=0; SET UNIQUE_CHECKS=0; SET AUTOCOMMIT=0;" ; \
cat /myback/all.sql ; \
echo "COMMIT; SET UNIQUE_CHECKS=1; SET FOREIGN_KEY_CHECKS=1;" ) | mysql -uroot -p
配置文件层面的临时提速(恢复大库前改,恢复后改回):
[mysqld]
innodb_buffer_pool_size = 物理内存的 60%~70%
innodb_flush_log_at_trx_commit = 2
innodb_log_file_size = 512M # 大 redo 减少 checkpoint 次数
sync_binlog = 0
skip-log-bin # 恢复期间彻底关闭 binlog(无从库时)
max_allowed_packet = 512M
net_read_timeout = 3600
外键检查关闭还有一个功能性作用:dump 文件里表的顺序是字母序,子表可能排在父表之前,若不关外键检查会直接报
errno 150/Cannot add or update a child row。
2.5 分库分表导出脚本
按库、按表拆成独立文件,恢复时可精确挑选,也便于并行:
#!/bin/bash
# 按库分别导出,每库一个文件
USER=root
PASS=YourPass
BACKDIR=/myback/$(date +%F)
mkdir -p $BACKDIR
# 取出所有业务库(排除系统库)
DBS=$(mysql -u$USER -p$PASS -N -e "SHOW DATABASES;" \
| grep -Ev "^(information_schema|performance_schema|mysql|sys|test)$")
for db in $DBS; do
echo "正在备份库:$db"
mysqldump -u$USER -p$PASS --single-transaction --master-data=2 \
--routines --triggers --events --hex-blob \
--default-character-set=utf8 -B "$db" | gzip > "$BACKDIR/${db}.sql.gz"
done
#!/bin/bash
# 按表分别导出(大库定位单表恢复极方便)
USER=root; PASS=YourPass; DB=m2
BACKDIR=/myback/$(date +%F)/$DB
mkdir -p $BACKDIR
TABLES=$(mysql -u$USER -p$PASS -N -e "SHOW TABLES FROM $DB;")
for tb in $TABLES; do
mysqldump -u$USER -p$PASS --single-transaction "$DB" "$tb" | gzip > "$BACKDIR/${tb}.sql.gz"
done
# 结构与数据分离导出,便于比对结构变更
mysqldump -u$USER -p$PASS -d -B $DB > $BACKDIR/${DB}_schema.sql # 只有结构
mysqldump -u$USER -p$PASS -t -B $DB > $BACKDIR/${DB}_data.sql # 只有数据
并行导出多个库(提升速度,注意别把 IO 打满):
echo "$DBS" | xargs -n1 -P4 -I{} sh -c \
'mysqldump -uroot -pYourPass --single-transaction -B {} | gzip > /myback/{}.sql.gz'
2.6 mysqldump 的性能问题与大库替代方案
mysqldump 的固有短板:
| 问题 | 说明 |
|---|---|
| 单线程 | 只有一个连接串行导出,无法利用多核与多磁盘 |
| 恢复更慢 | 恢复要逐条 INSERT 并重建全部索引,经验值:导出 1 小时的库,恢复可能要 4~10 小时 |
| 长事务副作用 | --single-transaction 期间 undo 不能清理,ibdata1 膨胀、历史链变长 |
| 不适合超大库 | 一般界线:< 50GB 用 mysqldump,> 50GB 用物理备份(XtraBackup) |
| DDL 冲突 | 备份窗口内的 DDL 会破坏快照 |
替代方案对比:
| 工具 | 类型 | 特点 | 适用 |
|---|---|---|---|
| mydumper / myloader | 逻辑,多线程 | 按表/按块并行导出与导入,自带压缩、正则筛选、记录 binlog 位点;速度可达 mysqldump 的数倍。见本文第三章 | 中大型库的逻辑备份/迁移 |
| mysqlpump | 逻辑,多线程 | MySQL 5.7 官方自带(mysqlpump),支持并行、进度显示、--exclude-*/--include-* 过滤、导出后压缩(ZLIB/LZ4)。缺点:不支持一致性快照跨表(早期版本),MySQL 8.0 起官方转向 mysqlshell |
5.7 环境的快速逻辑导出 |
| MySQL Shell util.dumpInstance() | 逻辑,多线程 | MySQL 8.0 官方推荐,支持并行 dump/load、断点续传、直传 OSS/S3 | 8.0 生产环境 |
| XtraBackup | 物理,热备 | 大库首选,支持增量、压缩、并行、流式传输。见本文第五章 | 100GB ~ TB 级 |
| LVM 快照 | 物理 | 秒级完成,锁表 1~2 秒 | 数据目录在 LVM 上的场景 |
# mysqlpump 示例(MySQL 5.7)
mysqlpump -uroot -p --default-parallelism=4 --all-databases > /myback/all_pump.sql
mysqlpump -uroot -p --exclude-databases=mysql,sys --compress-output=LZ4 \
--result-file=/myback/all.lz4
# 只导指定库的指定表
mysqlpump -uroot -p --include-tables=a,b m2 > /myback/m2_ab.sql
2.7 实操演练:mysqldump + binlog 增量备份与恢复
以上 mysqldump 备份均为全备方式。实现增量备份最简单的方法是通过 mysqldump 配合 bin-log。
原理: 使用 --flush-logs(-F)参数,每次全备后刷新一次 bin-log,全备后的新操作都记录到新的 bin-log 中,从而实现「全备文件 + 一个完整的新 binlog 文件 = 全量 + 增量」。
第 1 步:准备备份目录与测试数据
mkdir /myback
create database m2;
create table m2.a (id int(2),name char(10),sex char(1));
insert into m2.a values (1,'zhangsan','f'),(2,'lisi','m');
select * from m2.a;
+------+----------+------+
| id | name | sex |
+------+----------+------+
| 1 | zhangsan | f |
| 2 | lisi | m |
+------+----------+------+
第 2 步:第一次全备(同时切换 binlog)
mysqldump -uroot -p -B m2 -F > /myback/all.sql
# 确认切换后的新日志文件(后续所有变更都记在这里)
mysql -uroot -p -e "SHOW MASTER STATUS;"
第 3 步:全备后产生新数据(这部分只存在于 binlog 中)
insert into m2.a values (3,'wangwu','f');
第 4 步:将最新的 bin-log 备份出去
cp /var/lib/mysql/mysql-bin.000002 /myback
生产中此处应先
FLUSH LOGS让当前日志封口,再拷贝已封口的日志,避免拷到正在写入的文件。
第 5 步:模拟误删库
drop database m2;
第 6 步:恢复全备
mysql < /myback/all.sql
select * from m2.a;
+------+----------+------+
| id | name | sex |
+------+----------+------+
| 1 | zhangsan | f |
| 2 | lisi | m |
+------+----------+------+
此时只回到了全备时刻,wangwu 还没有。
第 7 步:恢复增量备份(应用 binlog)
mysqlbinlog /myback/mysql-bin.000002 | mysql -p
select * from m2.a;
+------+----------+------+
| id | name | sex |
+------+----------+------+
| 1 | zhangsan | f |
| 2 | lisi | m |
| 3 | wangwu | f |
+------+----------+------+
数据完整恢复,演练结束。
注意:本例中
drop database m2;这条语句也被记录在mysql-bin.000002里。之所以直接回放没出问题,是因为drop发生在拷贝 binlog 之后,拷走的副本里不含它。若日志里含误操作语句,必须用--start-position/--stop-position跳过——完整方法见第四章 4.7。
第 8 步:验证清单
-- 库表是否齐全
SHOW DATABASES;
SHOW TABLES FROM m2;
-- 行数是否与故障前一致
SELECT COUNT(*) FROM m2.a;
-- 表结构、索引是否完整
SHOW CREATE TABLE m2.a\G
-- 存储过程/函数/触发器/事件是否恢复(备份时若漏加 -R -E 此处会为空)
SHOW PROCEDURE STATUS WHERE Db='m2';
SHOW TRIGGERS FROM m2;
SHOW EVENTS FROM m2;
-- 表是否有损坏
CHECK TABLE m2.a;
三、mydumper / myloader 多线程逻辑备份
mydumper 是第三方多线程逻辑备份工具,弥补了 mysqldump 单线程的缺陷:按表(大表还可按行块)并行导出,自带压缩与正则筛选,并在 metadata 文件中记录 binlog 位点。
3.1 安装 mydumper
安装依赖:
yum install glib2-devel mysql-devel zlib-devel pcre-devel openssl-devel gcc gcc-c++ -y
yum install -y cmake
下载源码包:
wget https://launchpadlibrarian.net/185032423/mydumper-0.6.2.tar.gz
# 或内网源
wget http://192.168.2.254/mysql/pack/mydumper-0.6.2.tar.gz
编译安装:
tar xf ./mydumper-0.6.2.tar.gz -C /usr/src/
cd /usr/src/mydumper-0.6.2
mkdir ./bin && cd bin
cmake ../
make
ll mydumper myloader
-rwxr-xr-x 1 root root 184624 mydumper
-rwxr-xr-x 1 root root 53824 myloader
看到这两个文件即表示编译成功。可再做软链接方便调用:
ln -s /usr/src/mydumper-0.6.2/bin/mydumper /usr/bin/mydumper
ln -s /usr/src/mydumper-0.6.2/bin/myloader /usr/bin/myloader
3.2 mydumper 常用参数
| 参数 | 缩写 | 说明 |
|---|---|---|
--host |
-h |
主机 |
--user |
-u |
用户(-u 与用户名之间必须有空格) |
--password |
-p |
密码 |
--port |
-P |
端口 |
--socket |
-S |
socket 文件 |
--outputdir |
-o |
备份到指定目录 |
--database |
-B |
备份指定数据库 |
--tables-list |
-T |
备份指定表,多个以逗号分隔 |
--compress |
-c |
启用压缩备份(生成 .gz) |
--regex |
-x |
使用正则匹配备份表或库 |
--build-empty-files |
-e |
表无数据时仍备份空文件 |
--no-schemas |
-m |
只备份表数据,不备份表结构 |
--logfile |
-L |
备份错误信息追加到指定文件 |
--kill-long-queries |
-K |
备份时杀掉长查询 |
--lock-all-tables |
备份前锁定所有表,完成后自动解锁 | |
--threads |
-t |
并发线程数,默认 4,一般设为 CPU 核数 |
--rows |
-r |
按行数切分大表,实现单表内并行 |
--long-query-guard |
长查询判定阈值(秒),默认 60 | |
--no-locks |
不加锁(无一致性保证,但完全不影响业务) |
3.3 mydumper 使用示例
备份指定数据库到目录:
mydumper -u root -p -B m2 -o /myback
# 或
mydumper -u root -p --database=m2 --outputdir=/myback
注意:-u 选项和用户名之间必须有一个以上空格,否则会报错。
备份目录内容:
m2.a-schema.sql 存放表结构
m2.a.sql 存放表数据
metadata 存放 master 日志名及初始位置
metadata 文件内容示例(做增量恢复的起点):
Started dump at: 2026-08-22 02:00:01
SHOW MASTER STATUS:
Log: mysql-bin.000006
Pos: 1245
GTID:
Finished dump at: 2026-08-22 02:03:47
备份指定表:
mydumper -u root -p -B m2 -T b -o /myback
压缩备份:
mydumper -u root -p -B m2 -c -o /myback
备份后文件为 .gz 压缩格式。
正则匹配备份:
# 只备份 mysql 开头的表
mydumper -u root -p -B m2 -o /myback -x "mysql_*"
# 同时备份 mysql_* 和 *_master 的表
mydumper -u root -p -B m2 -o /myback -x "m2.mysql_*|m2.*_master"
# 备份 a 开头的表
mydumper -B m2 -o /myback/ -x 'a.'
空表也生成备份文件:
mydumper -u root -p -B m2 -T abc -e -o /myback
不备份表结构:
mydumper -B m2 -m -o /myback/
备份时记录日志:
mydumper -u root -p -B m2 -o /myback -L /myback/sql_back.log
杀掉长查询后备份:
mydumper -u root -p -B m2 -o /myback -K
工作环境中难免存在长查询,长查询存在时无法锁表,可能导致数据不一致。备份时建议先杀掉长查询、锁表后再备份。
锁表备份:
mydumper -u root -p -B m2 -o /myback --lock-all-tables
多线程 + 分块 + 压缩的生产写法:
mydumper -u root -p -h 127.0.0.1 -B m2 -t 8 -r 500000 -c \
-o /myback/$(date +%F) -L /myback/mydumper.log
3.4 myloader 数据恢复
常用参数:
| 参数 | 缩写 | 说明 |
|---|---|---|
--host |
-h |
指定还原主机 |
--user |
-u |
指定用户 |
--password |
-p |
用户密码 |
--port |
-P |
指定端口 |
--socket |
-S |
指定 socket 文件路径 |
--directory |
-d |
指定备份文件存放路径 |
--overwrite-tables |
-o |
恢复时表存在则先 drop(备份中需包含表结构) |
--database |
-B |
指定还原到哪个数据库 |
--enable-binlog |
-e |
还原时一并还原二进制日志 |
--threads |
-t |
并发恢复线程数 |
--queries-per-transaction |
-q |
每个事务包含的语句数,默认 1000 |
恢复示例:
# 还原 /myback 目录下所有备份数据
myloader -u root -p -d /myback
# 恢复时先 drop 已存在的表
myloader -u root -p -o -d /myback
# 还原到指定数据库
myloader -u root -p -B m2 -d /myback
# 还原时一并恢复二进制日志
myloader -u root -p -e -d /myback
# 多线程恢复
myloader -u root -p -d /myback -t 8 -o -B m2
四、binlog 与基于时间点恢复(PITR)
4.1 二进制日志的作用
二进制日志(binary log,简称 binlog)记录所有引起数据变化的语句或行变更(INSERT/UPDATE/DELETE/DDL),不记录纯查询(SELECT/SHOW)。它工作在 Server 层,与引擎无关。
三个核心日志的对比(初学者最易混淆):
| 日志 | 层次 | 记录内容 | 用途 |
|---|---|---|---|
undo log |
InnoDB 引擎层 | 修改前的数据(逻辑日志) | 事务回滚 + MVCC 读旧版本 |
redo log |
InnoDB 引擎层 | 数据页的物理修改(ib_logfile*) |
崩溃恢复(crash recovery),保证已提交事务不丢 |
binlog |
Server 层 | 逻辑 SQL 或行变更 | 主从复制 + 时间点恢复(PITR) |
binlog 的三大用途:
| 用途 | 说明 |
|---|---|
| 增量备份 / 时间点恢复 | 全备只能回到备份时刻,binlog 让你能恢复到故障前一秒 |
| 主从复制 | 主库把 binlog 发给从库,从库重放实现数据同步(Binlog Dump 线程) |
| 审计与数据订阅 | 通过解析 binlog 追查谁改了数据;Canal/Maxwell/Debezium 等据此做实时数据同步到 ES/Kafka |
因为 DDL 不能回滚(
DROP TABLE打错表名无法ROLLBACK),且 MySQL 默认autocommit=1使误删无法回滚,「全备 + binlog」是误操作唯一的救命通道。事务与自动提交的细节见11-04-索引事务与状态查询.md。
4.2 三种 binlog 格式
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT(SBR) | 记录原始 SQL 语句本身 | 日志量最小;可读性最好;只记一条语句即可代表百万行变更 | 不安全:含 NOW()、UUID()、RAND()、LAST_INSERT_ID()、用户自定义函数、LIMIT 无序更新等不确定性语句时,主从/恢复后结果不同;RC 隔离级别下可能主从不一致 |
| ROW(RBR,默认且推荐) | 记录每一行修改前后的镜像(before image / after image) | 绝对安全,与语句无关,恢复/复制结果必然一致;可做数据审计、可反向生成回滚 SQL(闪回) | 日志量大(UPDATE t SET x=1 影响百万行会产生百万条记录);需 --base64-output=decode-rows -v 才能读懂 |
| MIXED(MBR) | 默认用 STATEMENT,MySQL 判定语句「不安全」时自动切换为 ROW | 兼顾体积与安全 | 切换规则由 MySQL 内部判定,行为不够可预期;排查问题时两种格式混杂 |
选择建议:
生产统一使用 ROW。理由:
① 数据一致性优先于日志体积(磁盘便宜,数据无价);
② 隔离级别改 RC(降低死锁)的前提就是 binlog_format=ROW;
③ 误删数据的「闪回恢复」只有 ROW 格式能做(有前后镜像才能反向生成 SQL);
④ Canal 等数据订阅工具只支持 ROW。
MySQL 5.7.7 起默认即为 ROW;5.6 及 MariaDB 部分版本默认 STATEMENT,需手工修改。
-- 查看与修改(不重启,只对新会话/新事务生效)
SHOW VARIABLES LIKE 'binlog_format';
SET GLOBAL binlog_format = 'ROW'; -- 全局,需 SUPER 权限;已有连接不受影响
SET SESSION binlog_format = 'ROW'; -- 仅当前会话
4.3 开启与配置 binlog
CentOS 7 / MySQL 5.7 / MariaDB 10.x:
# /etc/my.cnf 或 /etc/my.cnf.d/server.cnf
[mysqld]
# ① 开启 binlog:值为文件名前缀,可含路径
log_bin = /var/lib/mysql/mysql-bin
# MariaDB 亦可写 log-bin=mysql-bin
# ② server_id 必须设置且全局唯一,MySQL 5.7 开 binlog 时不设会启动失败
server_id = 1
# ③ 格式
binlog_format = ROW
binlog_row_image = FULL # FULL(默认,记录所有列) / MINIMAL(只记主键与改动列) / NOBLOB
# ④ 单个 binlog 文件大小上限,写满自动切换,默认 1G
max_binlog_size = 512M
# ⑤ 自动清理策略(二者只用其一)
expire_logs_days = 7 # MySQL 5.7 / MariaDB:保留天数,0 为不自动删
# binlog_expire_logs_seconds = 604800 # MySQL 8.0:秒为单位,优先级高于 expire_logs_days
# ⑥ 刷盘策略
sync_binlog = 1 # 1=每次提交都 fsync(最安全,生产必须)
# ⑦ binlog 缓存
binlog_cache_size = 1M # 每个会话的事务 binlog 缓存
max_binlog_cache_size = 2G
# ⑧ 只记录/忽略指定库(谨慎使用,跨库语句会漏记)
# binlog_do_db = m2
# binlog_ignore_db = mysql
# ⑨ 让从库也记录自己重放的日志(级联复制或从库做备份时必须)
log_slave_updates = 1
CentOS 6 / MySQL 5.5 的差异:
# CentOS 6 上服务名与配置文件路径
# 配置文件:/etc/my.cnf(同 7);服务管理用 SysV 脚本
[mysqld]
log-bin=mysql-bin # 5.5 常见写法用连字符
server-id=1
binlog_format=MIXED # 5.5/5.6 默认 STATEMENT,建议至少改 MIXED,能改 ROW 更好
expire_logs_days=7
# 5.5 没有 binlog_row_image、没有 GTID(5.6 才引入)、没有 binlog_expire_logs_seconds
# CentOS 6:SysV 方式重启并设置开机自启
service mysqld restart
chkconfig mysqld on
chkconfig --list mysqld
# CentOS 7:systemd 方式
systemctl restart mysqld # MariaDB 为 systemctl restart mariadb
systemctl enable mysqld
systemctl status mysqld
版本差异汇总表:
| 特性 | MySQL 5.5 / CentOS 6 | MySQL 5.6 | MySQL 5.7 / CentOS 7 | MySQL 8.0 | MariaDB 10.x |
|---|---|---|---|---|---|
默认 binlog_format |
STATEMENT | STATEMENT | ROW(5.7.7+) | ROW | 10.2 起 MIXED,10.5+ 建议 ROW |
开 binlog 是否强制 server_id |
否(默认 0 也能启动) | 否 | 是,不设启动失败 | 是 | 否,但强烈建议设 |
binlog_row_image |
无 | 有 | 有 | 有 | 10.1+ 有 |
| GTID | 无 | 有(gtid_mode) |
有 | 默认可开 | 自有 GTID 实现,格式不同 |
expire_logs_days |
有 | 有 | 有(8.0 起废弃) | 用 binlog_expire_logs_seconds |
有 |
| 服务管理 | service / chkconfig |
— | systemctl |
systemctl |
systemctl mariadb |
| 系统库引擎 | MyISAM | MyISAM | MyISAM(8.0 才转 InnoDB) | InnoDB | Aria/MyISAM |
验证是否开启成功:
SHOW VARIABLES LIKE 'log_bin'; -- ON 表示已开启
SHOW VARIABLES LIKE 'log_bin_basename'; -- 日志路径前缀
SHOW VARIABLES LIKE 'binlog%';
SHOW VARIABLES LIKE 'server_id';
SELECT @@sql_log_bin; -- 当前会话是否写 binlog
4.4 binlog 文件与索引文件
ls -l /var/lib/mysql/mysql-bin.*
-rw-rw---- 1 mysql mysql 1073741952 mysql-bin.000001 ← 已写满并切换
-rw-rw---- 1 mysql mysql 245 mysql-bin.000002 ← 正在写入的当前日志
-rw-rw---- 1 mysql mysql 57 mysql-bin.index ← 索引文件:所有 binlog 的清单
cat /var/lib/mysql/mysql-bin.index
/var/lib/mysql/mysql-bin.000001
/var/lib/mysql/mysql-bin.000002
严禁用
rm直接删除 binlog 文件!mysql-bin.index中仍保留记录,会导致SHOW BINARY LOGS报错、PURGE失败、从库找不到日志而复制中断。必须用PURGE BINARY LOGS命令删除(见 4.6)。
4.5 binlog 查看命令
-- ① 列出服务器上所有 binlog 及大小
SHOW BINARY LOGS;
SHOW MASTER LOGS; -- 同义词
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000001 | 245 |
| mysql-bin.000002 | 1893 |
+------------------+-----------+
-- ② 查看当前正在写入的日志与位置(做备份/搭从库/PITR 的起点)
SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000002 | 1893 | | | |
+------------------+----------+--------------+------------------+-------------------+
-- ③ 在线查看日志中的事件(不用退出 mysql)
SHOW BINLOG EVENTS; -- 默认看第一个日志文件
SHOW BINLOG EVENTS IN 'mysql-bin.000002';
SHOW BINLOG EVENTS IN 'mysql-bin.000002' FROM 245;
SHOW BINLOG EVENTS IN 'mysql-bin.000002' FROM 245 LIMIT 0,20; -- 分页,日志大时必用
+------------------+-----+-------------+-----------+-------------+-------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+-------------+-----------+-------------+-------------------------------------+
| mysql-bin.000002 | 4 | Format_desc | 1 | 245 | Server ver: 5.7.36-log, Binlog ver:4|
| mysql-bin.000002 | 245 | Query | 1 | 321 | BEGIN |
| mysql-bin.000002 | 321 | Table_map | 1 | 369 | table_id: 108 (m2.a) |
| mysql-bin.000002 | 369 | Write_rows | 1 | 413 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000002 | 413 | Xid | 1 | 444 | COMMIT /* xid=63 */ |
| mysql-bin.000002 | 444 | Query | 1 | 556 | drop database m2 |
+------------------+-----+-------------+-----------+-------------+-------------------------------------+
关键字段与事件类型:
| 字段 | 含义 |
|---|---|
Pos |
事件的起始位置 |
End_log_pos |
事件的结束位置,也就是下一个事件的 Pos |
Event_type |
事件类型,见下 |
Info |
事件内容摘要(ROW 格式的行事件此处看不到具体数据,需 mysqlbinlog -v) |
| 事件类型 | 含义 |
|---|---|
Format_desc |
日志文件头(每个文件开头都有,位置 4 ~ 245 左右),位置 4 是所有 binlog 的有效起点 |
Query |
SQL 语句事件:BEGIN、DDL、STATEMENT 格式的 DML |
Table_map |
ROW 格式中声明后续行事件操作哪张表 |
Write_rows / Update_rows / Delete_rows |
ROW 格式的插入 / 更新 / 删除 |
Xid |
事务提交标记(COMMIT),PITR 的 stop-position 应落在 Xid 结束处 |
Rotate |
日志切换,指向下一个文件 |
Gtid / Previous_gtids |
GTID 相关事件 |
4.6 FLUSH LOGS 切换与 PURGE 清理
-- 切换日志:关闭当前 binlog 并新建一个(编号 +1)
FLUSH LOGS;
FLUSH BINARY LOGS; -- 只切 binlog,不动其他日志(推荐,5.5+)
# 命令行等价写法
mysqladmin -uroot -p flush-logs
mysql -uroot -p -e "FLUSH LOGS;"
FLUSH LOGS的运维价值:让当前日志封口,之后拷贝这个已封口的文件绝对安全,这就是「全备 +-F」实现增量备份的原理。此外,mysqldump -F、xtrabackup、重启服务、日志写满max_binlog_size都会触发切换。
-- 清理 binlog(唯一正确的删除方式)
PURGE BINARY LOGS TO 'mysql-bin.000005'; -- 删除该文件之前的所有日志(不含它本身)
PURGE BINARY LOGS BEFORE '2026-08-15 00:00:00'; -- 删除该时间点之前的日志
PURGE MASTER LOGS TO 'mysql-bin.000005'; -- 同义词
RESET MASTER; -- 删除所有 binlog 并从 000001 重新开始
清理的风险:
| 风险 | 说明 |
|---|---|
| 破坏恢复链 | 一旦删除了「最老可用全备之后」的日志,就无法做时间点恢复。清理前必须确认:保留的 binlog 起点 ≤ 最老要保留的全备位点 |
| 中断主从复制 | 从库还没读到的日志被删 → Got fatal error 1236 ... could not find first log file name,只能重建从库。删除前必须检查所有从库的 Relay_Master_Log_File |
RESET MASTER 极度危险 |
会清空全部 binlog 并重置计数,主库上执行等于自断退路;只可在初始化新实例或明确无从库、无恢复需求时使用 |
rm 删除后遗症 |
index 文件残留导致 SHOW BINARY LOGS 与实际不符,需手工编辑 mysql-bin.index 并重启修复 |
-- 清理前的安全检查
SHOW BINARY LOGS; -- 现有日志
SHOW SLAVE HOSTS; -- 有哪些从库
-- 在每个从库上执行,确认已消费到哪个文件
SHOW SLAVE STATUS\G -- 看 Master_Log_File / Relay_Master_Log_File
4.7 mysqlbinlog 工具
mysqlbinlog 把二进制日志解析成可读的 SQL 文本,输出到标准输出,因此既能查看也能管道给 mysql 执行。
# 基本查看
mysqlbinlog /var/lib/mysql/mysql-bin.000002
mysqlbinlog /var/lib/mysql/mysql-bin.000002 | less
# 解析 ROW 格式(必须加这两个参数才能看到具体行数据)
mysqlbinlog --base64-output=decode-rows -v /var/lib/mysql/mysql-bin.000002
mysqlbinlog --base64-output=decode-rows -vv mysql-bin.000002 # -vv 额外显示列类型与元信息
常用参数:
| 参数 | 说明 |
|---|---|
--start-position=N |
从位置 N 开始解析(必须是某个事件的起始位置,不能是中间) |
--stop-position=N |
解析到位置 N 结束 |
--start-datetime='YYYY-MM-DD HH:MM:SS' |
从该时间开始 |
--stop-datetime='YYYY-MM-DD HH:MM:SS' |
到该时间结束 |
--database=库名 / -d |
只输出指定库的事件(ROW 格式可靠;STATEMENT 格式下跨库语句会被漏掉) |
--base64-output=decode-rows |
把 base64 编码的行事件解码(不解码则显示为一串乱码 base64) |
-v / -vv |
把行事件还原成伪 SQL(### INSERT INTO ...)/ 额外显示类型注释 |
--skip-gtids |
输出时忽略 GTID 事件,避免恢复到同实例时 GTID 冲突 |
--read-from-remote-server / -R |
从远程服务器直接读取 binlog(配合 -h -u -p),无需登录对端 |
--raw |
以二进制原样保存(配合 -R 实现 binlog 实时远程备份) |
--stop-never |
持续监听主库,实时拉取新 binlog(配合 -R --raw 做异地实时归档,RPO≈0) |
--result-file=前缀 / -r |
指定输出文件(--raw 时为文件名前缀) |
--to-last-log |
读到最后一个日志文件为止 |
--set-charset=utf8 |
在输出中加入 SET NAMES utf8,防止恢复时中文乱码 |
--disable-log-bin |
输出中加入 SET sql_log_bin=0,恢复时不再写新 binlog |
--verify-binlog-checksum |
校验日志校验和,检测文件损坏 |
按位置与时间恢复的四种典型用法:
# ① 通过起始位置恢复
mysqlbinlog --start-position=4 --stop-position=798 mysql_bin.000001 | mysql -uroot -p
# ② 通过时间范围恢复
mysqlbinlog --start-datetime='2018-09-20 00:00:00' \
--stop-datetime='2018-09-30 12:00:00' mysql_bin.000001 | mysql -uroot -p
# ③ 只恢复某个库
mysqlbinlog --database=m2 --start-position=245 mysql-bin.000002 | mysql -uroot -p
# ④ 先导出为 SQL 文件,人工检查/编辑后再执行(生产强烈推荐)
mysqlbinlog --start-position=245 --stop-position=1893 mysql-bin.000002 > /myback/inc.sql
vim /myback/inc.sql # 删掉误操作语句、确认无 DROP
mysql -uroot -p < /myback/inc.sql
多个日志文件必须一次性、按顺序传给 mysqlbinlog:
# 正确:一条命令处理多个文件,事务跨文件也不会断
mysqlbinlog mysql-bin.000002 mysql-bin.000003 mysql-bin.000004 | mysql -uroot -p
# 错误:分多次执行,跨文件的事务会被截断,且临时变量丢失
mysqlbinlog mysql-bin.000002 | mysql -uroot -p
mysqlbinlog mysql-bin.000003 | mysql -uroot -p # ← 有风险
# 批量按顺序拼接
mysqlbinlog mysql-bin.00000[2-9] > /myback/all_inc.sql
binlog 实时远程归档(把 RPO 降到秒级,生产强烈建议部署):
# 在备份机上持续拉取主库的 binlog,写到本地目录
cd /binlog_backup
mysqlbinlog -R --raw --stop-never --host=192.168.2.10 --port=3306 \
-u repl -p'ReplPass' mysql-bin.000001 &
# 说明:从 mysql-bin.000001 开始,持续跟随主库写入,主库切换日志时本地也自动生成新文件
4.8 误删数据的完整恢复演练(全备 + binlog 时间点恢复)
场景:每天 02:00 做一次全备;10:30 运维在生产库上执行了 DELETE FROM m2.a; 忘写 WHERE(或 drop database m2;),10:35 被发现。目标:恢复到 10:30 误操作前的状态,且 10:00~10:30 之间的正常业务数据不能丢。
第 0 步:止损(最重要,也最容易被忽略)
□ 立刻停止应用写入:下线应用 / 把库设为只读,防止新数据覆盖、防止误操作被更多变更掩埋
SET GLOBAL read_only = ON; -- 普通用户只读(super 用户仍可写)
SET GLOBAL super_read_only = ON; -- MySQL 5.7+:连 super 也只读
□ 立即备份当前的 binlog 与 datadir(保命快照,防止恢复操作把现场破坏掉)
FLUSH LOGS; 然后 cp -a /var/lib/mysql/mysql-bin.* /safe/
□ 严禁在生产库上直接 mysql < all.sql(全备里的 DROP 会毁掉误操作后的正常数据)
□ 优先在一台空闲机器 / 临时实例上恢复,校验后再回搬数据
第 1 步:准备实验环境与初始数据
-- 建库建表
CREATE DATABASE m2 DEFAULT CHARSET utf8;
CREATE TABLE m2.a (id INT(2), name CHAR(10), sex CHAR(1));
INSERT INTO m2.a VALUES (1,'zhangsan','f'),(2,'lisi','m');
SELECT * FROM m2.a;
第 2 步:02:00 的全备(记录位点)
# 方式一:切换日志式全备(增量就是新的整个日志文件)
mysqldump -uroot -p --single-transaction -B m2 -F > /myback/all.sql
# 方式二:记录位点式全备(推荐,位点写在 dump 文件里)
mysqldump -uroot -p --single-transaction --master-data=2 -B m2 > /myback/all.sql
# 查看全备对应的 binlog 位点,这是增量恢复的起点
grep "CHANGE MASTER TO" /myback/all.sql
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=245;
第 3 步:模拟白天的正常业务变更(必须保住的数据)
INSERT INTO m2.a VALUES (3,'wangwu','f');
INSERT INTO m2.a VALUES (4,'zhaoliu','m');
UPDATE m2.a SET sex='m' WHERE id=1;
第 4 步:模拟 10:30 的误操作
DELETE FROM m2.a; -- 忘写 WHERE,全表清空
-- 或者更严重的
-- DROP DATABASE m2;
第 5 步:紧急止损并归档 binlog
SET GLOBAL read_only = ON;
FLUSH LOGS; -- 让含误操作的日志封口,新变更写入下一个文件
mkdir -p /safe
cp -a /var/lib/mysql/mysql-bin.* /safe/
ls /safe/
第 6 步:找到误操作语句的 position(核心步骤)
-- 方法一:在 mysql 里翻日志,定位 DELETE / DROP 所在事件
SHOW BINLOG EVENTS IN 'mysql-bin.000002';
+------------------+------+-------------+-------------+---------------------------------+
| Log_name | Pos | Event_type | End_log_pos | Info |
+------------------+------+-------------+-------------+---------------------------------+
| mysql-bin.000002 | 4 | Format_desc | 245 | Server ver: 5.7.36-log |
| mysql-bin.000002 | 245 | Query | 321 | BEGIN |
| mysql-bin.000002 | 321 | Table_map | 369 | table_id: 108 (m2.a) |
| mysql-bin.000002 | 369 | Write_rows | 413 | table_id: 108 (id=3 wangwu) |
| mysql-bin.000002 | 413 | Xid | 444 | COMMIT /* xid=63 */ |
| mysql-bin.000002 | 444 | Query | 520 | BEGIN |
| mysql-bin.000002 | 520 | Table_map | 568 | table_id: 108 (m2.a) |
| mysql-bin.000002 | 568 | Write_rows | 612 | table_id: 108 (id=4 zhaoliu) |
| mysql-bin.000002 | 612 | Xid | 643 | COMMIT /* xid=64 */ |
| mysql-bin.000002 | 643 | Query | 719 | BEGIN |
| mysql-bin.000002 | 719 | Table_map | 767 | table_id: 108 (m2.a) |
| mysql-bin.000002 | 767 | Update_rows | 820 | table_id: 108 (id=1 sex f->m) |
| mysql-bin.000002 | 820 | Xid | 851 | COMMIT /* xid=65 */ |
| mysql-bin.000002 | 851 | Query | 927 | BEGIN | ← 误操作事务开始
| mysql-bin.000002 | 927 | Table_map | 975 | table_id: 108 (m2.a) |
| mysql-bin.000002 | 975 | Delete_rows | 1080 | table_id: 108 flags: STMT_END_F | ← 误删!
| mysql-bin.000002 | 1080 | Xid | 1111 | COMMIT /* xid=66 */ | ← 误操作事务结束
+------------------+------+-------------+-------------+---------------------------------+
# 方法二:用 mysqlbinlog 解析并搜索关键字,同时看到时间戳
mysqlbinlog --base64-output=decode-rows -v /safe/mysql-bin.000002 | grep -n -B5 -i "DELETE FROM"
mysqlbinlog --base64-output=decode-rows -v /safe/mysql-bin.000002 | grep -n -i -E "drop|truncate|delete"
# 方法三:按时间窗口缩小范围(已知误操作大约 10:30 发生)
mysqlbinlog --base64-output=decode-rows -v \
--start-datetime='2026-08-22 10:00:00' \
--stop-datetime='2026-08-22 10:35:00' /safe/mysql-bin.000002 | less
解析出的关键片段(ROW 格式):
# at 851
#260822 10:30:12 server id 1 end_log_pos 927 CRC32 0x9f3c1a2b Query thread_id=45
BEGIN
# at 975
#260822 10:30:12 server id 1 end_log_pos 1080 Delete_rows: table id 108 flags: STMT_END_F
### DELETE FROM `m2`.`a`
### WHERE
### @1=1
### @2='zhangsan'
### @3='m'
### DELETE FROM `m2`.`a`
### WHERE
### @1=2
### ...
# at 1080
#260822 10:30:12 server id 1 end_log_pos 1111 CRC32 0x1a2b3c4d Xid = 66
COMMIT/*!*/;
结论(记牢这两个数字):
- 误操作事务的起始位置 = 851(
BEGIN的Pos)→ 这是重放时的--stop-position。 - 误操作事务的结束位置 = 1111(
Xid的End_log_pos)→ 这是跳过误操作后继续重放的--start-position。
位置必须落在事件边界上:
--stop-position取误操作BEGIN的Pos,--start-position取误操作COMMIT的End_log_pos。取到事件中间会报Error in Log_event::read_log_event()或产生不完整事务。
第 7 步:恢复全备(到临时实例或已清空的目标库)
# 若是 DROP DATABASE,恢复前无需处理;若是 DELETE,为避免主键冲突先清理目标库
mysql -uroot -p -e "DROP DATABASE IF EXISTS m2;"
# 恢复全备 → 回到 02:00 的状态
mysql -uroot -p < /myback/all.sql
SELECT * FROM m2.a;
+------+----------+------+
| id | name | sex |
+------+----------+------+
| 1 | zhangsan | f |
| 2 | lisi | m |
+------+----------+------+
第 8 步:应用 binlog,跳过误操作语句
方案 A —— 两段式重放(保留误操作之后的正常数据,最完整):
# 第一段:从全备位点 245 → 误操作前 851
mysqlbinlog --start-position=245 --stop-position=851 /safe/mysql-bin.000002 > /myback/inc1.sql
# 第二段:从误操作事务之后 1111 → 日志结尾(含后续日志文件)
mysqlbinlog --start-position=1111 /safe/mysql-bin.000002 > /myback/inc2.sql
mysqlbinlog /safe/mysql-bin.000003 >> /myback/inc2.sql # 若已切换过日志
# 人工检查两个文件,确认不含 DELETE/DROP
grep -i -E "delete from|drop |truncate" /myback/inc1.sql /myback/inc2.sql
# 依次执行
mysql -uroot -p < /myback/inc1.sql
mysql -uroot -p < /myback/inc2.sql
方案 B —— 只恢复到误操作前(最简单,会丢弃误操作之后的所有变更):
mysqlbinlog --start-position=245 --stop-position=851 /safe/mysql-bin.000002 | mysql -uroot -p
# 或按时间
mysqlbinlog --start-datetime='2026-08-22 02:00:00' \
--stop-datetime='2026-08-22 10:30:11' /safe/mysql-bin.000002 | mysql -uroot -p
# 注意:stop-datetime 要早于误操作时间;同一秒内多个事务时时间无法精确区分,必须用 position
方案 C —— 导出为 SQL 后手工删除误操作语句(一段式,适合误操作夹在大量正常语句中间):
mysqlbinlog --start-position=245 /safe/mysql-bin.000002 > /myback/inc_all.sql
vim /myback/inc_all.sql
# 定位到 "# at 851" 到 "# at 1111" 之间的整个事务,连同 BEGIN/COMMIT 一并删除
mysql -uroot -p < /myback/inc_all.sql
第 9 步:验证数据
SELECT * FROM m2.a;
+------+----------+------+
| id | name | sex |
+------+----------+------+
| 1 | zhangsan | m | ← UPDATE 已重放
| 2 | lisi | m |
| 3 | wangwu | f | ← 全备后的新数据已找回
| 4 | zhaoliu | m |
+------+----------+------+
-- 完整验证清单
SELECT COUNT(*) FROM m2.a; -- 行数与故障前对账
CHECK TABLE m2.a; -- 表无损坏
SHOW CREATE TABLE m2.a\G -- 结构、索引、字符集
SELECT MAX(id), MAX(update_time) FROM m2.a; -- 最大主键/最新时间是否到误操作前一刻
-- 与业务方核对关键业务数据(订单数、金额合计),核对通过后再放开写入
SET GLOBAL super_read_only = OFF;
SET GLOBAL read_only = OFF;
第 10 步:事后复盘
□ 恢复过程与耗时记录归档(用于评估真实 RTO)
□ 分析根因:为何能在生产执行无 WHERE 的 DELETE
□ 加固措施:
- mysql 客户端启用安全模式:mysql -U / --safe-updates(无 WHERE 的 UPDATE/DELETE 直接报错)
写进 ~/.my.cnf:[mysql] safe-updates
- 生产账号权限收敛:应用账号不给 DROP;DBA 用跳板机 + 审计
- 上线延迟从库(延迟 1 小时),误操作可从延迟库直接取数
CHANGE MASTER TO MASTER_DELAY=3600;
- 引入 SQL 审核平台(Archery/Yearning),所有变更走工单
- 定期恢复演练
4.9 GTID 模式下的恢复注意事项
GTID(Global Transaction Identifier,全局事务标识)给每个事务一个全局唯一编号 server_uuid:序号,复制与恢复不再依赖「文件名 + position」。
# MySQL 5.6+ 开启 GTID
[mysqld]
gtid_mode = ON
enforce_gtid_consistency = ON
log_bin = mysql-bin
log_slave_updates = 1
SHOW VARIABLES LIKE 'gtid_mode';
SELECT @@global.gtid_executed; -- 本实例已执行的所有 GTID 区间
SELECT @@global.gtid_purged; -- 已被清理掉的 GTID 区间
SHOW MASTER STATUS; -- Executed_Gtid_Set 列
恢复时的四个坑:
| 问题 | 现象 | 解决 |
|---|---|---|
dump 文件里的 GTID_PURGED 冲突 |
恢复时报 @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty |
备份时用 --set-gtid-purged=OFF;或恢复前 RESET MASTER 清空 GTID 状态(确认无从库) |
| binlog 重放时事务被跳过 | mysqlbinlog \| mysql 执行后数据没变化,因为该 GTID 已在 gtid_executed 中,MySQL 认为「已执行过」直接忽略 |
加 --skip-gtids:mysqlbinlog --skip-gtids --start-position=... \| mysql -uroot -p |
| 跳过某个坏事务 | 复制中断在某 GTID | 注入空事务:SET GTID_NEXT='uuid:序号'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC'; |
| GTID 与 position 混用 | 搭从库时 MASTER_LOG_FILE 与 MASTER_AUTO_POSITION=1 冲突 |
GTID 模式统一用 CHANGE MASTER TO MASTER_AUTO_POSITION=1 |
# GTID 模式下做 PITR 的标准写法
mysqldump -uroot -p -A --single-transaction --master-data=2 --set-gtid-purged=OFF > all.sql
mysql -uroot -p < all.sql
mysqlbinlog --skip-gtids --start-position=245 --stop-position=851 mysql-bin.000002 | mysql -uroot -p
MariaDB 的 GTID 实现与 MySQL 完全不同(格式为
域ID-服务器ID-序列号,如0-1-1234,变量为gtid_slave_pos、gtid_binlog_pos),两者不能互相复制,恢复操作也不通用。跨产品迁移只能用逻辑备份。
4.10 其他日志简述
| 日志 | 作用 | 关键参数 |
|---|---|---|
| 错误日志(Error Log) | 记录启动/关闭过程、启动失败原因、崩溃恢复、复制错误、死锁摘要。排障第一个要看的文件 | log_error=/var/log/mysqld.log、log_warnings(5.7+ 为 log_error_verbosity=1/2/3) |
| 慢查询日志(Slow Query Log) | 记录执行时间超过阈值的语句,索引优化的主要输入 | slow_query_log=1、slow_query_log_file、long_query_time=1、log_queries_not_using_indexes、min_examined_row_limit、log_slow_admin_statements;MariaDB 专有 log_slow_verbosity=query_plan,explain |
| 通用查询日志(General Query Log) | 记录所有连接与语句(含 SELECT)。信息最全但性能损耗极大、日志暴涨,仅用于短时排查,用完立即关 | general_log=1、general_log_file、log_output=FILE 或 TABLE |
| 二进制日志(Binary Log) | 见本章前文:复制与 PITR | log_bin、binlog_format、sync_binlog |
| 中继日志(Relay Log) | 仅从库有。IO 线程把主库 binlog 拉过来先落地为 relay log,SQL 线程再从中读取重放。格式与 binlog 相同,可用 mysqlbinlog 解析 |
relay_log=mysqld-relay-bin、relay_log_purge=1(默认自动清理)、relay_log_recovery=1(崩溃后自动重建,5.6+ 建议开) |
| redo log / undo log | InnoDB 引擎层日志,崩溃恢复与回滚/MVCC,用户不直接操作 | innodb_log_file_size、innodb_flush_log_at_trx_commit、innodb_undo_tablespaces |
-- 快速查看各日志开关与位置
SHOW VARIABLES LIKE 'log_error';
SHOW VARIABLES LIKE 'general_log%';
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'relay_log%';
-- 临时开关(无需重启)
SET GLOBAL general_log = ON; -- 排查完立刻 OFF
SET GLOBAL general_log = OFF;
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
# 日志轮转(错误日志/慢日志被 mv 后需 flush 才会重新创建)
mv /var/log/mysql/mysql-slow.log /var/log/mysql/mysql-slow.log.$(date +%F)
mysqladmin -uroot -p flush-logs
# 或交给 logrotate 管理(/etc/logrotate.d/mysql)
慢查询日志的完整分析方法(
mysqldumpslow全部参数、pt-query-digest、SHOW PROCESSLIST实时定位、EXPLAIN执行计划解读)详见11-04-索引事务与状态查询.md。
五、物理备份
5.1 冷备:停库直接拷贝 datadir
最简单、最可靠、也最容易被轻视的备份方式。适合迁移、升级前留底、允许停机的场景。
完整步骤:
# ① 确认数据目录位置
mysql -uroot -p -e "SHOW VARIABLES LIKE 'datadir';"
grep -E "^datadir" /etc/my.cnf
# 默认 /var/lib/mysql
# ② 检查磁盘空间足够
du -sh /var/lib/mysql
df -h /myback
# ③ 优雅停库(必须等进程完全退出,确保脏页刷盘、日志 checkpoint 完成)
systemctl stop mysqld # CentOS 7;MariaDB 为 systemctl stop mariadb
# CentOS 6:service mysqld stop
ps -ef | grep -v grep | grep mysqld # 确认无残留进程
# 更稳妥:停库前先执行一次全量刷盘
# mysql -uroot -p -e "SET GLOBAL innodb_fast_shutdown=0;" 然后再停,保证 undo 全部清理
# ④ 拷贝(-a 保留权限、属主、时间戳、软链接,关键!)
cp -a /var/lib/mysql /myback/mysql_$(date +%F)
# 或打包压缩(同时保留权限)
tar -zcvpf /myback/mysql_$(date +%F).tar.gz -C /var/lib mysql
# 顺便把配置文件一起备份,否则恢复后参数不一致
cp -a /etc/my.cnf /etc/my.cnf.d /myback/mysql_$(date +%F)_conf
# ⑤ 启动数据库
systemctl start mysqld
冷备恢复:
systemctl stop mysqld
# 保留现场(不要直接删!)
mv /var/lib/mysql /var/lib/mysql.bak.$(date +%s)
# 还原
tar -zxvpf /myback/mysql_2026-08-22.tar.gz -C /var/lib
# 或 cp -a /myback/mysql_2026-08-22 /var/lib/mysql
# ⑥ 恢复权限与属主(最高频的失败原因!)
chown -R mysql:mysql /var/lib/mysql
chmod 750 /var/lib/mysql
# CentOS 7 还需恢复 SELinux 上下文
restorecon -Rv /var/lib/mysql
# 若数据目录不在默认位置,需先加规则
# semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?" && restorecon -Rv /data/mysql
systemctl start mysqld
tail -50 /var/log/mysqld.log # 启动失败第一时间看错误日志
mysql -uroot -p -e "SHOW DATABASES;"
注意事项:
| 注意点 | 说明 |
|---|---|
| 必须完全停库 | 运行中直接 cp 数据文件,会拷到写了一半的页,恢复后表损坏。这不是「热备」,是「破备」 |
必须用 cp -a / tar -p |
丢失属主会导致 mysqld 无法读写数据目录,报 Permission denied 启动失败 |
| 必须整目录拷贝 | 不能只拷 .ibd 文件——InnoDB 的表空间 ID 记录在 ibdata1 里,单独拷 .ibd 无法识别(除非用表空间传输,见 5.5) |
*别忘 `ib_logfile与ibdata1`** |
这三类文件是一致的整体,缺一个即无法启动 |
| 版本要匹配 | 恢复到不同大版本可能需 mysql_upgrade;跨大版本降级基本不可行 |
auto.cnf 的坑 |
该文件保存 server_uuid,克隆到另一台后必须删除 auto.cnf 让其重新生成,否则主从复制会因 UUID 相同而失败 |
| 配置文件同步 | innodb_log_file_size、innodb_data_file_path 等参数与数据文件强绑定,配置不一致会启动失败 |
| 非 yum 版的坑 | 如果是非 yum 版 MySQL(源码编译/二进制解压),删除数据目录后将无法启动,需要重新初始化(mysqld --initialize 或 mysql_install_db) |
5.2 基于文件系统快照的备份(LVM 快照)
LVM 快照利用写时复制(COW),瞬间创建一个数据目录的时间点视图,锁表时间只有 1~2 秒,是「几乎热备」的物理备份方案。前提:datadir 必须位于 LVM 逻辑卷上,且 VG 中有剩余空闲空间。
完整流程:
# ① 前置检查:datadir 在哪个 LV 上,VG 剩余空间是否充足
df -h /var/lib/mysql
lvs ; vgs
# VG 剩余空间建议 ≥ 备份期间的数据变化量(一般给 LV 的 10%~20%)
-- ② 加全局读锁(此后只能读,写请求阻塞。务必确认无长事务,否则会卡死)
SELECT * FROM information_schema.innodb_trx WHERE TIMESTAMPDIFF(SECOND,trx_started,NOW())>10;
FLUSH TABLES WITH READ LOCK; -- 简称 FTWRL,全库只读
-- ③ 记录位点(恢复后接主库/做增量都要用),此会话不要退出,否则锁会释放!
SHOW MASTER STATUS;
# ④ 另开一个终端,立即创建快照(1~2 秒完成)
lvcreate -L 5G -s -n mysql_snap /dev/vg_data/lv_mysql
# -L 快照空间大小 -s 快照 -n 快照名 最后是源 LV 路径
-- ⑤ 回到第一个会话,立即释放锁(业务恢复正常)
UNLOCK TABLES;
# ⑥ 挂载快照并拷贝数据到备份目标(此时业务已正常,慢慢拷)
mkdir -p /mnt/snap
mount -o ro /dev/vg_data/mysql_snap /mnt/snap # XFS 需加 -o nouuid,ro
tar -zcvpf /myback/mysql_snap_$(date +%F).tar.gz -C /mnt/snap .
# 顺便把位点信息存下来
echo "mysql-bin.000006 1245" > /myback/mysql_snap_$(date +%F).position
# ⑦ 卸载并删除快照(快照存在期间会持续消耗 IO 与 COW 空间,必须及时删)
umount /mnt/snap
lvremove -f /dev/vg_data/mysql_snap
注意事项:
FLUSH TABLES WITH READ LOCK会等待所有正在执行的语句结束。若有一个跑了半小时的大查询,FTWRL 会一直挂着,同时新来的所有查询都被它阻塞(因为 MDL 请求排队)→ 业务瞬间雪崩。执行前必须确认无长查询/长事务。- 快照空间被写满后快照立即失效(
lvs显示Invalid snapshot),备份作废。 - 快照恢复出的数据处于「崩溃状态」,启动时 InnoDB 会自动做 crash recovery,这是正常的。
- MySQL 5.6+ 可用
FLUSH TABLES WITH READ LOCK的轻量替代:先FLUSH TABLES再 FTWRL,缩短锁持有时间。
LVM 的创建、扩容、快照原理与命令详见
03-01-磁盘管理与文件系统.md。
5.3 Xtrabackup / innobackupex 物理热备
Percona XtraBackup 是开源的 MySQL 物理热备工具,通过持续拷贝 redo log 实现 InnoDB 表在线备份不锁表。
5.3.1 xtrabackup 与 innobackupex 的区别
| 工具 | 说明 |
|---|---|
xtrabackup |
可对 InnoDB 表进行热备份(不加锁,备份期间数据可读写),但不能备份 MyISAM 表 |
innobackupex |
可同时备份 InnoDB 和 MyISAM 表,但 MyISAM 表仍不能热备(必须加锁后才能备份) |
版本演进:
innobackupex是xtrabackup的 perl 封装脚本。XtraBackup 2.4 起两者功能合并,innobackupex成为xtrabackup的软链接并被标记为废弃;8.0 起innobackupex被彻底移除,只能用xtrabackup。此外 XtraBackup 2.4 只支持 MySQL 5.7 及以下,MySQL 8.0 必须用 XtraBackup 8.0(大版本必须对应),MariaDB 10.3+ 请改用官方的mariabackup(用法与 innobackupex 几乎一致)。
5.3.2 安装 xtrabackup
下载二进制包:
wget https://www.percona.com/downloads/XtraBackup/Percona-XtraBackup-2.4.12/binary/tarball/percona-xtrabackup-2.4.12-Linux-x86_64.libgcrypt145.tar.gz
安装依赖:
yum install libaio libaio-devel -y
# 完整依赖(perl 模块用于 innobackupex)
yum install -y libaio libaio-devel perl perl-DBI perl-DBD-MySQL perl-Time-HiRes perl-IO-Socket-SSL rsync
解压并创建软链接:
tar xf percona-xtrabackup-2.4.12-Linux-x86_64.libgcrypt145.tar.gz -C /usr/local/
ln -s /usr/local/percona-xtrabackup-2.4.12-Linux-x86_64/ /usr/local/xtrabackup
ln -s /usr/local/xtrabackup/bin/xtrabackup /usr/bin/xtrabackup
ln -s /usr/local/xtrabackup/bin/innobackupex /usr/bin/innobackupex
# 验证
xtrabackup --version
innobackupex --version
yum 方式安装(更省事):
yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
yum install -y percona-xtrabackup-24
备份账号最小权限:
CREATE USER 'bkpuser'@'localhost' IDENTIFIED BY 'BkpPass';
GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT, SUPER ON *.* TO 'bkpuser'@'localhost';
FLUSH PRIVILEGES;
5.3.3 Xtrabackup 常用参数
| 参数 | 说明 |
|---|---|
--defaults-file |
指定 MySQL 主配置文件路径,自动读取 datadir、basedir 等(必须是第一个参数) |
--defaults-extra-file |
除主配置文件外,额外读取指定配置文件 |
--target-dir |
指定备份存放路径,不指定则备份到当前目录 xtrabackup_backupfiles |
--backup |
备份到 --target-dir(必选),不选则只打印过程 |
--prepare |
恢复前准备,生成 InnoDB log file(应用 redo,把备份变成一致状态) |
--apply-log-only |
只应用 redo 不回滚未提交事务,增量备份合并时除最后一次外都必须加 |
--copy-back |
从备份目录以拷贝形式恢复到数据目录 |
--move-back |
从备份目录以移动形式恢复到数据目录(省空间,但备份被消耗) |
--print-param |
打印备份或恢复时需要的参数 |
--suspend-at-end |
在备份目录生成 xtrabackup_suspended 文件,挂起进程持续同步变化,直到手动删除该文件 |
--tables |
备份指定表(支持正则) |
--tables-exclude |
排除指定表 |
--databases |
备份指定库 |
--datadir |
MySQL 数据目录路径,一般不用指定(从配置文件读) |
--incremental |
创建增量备份 |
--incremental-basedir |
增量备份时指定上一次备份目录,自动确定备份起点(读取其 LSN) |
--incremental-lsn |
增量备份时指定 to_lsn 位置(手工指定起点) |
--incremental-dir |
将增量备份与全量备份合并后放入新目录 |
--compress |
备份时启用压缩(qpress 格式,需装 qpress) |
--compress-threads=N |
压缩线程数 |
--decompress |
解压压缩备份 |
--parallel=N |
并行拷贝文件的线程数,大库必调(一般 4~8) |
--throttle=N |
限速,每秒最多 N 对 IO 操作,避免打满磁盘影响业务 |
--no-timestamp |
备份时不自动创建以日期时间命名的目录(脚本里常用,目录名可控) |
--stream=xbstream/tar |
备份时启用打包/流式输出,可直接管道到远端或压缩 |
--no-lock |
备份时关闭自动锁表功能(仅当全库都是 InnoDB 且不需要 binlog 位点时才可用) |
--safe-slave-backup |
备份时先停止 slave 的 SQL 线程,保证一致 |
--slave-info |
备份从库时把主库位点写入 xtrabackup_slave_info |
--galera-info |
备份 Galera 集群节点时记录状态 |
--remote-host |
使用 ssh 将备份文件拷贝至远程服务器(老版本 innobackupex 参数,新版用 --stream + ssh) |
--apply-log |
(innobackupex)恢复时通过 xtrabackup_logfile 在备份目录中创建备份(恢复时必选),等价于新版 --prepare |
--redo-only |
(innobackupex)等价于 --apply-log-only |
--uncompress |
(innobackupex)解压以压缩方式备份的文件 |
5.3.4 xtrabackup 全量备份与恢复
全量备份:
xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/myback -uroot --port=3306 -S /var/lib/mysql/mysql.sock -p
备份目录内容:
a/ ← 各数据库目录(含 .ibd/.frm)
backup-my.cnf ← 备份时的关键配置(恢复时要用)
ibdata1 ← 系统表空间
mysql/
performance_schema/
test/
xtrabackup_binlog_info ← 备份时刻的 binlog 文件名与 position(PITR 起点!)
xtrabackup_checkpoints ← LSN 检查点信息(增量备份的依据)
xtrabackup_info ← 备份的元信息(工具版本、命令、时间、是否压缩)
xtrabackup_logfile ← 备份期间拷贝的 redo log,prepare 时用它把数据补齐到一致状态
# 备份关键产物解读
cat /myback/xtrabackup_binlog_info
# mysql-bin.000006 1245 ← 从这个位点之后应用 binlog 即可做时间点恢复
cat /myback/xtrabackup_checkpoints
cat /myback/xtrabackup_info
全量恢复:
模拟数据丢失,停止数据库并删除数据目录:
systemctl stop mariadb
rm /var/lib/mysql/*
注意:如果是非 yum 版 MySQL,删除数据目录后将无法启动,需要重新初始化。生产中请用
mv保留现场而不是rm。
恢复前准备(初始化备份目录,应用 redo 达到一致状态):
xtrabackup --defaults-file=/etc/my.cnf --prepare --target-dir=/myback
将备份目录全部文件拷贝到数据目录:
\cp -rf /myback/* /var/lib/mysql
# 或使用官方推荐的方式(会自动排除 xtrabackup_* 元文件)
# xtrabackup --defaults-file=/etc/my.cnf --copy-back --target-dir=/myback
重新授权并启动:
chown -R mysql.mysql /var/lib/mysql
systemctl start mariadb
# 验证
mysql -uroot -p -e "SHOW DATABASES;"
mysql -uroot -p -e "use a;show tables;"
恢复后如需继续做时间点恢复:取
xtrabackup_binlog_info里的位点,用mysqlbinlog --start-position=1245 mysql-bin.000006 | mysql -uroot -p追加增量。
5.3.5 xtrabackup 增量备份与恢复
增量备份的原理:InnoDB 每个数据页头部都有 LSN(Log Sequence Number),xtrabackup 只拷贝「页 LSN > 上次备份 to_lsn」的页,因此增量备份体积极小。
全量备份(作为增量的基准):
xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/myback -uroot --port=3306 -S /var/lib/mysql/mysql.sock -p
查看检查点:
cat /myback/xtrabackup_checkpoints
backup_type = full-backuped
from_lsn = 0 本次备份开始点
to_lsn = 1632546 本次备份结束点
last_lsn = 1632546 下次备份开始点
compact = 0
recover_binlog_info = 0
第一次增量备份(备份至 /inc/inc1):
mysql -uroot -p -e "create table a.a2 (id int);show tables;"
xtrabackup --backup --target-dir=/inc/inc1 --incremental-basedir=/myback -uroot -S /var/lib/mysql/mysql.sock -P 3306
cat /inc/inc1/xtrabackup_checkpoints
backup_type = incremental
from_lsn = 1632546
to_lsn = 1634384
last_lsn = 1634384
compact = 0
recover_binlog_info = 0
第二次增量备份(备份至 /inc/inc2):
mysql -uroot -p -e "create table a.a3 (id int);show tables;"
xtrabackup --backup --target-dir=/inc/inc2 --incremental-basedir=/inc/inc1 -uroot -S /var/lib/mysql/mysql.sock -P 3306
cat /inc/inc2/xtrabackup_checkpoints
backup_type = incremental
from_lsn = 1634384
to_lsn = 1636206
last_lsn = 1636206
compact = 0
recover_binlog_info = 0
增量备份参数说明:
| 参数 | 说明 |
|---|---|
--target-dir |
指定增备目录(备份到哪儿) |
--incremental-basedir |
指定上一次备份目录。第一次增量备份时为全备目录,第二次增量备份时为第一次增备目录 |
LSN 链条必须首尾相接:全备
to_lsn=1632546→ inc1from_lsn=1632546;inc1to_lsn=1634384→ inc2from_lsn=1634384。检查备份链是否完整,只需核对相邻两次的to_lsn与from_lsn是否相等,不相等说明中间少了一次备份,链条已断,无法恢复。 若把--incremental-basedir始终指向全备目录,得到的就是差异备份(体积更大但恢复只需两步)。
全量恢复(全备 + 增备):
systemctl stop mariadb
rm /var/lib/mysql/*
初始化全量备份:
xtrabackup --defaults-file=/etc/my.cnf --prepare --target-dir=/myback
初始化第一次增量备份:
xtrabackup --defaults-file=/etc/my.cnf --prepare --apply-log-only --target-dir=/myback --incremental-basedir=/inc/inc1
初始化第二次增量备份:
xtrabackup --defaults-file=/etc/my.cnf --prepare --apply-log-only --target-dir=/myback --incremental-basedir=/inc/inc2
拷贝合并后的数据到数据目录:
cp /inc/inc2/* /var/lib/mysql/ -r
chown -R mysql.mysql /var/lib/mysql
systemctl start mariadb
验证:
mysql -uroot -p -e "use a;show tables;"
+-------------+
| Tables_in_a |
+-------------+
| a1 |
| a2 |
| a3 |
+-------------+
关于
--apply-log-only的规范用法(官方推荐顺序,比上面的写法更严谨,生产按此执行):# ① 全备 prepare,只应用 redo,不回滚未提交事务(因为后面还要合并增量) xtrabackup --prepare --apply-log-only --target-dir=/myback # ② 合并第一个增量(同样加 --apply-log-only) xtrabackup --prepare --apply-log-only --target-dir=/myback --incremental-dir=/inc/inc1 # ③ 合并最后一个增量(最后一次【不加】--apply-log-only,此时回滚未提交事务) xtrabackup --prepare --target-dir=/myback --incremental-dir=/inc/inc2 # ④ 从合并后的全备目录恢复 xtrabackup --copy-back --target-dir=/myback chown -R mysql:mysql /var/lib/mysql systemctl start mysqld记忆要点:除最后一次外,所有 prepare 都要
--apply-log-only;最后一次绝不能加。加错的后果是数据处于不一致状态或无法再合并后续增量。
压缩与并行备份:
# 并行 + 限速(大库生产写法)
xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/myback \
--parallel=4 --throttle=200 -ubkpuser -p'BkpPass'
# 压缩备份(需先 yum install qpress)
xtrabackup --backup --compress --compress-threads=4 --target-dir=/myback_c -ubkpuser -p'BkpPass'
# 解压后才能 prepare
xtrabackup --decompress --parallel=4 --target-dir=/myback_c
xtrabackup --prepare --target-dir=/myback_c
# 流式备份直接传到远端(不落地本地磁盘)
xtrabackup --backup --stream=xbstream --target-dir=/tmp -ubkpuser -p'BkpPass' \
| ssh root@192.168.2.20 "xbstream -x -C /myback/remote_$(date +%F)"
# 流式 + 压缩到单个文件
xtrabackup --backup --stream=xbstream --target-dir=/tmp | gzip > /myback/full_$(date +%F).xbstream.gz
备份日志解读(备份结束时的关键输出,必须确认):
xtrabackup: Transaction log of lsn (1632546) to (1632546) was copied.
...
xtrabackup: Streaming complete.
completed OK! ← 【最关键】只有出现这行才算备份成功
# 脚本中判断备份是否成功的正确方式
xtrabackup --backup --target-dir=/myback 2>/myback/xtrabackup.log
if grep -q "completed OK!" /myback/xtrabackup.log; then
echo "备份成功"
else
echo "备份失败,请检查日志"
fi
| 日志关键行 | 含义 |
|---|---|
Copying ./ibdata1 to ... |
正在拷贝系统表空间 |
>> log scanned up to (LSN) |
后台线程正在持续跟踪 redo log,这是热备不丢数据的关键 |
Executing FLUSH NO_WRITE_TO_BINLOG TABLES... |
准备锁 MyISAM 表 |
Executing FLUSH TABLES WITH READ LOCK... |
开始加全局读锁(此刻业务只读,通常仅 1 秒内) |
All tables unlocked |
锁已释放 |
MySQL binlog position: filename 'mysql-bin.000006', position '1245' |
备份对应的 binlog 位点 |
completed OK! |
备份成功。没有这行即备份失败,不管目录里有多少文件 |
5.4 innobackupex 备份与恢复
5.4.1 innobackupex 常用参数
| 参数 | 说明 |
|---|---|
--apply-log |
恢复时通过 xtrabackup_logfile 在备份目录中创建备份(恢复时必选) |
--copy-back |
从备份目录以拷贝形式恢复到数据目录 |
--move-back |
从备份目录以移动形式恢复到数据目录 |
--defaults-file |
指定 MySQL 主配置文件路径 |
--remote-host |
使用 ssh 将备份文件拷贝至远程服务器 |
--stream |
备份时启用打包 |
--compress |
备份时启用压缩 |
--tables |
指定要备份的表 |
--databases |
指定要备份的库 |
--uncompress |
解压以压缩方式备份的文件 |
--incremental |
创建增量备份 |
--incremental-basedir |
增量备份时指定全量备份路径,以确定全量备份终止位置 |
--incremental-dir |
将增量备份与全量备份合并后放入新目录 |
--incremental-lsn |
增量备份时指定起点 |
--redo-only |
只应用 redo 不回滚,合并增量时除最后一次外都要加 |
--no-lock |
备份时关闭自动锁表功能 |
--safe-slave-backup |
备份时先停止 slave 进程 |
--no-timestamp |
备份时不自动创建以日期时间命名的目录 |
5.4.2 innobackupex 全量备份与恢复
全量备份:
innobackupex --defaults-file=/etc/my.cnf /myback -uroot -P 3306 -S /var/lib/mysql/mysql.sock
# 或
innobackupex --defaults-file=/etc/my.cnf -u root -p123 -h 127.0.0.1 /myback
备份文件会存放在 /myback 下以当前日期命名的目录中,如 /myback/2018-09-24_22-07-00/。加 --no-timestamp 可让目录名由自己指定。
模拟误操作:
mysql -uroot -p -e "drop database a;"
mysql -uroot -p -e "show databases;"
恢复前停止数据库并清空数据目录:
systemctl stop mariadb
rm -rf /var/lib/mysql/*
恢复前准备(初始化数据):
innobackupex --defaults-file=/etc/my.cnf --apply-log /myback/2018-09-24_22-07-00/
恢复数据到数据目录:
innobackupex --defaults-file=/etc/my.cnf --copy-back --datadir=/var/lib/mysql /myback/2018-09-24_22-07-00/
--datadir指定数据目录位置,如果配置文件中已有指定则不需要再指定。
重新授权并启动:
chown -R mysql.mysql /var/lib/mysql
systemctl start mariadb
验证数据:
mysql -uroot -p -e "show databases;"
mysql -uroot -p -e "select * from a.a1;"
5.4.3 innobackupex 增量备份与恢复
查看全量备份检查点:
cat /myback/`ls /myback/`/xtrabackup_checkpoints
backup_type = full-backuped
from_lsn = 0
to_lsn = 1623090 全量结束点,即增量备份开始点
last_lsn = 1623090
compact = 0
recover_binlog_info = 0
第一次增量备份:
innobackupex --defaults-file=/etc/my.cnf --incremental-basedir=/myback/`ls /myback/` --incremental /inc/ -uroot -S /var/lib/mysql/mysql.sock -P 3306
查看增备检查点:
cat /inc/2018-09-24_22-18-17/xtrabackup_checkpoints
backup_type = incremental
from_lsn = 1623090 本次增量起点
to_lsn = 1630106 本次增量终点
last_lsn = 1630106 下次增量起点
compact = 0
recover_binlog_info = 0
第二次增量备份:
innobackupex --defaults-file=/etc/my.cnf --incremental-basedir=/inc/2018-09-24_22-18-17 --incremental /inc -uroot -S /var/lib/mysql/mysql.sock -P 3306
备份目录说明:
| 目录 | 用途 |
|---|---|
/myback/2018-09-24_22-32-18 |
全备目录(--incremental-basedir 使用) |
/inc/2018-09-24_22-34-47 |
第一次增量备份目录(--incremental 使用) |
/inc/2018-09-24_22-38-10 |
第二次增量备份目录(--incremental 使用) |
增量恢复:
systemctl stop mariadb
rm /var/lib/mysql/*
初始化全备目录:
innobackupex --apply-log --redo-only /myback/2018-09-24_22-32-18
初始化第一次增备目录:
innobackupex --apply-log --redo-only /myback/2018-09-24_22-32-18 --incremental-dir=/inc/2018-09-24_22-34-47
初始化第二次增备目录(最后一次不需要加 --redo-only):
innobackupex --apply-log /myback/2018-09-24_22-32-18 --incremental-dir=/inc/2018-09-24_22-38-10
完整恢复:
innobackupex --copy-back /myback/2018-09-24_22-32-18
过程:先将两次增量备份的数据整合到全备目录中,再一次性恢复。注意 --incremental-dir 指向增备目录,而 --apply-log 后面的路径始终是全备目录(合并的目标)。
重新授权并启动:
chown -R mysql.mysql /var/lib/mysql
systemctl start mariadb
验证:
mysql -uroot -p -e "show databases;"
mysql -uroot -p -e "use a;show tables;"
+-------------+
| Tables_in_a |
+-------------+
| a1 |
| a2 |
| a3 |
| a4 |
+-------------+
至此,innobackupex 增量备份与恢复完成。
5.5 表级操作:表空间传输与单表快速恢复
InnoDB 在 innodb_file_per_table=ON(5.6+ 默认)时每表一个 .ibd 文件,可以做可传输表空间(Transportable Tablespace),实现单表的物理级导出导入——比逻辑备份快几十倍,是「单表误删」的最快恢复手段。
5.5.1 导出表空间
-- 前提确认
SHOW VARIABLES LIKE 'innodb_file_per_table'; -- 必须为 ON
-- ① 在源库上锁表并把脏页刷到 .ibd
USE m2;
FLUSH TABLES a FOR EXPORT;
-- 此时会在数据目录生成 m2/a.cfg(表结构元数据)
-- 【本会话不要退出】,退出即释放锁,.cfg 文件会被删除
# ② 另开终端,拷走 .ibd 与 .cfg
cp /var/lib/mysql/m2/a.ibd /var/lib/mysql/m2/a.cfg /myback/
-- ③ 回到第一个会话,解锁
UNLOCK TABLES;
5.5.2 导入表空间
-- ① 在目标库建一张【结构完全相同】的空表(列、类型、索引、行格式都必须一致)
CREATE TABLE m2.a (id INT(2), name CHAR(10), sex CHAR(1)) ENGINE=InnoDB;
-- ② 丢弃它自带的空表空间(此后 a.ibd 被删除)
ALTER TABLE m2.a DISCARD TABLESPACE;
# ③ 把备份的 .ibd/.cfg 放进目标库目录并改好属主
cp /myback/a.ibd /myback/a.cfg /var/lib/mysql/m2/
chown mysql:mysql /var/lib/mysql/m2/a.ibd /var/lib/mysql/m2/a.cfg
-- ④ 导入表空间
ALTER TABLE m2.a IMPORT TABLESPACE;
-- ⑤ 验证
CHECK TABLE m2.a;
SELECT COUNT(*) FROM m2.a;
常见报错与原因:
| 报错 | 原因 |
|---|---|
Schema mismatch (Table has ROW_TYPE_DYNAMIC ...) |
表结构/行格式不一致,需保证 ROW_FORMAT、字符集、列定义完全相同 |
Tablespace is missing for table |
.ibd 没放进目录或属主不对 |
Cannot find .cfg file |
缺 .cfg(可用 IMPORT TABLESPACE 强制导入但风险高) |
| 表空间 ID 冲突 | 跨实例导入时正常,IMPORT 会自动改写 |
5.5.3 单表误删的快速恢复决策
误删了一张表的数据,怎么恢复最快?
① 有延迟从库(MASTER_DELAY)且延迟未追平
→ 直接从延迟库 SELECT ... INTO OUTFILE 或 mysqldump 单表,回搬。分钟级,最优。
② 有全备(逻辑)+ binlog
→ 从全备中 sed 提取该表 → 恢复到临时库 → 应用 binlog 到误操作前 → INSERT ... SELECT 回搬。
③ 有全备(物理 xtrabackup)
→ xtrabackup --prepare --export 后用表空间传输把单表搬回临时实例,再回搬数据。
④ 只有 binlog(ROW 格式)
→ 闪回:解析 binlog 反向生成补偿 SQL(DELETE→INSERT、UPDATE 前后镜像互换)
工具:binlog2sql、MyFlash、my2sql
python binlog2sql.py -h127.0.0.1 -uroot -p'pwd' -dm2 -ta \
--start-file='mysql-bin.000002' --start-position=851 --stop-position=1111 -B > rollback.sql
(-B 即 flashback 模式,生成回滚 SQL)
⑤ 什么都没有 → 只能找数据恢复公司扫盘,成功率低、代价极高。
# xtrabackup 单表恢复:备份时导出单表表空间
xtrabackup --prepare --export --target-dir=/myback
# prepare --export 后会为每个表生成 .exp/.cfg 文件,之后按 5.5.2 导入
六、备份自动化与运维
6.1 完整的 Shell 备份脚本
生产可用的 mysqldump 全备脚本,涵盖变量定义、按日期命名、压缩、删除过期备份、成功/失败判断、告警、写日志:
#!/bin/bash
#===============================================================
# 文件名: /opt/scripts/mysql_backup.sh
# 功能 : MySQL 全库逻辑备份(压缩 + 过期清理 + 校验 + 告警 + 日志)
# 用法 : bash /opt/scripts/mysql_backup.sh
# 作者 : DBA
#===============================================================
#--------- ① 变量定义 ---------
DB_USER="backup"
DB_PASS="BackupPass123"
DB_HOST="127.0.0.1"
DB_PORT="3306"
BACK_DIR="/myback" # 备份根目录
LOG_FILE="/var/log/mysql_backup.log" # 脚本日志
KEEP_DAYS=7 # 备份保留天数
DATE=$(date +%F_%H-%M) # 2026-08-22_02-00
DAY=$(date +%F)
BACK_FILE="${BACK_DIR}/all_${DATE}.sql.gz"
MAIL_TO="dba@example.com"
MIN_SIZE=1024 # 备份文件最小合理体积(KB),小于此判为失败
#--------- ② 日志函数 ---------
log() {
echo "[$(date '+%F %T')] $*" >> "$LOG_FILE"
}
alert() {
local msg="$1"
log "ALERT: $msg"
# 邮件告警(需已配置 mailx / postfix)
echo "$msg" | mail -s "[严重] MySQL备份失败 $(hostname) ${DAY}" "$MAIL_TO"
# 企业微信/钉钉机器人告警(按需启用)
# curl -s "https://oapi.dingtalk.com/robot/send?access_token=XXX" \
# -H 'Content-Type: application/json' \
# -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"MySQL备份失败: $msg\"}}"
}
#--------- ③ 前置检查 ---------
log "========== 备份开始 =========="
[ -d "$BACK_DIR" ] || mkdir -p "$BACK_DIR"
# 检查磁盘剩余空间(低于 10GB 直接告警退出)
AVAIL=$(df -k "$BACK_DIR" | awk 'NR==2{print $4}')
if [ "$AVAIL" -lt $((10*1024*1024)) ]; then
alert "备份目录 $BACK_DIR 剩余空间不足 10GB(当前 $((AVAIL/1024/1024))GB),备份中止"
exit 1
fi
# 检查数据库是否可连接
if ! mysqladmin -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" -P"$DB_PORT" ping &>/dev/null; then
alert "数据库无法连接,备份中止"
exit 1
fi
#--------- ④ 执行备份 ---------
log "开始导出到 $BACK_FILE"
START=$(date +%s)
mysqldump -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" -P"$DB_PORT" \
--all-databases \
--single-transaction \
--master-data=2 \
--flush-logs \
--routines --triggers --events \
--hex-blob \
--default-character-set=utf8 \
2>>"$LOG_FILE" | gzip > "$BACK_FILE"
# 关键:管道中 mysqldump 的退出码要用 PIPESTATUS 取,不能用 $?
DUMP_RC=${PIPESTATUS[0]}
GZIP_RC=${PIPESTATUS[1]}
END=$(date +%s)
COST=$((END-START))
#--------- ⑤ 成功/失败判断(三重校验) ---------
# 校验 1:退出码
if [ "$DUMP_RC" -ne 0 ] || [ "$GZIP_RC" -ne 0 ]; then
alert "mysqldump 返回码 $DUMP_RC / gzip 返回码 $GZIP_RC,备份失败:$BACK_FILE"
rm -f "$BACK_FILE"
exit 1
fi
# 校验 2:文件存在且体积合理
SIZE=$(du -k "$BACK_FILE" 2>/dev/null | awk '{print $1}')
if [ ! -s "$BACK_FILE" ] || [ "${SIZE:-0}" -lt "$MIN_SIZE" ]; then
alert "备份文件体积异常(${SIZE}KB),疑似截断:$BACK_FILE"
exit 1
fi
# 校验 3:压缩包完整性 + 结尾标记
if ! gzip -t "$BACK_FILE" 2>>"$LOG_FILE"; then
alert "备份文件压缩校验失败(gzip -t):$BACK_FILE"
exit 1
fi
if ! zcat "$BACK_FILE" | tail -1 | grep -q "Dump completed"; then
alert "备份文件缺少 'Dump completed' 结尾标记,内容不完整:$BACK_FILE"
exit 1
fi
# 记录 MD5,便于异地传输后核对
md5sum "$BACK_FILE" > "${BACK_FILE}.md5"
log "备份成功:$BACK_FILE 大小 $((SIZE/1024))MB 耗时 ${COST}s"
#--------- ⑥ 归档 binlog(增量部分) ---------
BINLOG_DIR=$(mysql -u"$DB_USER" -p"$DB_PASS" -N -e "SHOW VARIABLES LIKE 'log_bin_basename';" | awk '{print $2}')
if [ -n "$BINLOG_DIR" ]; then
mkdir -p "${BACK_DIR}/binlog/${DAY}"
# --flush-logs 已切换日志,此时把除【当前正在写】以外的日志全部归档
CUR_LOG=$(mysql -u"$DB_USER" -p"$DB_PASS" -N -e "SHOW MASTER STATUS;" | awk '{print $1}')
for f in ${BINLOG_DIR}.[0-9]*; do
[ "$(basename $f)" = "$CUR_LOG" ] && continue
cp -a "$f" "${BACK_DIR}/binlog/${DAY}/" && log "已归档 binlog: $(basename $f)"
done
fi
#--------- ⑦ 删除过期备份 ---------
log "清理 ${KEEP_DAYS} 天前的备份"
find "$BACK_DIR" -maxdepth 1 -name "all_*.sql.gz" -mtime +${KEEP_DAYS} -print -delete >> "$LOG_FILE" 2>&1
find "$BACK_DIR" -maxdepth 1 -name "all_*.sql.gz.md5" -mtime +${KEEP_DAYS} -print -delete >> "$LOG_FILE" 2>&1
find "${BACK_DIR}/binlog" -maxdepth 1 -mindepth 1 -type d -mtime +${KEEP_DAYS} -exec rm -rf {} \; 2>/dev/null
# 安全兜底:清理后至少要还剩 1 份备份,否则告警
LEFT=$(ls -1 ${BACK_DIR}/all_*.sql.gz 2>/dev/null | wc -l)
if [ "$LEFT" -lt 1 ]; then
alert "清理后备份目录内无任何备份文件,请立即检查!"
fi
log "当前保留备份数:$LEFT"
#--------- ⑧ 异地同步 ---------
rsync -avz --bwlimit=10240 -e "ssh -p 22" "$BACK_FILE" "${BACK_FILE}.md5" \
backup@192.168.2.200:/remote_backup/mysql/ >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
log "异地同步成功"
else
alert "异地同步失败:$BACK_FILE(本地备份仍然有效)"
fi
log "========== 备份结束 =========="
exit 0
脚本使用与权限:
mkdir -p /opt/scripts
chmod 700 /opt/scripts/mysql_backup.sh # 内含密码,必须 700
chown root:root /opt/scripts/mysql_backup.sh
mkdir -p /myback && chmod 700 /myback
touch /var/log/mysql_backup.log && chmod 600 /var/log/mysql_backup.log
# 先手工跑一次,确认无误再进 crontab
bash -x /opt/scripts/mysql_backup.sh # -x 打开调试,逐行看执行
tail -f /var/log/mysql_backup.log
密码更安全的做法:脚本中不写密码,改用
/root/.my.cnf(chmod 600),mysqldump自动读取;或使用 MySQL 5.6+ 的mysql_config_editor set --login-path=bk --host=... --user=... --password,脚本里写mysqldump --login-path=bk。 Shell 语法(变量、条件判断、循环、函数、PIPESTATUS、退出码、重定向)详见06-01-Shell脚本编程.md。
XtraBackup 版本的备份脚本(物理备份,大库用):
#!/bin/bash
# /opt/scripts/xtrabackup_full.sh —— 每周日全备,周一至周六增备
BACK_ROOT=/xtraback
FULL_DIR=${BACK_ROOT}/full
INC_DIR=${BACK_ROOT}/inc
LOG=/var/log/xtrabackup.log
USER=bkpuser
PASS=BkpPass
DAY_OF_WEEK=$(date +%u) # 1=周一 ... 7=周日
TODAY=$(date +%F)
mkdir -p $FULL_DIR $INC_DIR
if [ "$DAY_OF_WEEK" -eq 7 ]; then
# 周日:全量备份
TARGET=${FULL_DIR}/${TODAY}
xtrabackup --defaults-file=/etc/my.cnf --backup --no-timestamp \
--target-dir=$TARGET --parallel=4 --throttle=200 \
-u$USER -p$PASS -S /var/lib/mysql/mysql.sock 2>>$LOG
else
# 其余:基于上一次备份的增量
LAST=$(ls -1dt ${INC_DIR}/* ${FULL_DIR}/* 2>/dev/null | head -1)
TARGET=${INC_DIR}/${TODAY}
xtrabackup --defaults-file=/etc/my.cnf --backup --no-timestamp \
--target-dir=$TARGET --incremental-basedir=$LAST --parallel=4 \
-u$USER -p$PASS -S /var/lib/mysql/mysql.sock 2>>$LOG
fi
# 判断成功(xtrabackup 必须看 completed OK!)
if tail -20 $LOG | grep -q "completed OK!"; then
echo "[$(date '+%F %T')] 备份成功 $TARGET" >> $LOG
tar -zcf ${TARGET}.tar.gz -C $(dirname $TARGET) $(basename $TARGET) && rm -rf $TARGET
else
echo "[$(date '+%F %T')] 备份失败 $TARGET" >> $LOG
echo "xtrabackup 失败" | mail -s "[严重] $(hostname) XtraBackup 失败" dba@example.com
exit 1
fi
# 清理 30 天前的备份
find $BACK_ROOT -name "*.tar.gz" -mtime +30 -delete
6.2 crontab 定时执行
# 编辑 root 的定时任务
crontab -e
# ┌── 分 (0-59)
# │ ┌── 时 (0-23)
# │ │ ┌── 日 (1-31)
# │ │ │ ┌── 月 (1-12)
# │ │ │ │ ┌── 周 (0-7,0和7都是周日)
# │ │ │ │ │
# * * * * * command
# 每天凌晨 2:00 全备(业务低峰)
0 2 * * * /bin/bash /opt/scripts/mysql_backup.sh >/dev/null 2>&1
# 每周日 3:00 做一次 XtraBackup 全备,周一到周六 3:00 增备(脚本内部判断星期)
0 3 * * * /bin/bash /opt/scripts/xtrabackup_full.sh >/dev/null 2>&1
# 每 10 分钟归档一次 binlog 到异地(把 RPO 降到 10 分钟)
*/10 * * * * /usr/bin/rsync -az /var/lib/mysql/mysql-bin.0* backup@192.168.2.200:/remote_backup/binlog/ >/dev/null 2>&1
# 每周一 5:00 清理 7 天前的 binlog(脚本内含 PURGE 与从库检查)
0 5 * * 1 /bin/bash /opt/scripts/purge_binlog.sh >/dev/null 2>&1
# 每月 1 号 4:00 做一次恢复演练(在测试机上自动恢复并对账,结果发邮件)
0 4 1 * * /bin/bash /opt/scripts/restore_drill.sh >/dev/null 2>&1
# 常用管理命令
crontab -l # 查看当前用户的定时任务
crontab -l -u mysql # 查看指定用户
crontab -r # 删除全部任务(危险,慎用)
systemctl status crond # CentOS 7 确认服务在跑
systemctl enable crond
service crond status # CentOS 6
tail -f /var/log/cron # 查看 cron 执行记录(只记录是否调起,不记录脚本输出)
crontab 的常见坑:
| 坑 | 说明与解决 |
|---|---|
| PATH 极简 | cron 环境的 PATH 只有 /usr/bin:/bin,脚本里的 mysqldump、xtrabackup 若在 /usr/local/bin 会「command not found」。解决:脚本内用绝对路径,或开头 export PATH=/usr/local/bin:/usr/bin:/bin:$PATH |
% 被转义 |
cron 中 % 是特殊字符,date +%F 直接写在 crontab 里会被截断,必须写成 \%F 或把命令放进脚本(推荐) |
| 无输出丢失日志 | 不写 >/dev/null 2>&1 会给 root 发本地邮件塞满 /var/spool/mail;但全丢弃又看不到错误——所以脚本内部必须自己写日志 |
| 任务重叠 | 备份没跑完下一次又启动,会双份消耗 IO。用 flock 加锁:0 2 * * * /usr/bin/flock -n /tmp/bk.lock /bin/bash /opt/scripts/mysql_backup.sh |
| 时区/时间不同步 | 服务器时间漂移导致备份时间点不准,PITR 找错位置。装 ntpd/chronyd |
| 脚本无执行权限/换行符 | Windows 上传的脚本含 \r,报 bad interpreter。用 dos2unix script.sh 修复 |
crontab 语法与
at、anacron的完整说明见04-进程与任务管理.md(本仓库对应04-系列)。
6.3 备份文件的校验与恢复演练流程
每次备份后的自动校验(已集成在脚本 ⑤ 中):
# ① 文件存在且非空
[ -s /myback/all_2026-08-22.sql.gz ] && echo "存在"
# ② 压缩完整性
gzip -t /myback/all_2026-08-22.sql.gz && echo "压缩包完整"
# tar 包
tar -tzf /myback/full.tar.gz >/dev/null && echo "tar 完整"
# ③ 逻辑备份的结尾标记
zcat /myback/all_2026-08-22.sql.gz | tail -1 | grep "Dump completed"
# ④ 物理备份的成功标记
grep "completed OK!" /var/log/xtrabackup.log
# ⑤ MD5 一致性(异地传输后核对)
md5sum -c /myback/all_2026-08-22.sql.gz.md5
# ⑥ 体积同比(突然缩小 50% 说明出问题了)
ls -l --time-style=+%F /myback/all_*.sql.gz | awk '{print $6, $5/1024/1024"MB"}'
季度恢复演练标准流程(必须形成书面记录):
□ 1. 准备独立演练环境(不得在生产库上演练!)
版本、字符集、参数与生产一致的测试机或临时容器
□ 2. 从异地备份仓库拉取备份文件(顺便验证异地备份可用、网络带宽够用)
记录:下载耗时 ______
□ 3. 校验文件(md5 + gzip -t + 结尾标记)
□ 4. 执行恢复,全程计时
逻辑:zcat all.sql.gz | mysql -uroot -p
物理:xtrabackup --prepare → --copy-back → chown → start
记录:恢复耗时 ______ (这就是真实 RTO)
□ 5. 应用 binlog 到指定时间点
记录:能恢复到的最新时间点 ______ (这就是真实 RPO)
□ 6. 数据对账(关键!)
□ 库数量、表数量一致:SELECT COUNT(*) FROM information_schema.tables WHERE ...
□ 核心表行数一致:SELECT COUNT(*) FROM orders;
□ 关键业务值一致:SELECT SUM(amount) FROM orders WHERE create_time<'...';
□ CHECKSUM 对比:CHECKSUM TABLE orders; (或 pt-table-checksum)
□ 存储过程/函数/触发器/事件/用户权限是否齐全
□ 字符集与排序规则无变化,中文无乱码
□ 7. 启动应用连上恢复后的库,跑一遍核心业务流程
□ 8. 输出演练报告:耗时、RTO/RPO 实测值、发现的问题、改进项、责任人、下次演练时间
□ 9. 清理演练环境
-- 快速对账用的表清单与行数
SELECT table_schema, COUNT(*) AS tables, SUM(table_rows) AS approx_rows
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema','performance_schema','mysql','sys')
GROUP BY table_schema;
-- 精确校验单表(数据量大时较慢)
CHECKSUM TABLE m2.a;
CHECKSUM TABLE m2.a EXTENDED; -- 逐行计算,更准更慢
# 主从/两库之间的一致性校验(生产标准工具)
pt-table-checksum --host=127.0.0.1 --user=root --ask-pass --databases=m2
pt-table-sync --print --host=127.0.0.1 --user=root --ask-pass --sync-to-master h=slave_ip
6.4 异地同步:rsync 与 scp
# ① scp:简单一次性传输
scp /myback/all_2026-08-22.sql.gz backup@192.168.2.200:/remote_backup/mysql/
scp -P 2222 -l 8000 /myback/all.sql.gz backup@remote:/backup/ # -P 端口 -l 限速(Kbit/s)
scp -r /myback/2026-08-22/ backup@remote:/remote_backup/ # -r 递归目录
# ② rsync:增量同步,支持断点续传与限速,生产首选
rsync -avz --progress /myback/ backup@192.168.2.200:/remote_backup/mysql/
# -a 归档模式(保留权限属主时间) -v 详细 -z 传输压缩 --progress 进度
rsync -avz --bwlimit=10240 --partial --append-verify \
-e "ssh -p 22 -i /root/.ssh/id_rsa" \
/myback/ backup@192.168.2.200:/remote_backup/mysql/
# --bwlimit 限速 KB/s(避免打满出口带宽)
# --partial --append-verify 断点续传
rsync -avz --delete /myback/ backup@remote:/remote_backup/mysql/ # 目标端删除源端已不存在的文件(镜像,慎用)
# ③ 免密配置(定时任务必须免密)
ssh-keygen -t rsa -N '' -f /root/.ssh/id_rsa
ssh-copy-id -i /root/.ssh/id_rsa.pub backup@192.168.2.200
ssh backup@192.168.2.200 "echo ok" # 验证免密成功
# ④ 传输后校验
md5sum /myback/all_2026-08-22.sql.gz
ssh backup@192.168.2.200 "md5sum /remote_backup/mysql/all_2026-08-22.sql.gz"
# ⑤ 上传到对象存储(异地容灾的现代做法)
# 阿里云 OSS
ossutil cp /myback/all_2026-08-22.sql.gz oss://mybucket/mysql/ -f
# AWS S3
aws s3 cp /myback/all_2026-08-22.sql.gz s3://mybucket/mysql/ --storage-class STANDARD_IA
rsync的守护进程模式、--exclude、inotify实时同步、tar/dump/restore等系统级备份工具详见03-03-备份与恢复.md。
6.5 排错清单
6.5.1 备份阶段
| 现象 / 报错 | 原因 | 解决 |
|---|---|---|
备份期间业务卡死,大量 Waiting for table metadata lock |
用了 --lock-all-tables / FTWRL,或 --lock-tables 默认锁表;且有长查询把 FTWRL 顶住,后续请求全部排队 |
改用 --single-transaction(InnoDB);备份前 kill 长查询;备份放到从库上做 |
备份中断,Lost connection to MySQL server during query |
net_write_timeout/wait_timeout 太小,或网络抖动,或大表单行超包 |
--max-allowed-packet=512M;服务端调 net_read_timeout=3600、net_write_timeout=3600、wait_timeout=28800;加 --quick(默认已开) |
Got error: 2013: Lost connection to MySQL server during query when dumping table |
同上,常见于含大 BLOB 的表 | 见上;单独用 --where 分批导那张大表 |
Got error: 1044/1045 Access denied |
备份账号缺权限 | GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, RELOAD, PROCESS, REPLICATION CLIENT, EVENT ON *.* TO 'backup'@'localhost'; |
Couldn't execute 'SHOW FUNCTION STATUS': Access denied |
加了 --routines 但账号无 SELECT ON mysql.proc(5.7 之前) |
GRANT SELECT ON mysql.proc TO backup@localhost; |
--single-transaction 备份出的数据不一致 |
库中有 MyISAM 表;或备份期间执行了 DDL | MyISAM 转 InnoDB;备份窗口禁止 DDL;或改用 --lock-all-tables |
备份文件比预期小很多 / 结尾无 Dump completed |
磁盘写满导致截断(最常见的「假备份」) | 备份前检查 df;脚本必须做体积与结尾标记校验 |
mysqldump: Error 1412: Table definition has changed |
--single-transaction 期间该表被 DDL |
重做备份,避开 DDL 窗口 |
xtrabackup 报 Can't connect to local MySQL server through socket |
socket 路径不对 | 显式 -S /var/lib/mysql/mysql.sock,或 --defaults-file 指对配置 |
xtrabackup 日志无 completed OK! 但目录有文件 |
备份实际失败 | 脚本必须用 grep "completed OK!" 判断,不能只看返回码 |
xtrabackup 报 xtrabackup: error: The transaction log ... too small |
redo log 太小,备份期间日志被覆盖(写入量大 + innodb_log_file_size 小) |
调大 innodb_log_file_size(如 1G);或用 --parallel 加快备份速度 |
| 备份把磁盘 IO 打满,业务变慢 | 顺序大 IO 抢带宽,并挤占 Buffer Pool | xtrabackup --throttle=200、--parallel 减小;ionice -c2 -n7、nice -n 19;改到从库备份 |
mysqldump 备份 GTID 实例后无法恢复 |
dump 中写入了 SET @@GLOBAL.GTID_PURGED |
备份时加 --set-gtid-purged=OFF |
6.5.2 恢复阶段
| 现象 / 报错 | 原因 | 解决 |
|---|---|---|
ERROR 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci' |
备份来自 MySQL 8.0,恢复到 5.7/MariaDB(8.0 新增排序规则低版本不识别) | ① 在源库用低版本兼容字符集重新导出;② sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g; s/CHARSET=utf8mb4/CHARSET=utf8/g' all.sql;③ 升级目标库版本。根本原则:只能低版本 → 高版本恢复,不能反向 |
Unknown collation: 'utf8mb4_unicode_520_ci' |
5.6+ 导出恢复到 5.5 | 同上,sed 替换为 utf8mb4_unicode_ci 或 utf8_general_ci |
ERROR 1064 You have an error in your SQL syntax |
高版本语法(如 8.0 的 DEFAULT (expr)、ROW_FORMAT、窗口函数、CHECK 约束)在低版本无法解析 |
手工改 SQL;或用源版本客户端加 --compatible=mysql40/mysql5 导出 |
中文乱码(显示 ??? 或 测试) |
导出与导入字符集不一致;dump 文件缺 SET NAMES |
导出加 --default-character-set=utf8;导入前 SET NAMES utf8; 或 mysql --default-character-set=utf8 < all.sql;确认 SHOW VARIABLES LIKE 'character_set%' 三端一致;确认 dump 文件实际编码 file all.sql |
ERROR 1005/1215: Cannot add foreign key constraint |
子表先于父表恢复;或引擎/类型/字符集不一致 | 恢复前 SET FOREIGN_KEY_CHECKS=0;(mysqldump 默认已加,用 --compact 导出的文件会缺这行) |
ERROR 1062: Duplicate entry for key 'PRIMARY' |
目标表已有数据,恢复的备份不含 DROP TABLE(用了 --skip-add-drop-table 或 -t) |
先清空目标表/库;或用 --insert-ignore/--replace 重新导出 |
ERROR 2006: MySQL server has gone away |
单条 SQL 超过 max_allowed_packet(--extended-insert 合并出的超长 INSERT) |
服务端 SET GLOBAL max_allowed_packet=512M; 并写进配置重启;客户端 mysql --max-allowed-packet=512M;导出时减小 --net-buffer-length |
| 恢复极慢(几十小时) | 逐条 INSERT + 逐行维护索引 + 每条写 binlog + 每次提交 fsync | 见 §2.4.3:SET SQL_LOG_BIN=0、FOREIGN_KEY_CHECKS=0、UNIQUE_CHECKS=0、AUTOCOMMIT=0、innodb_flush_log_at_trx_commit=2、sync_binlog=0、调大 innodb_buffer_pool_size;大库改用物理备份 |
| binlog 找不到对应 position | ① 位置取到了事件中间;② 该 binlog 已被 PURGE/expire_logs_days 删除;③ 找错了日志文件 |
① 用 SHOW BINLOG EVENTS 或 mysqlbinlog 确认事件边界,--stop-position 取 BEGIN 的 Pos、--start-position 取 COMMIT 的 End_log_pos;② 从异地归档目录找;③ 用 --start-datetime 先粗定位文件,再用 position 精确定位 |
mysqlbinlog: Error in Log_event::read_log_event(): 'Sanity check failed' |
position 不在事件边界,或日志文件损坏 | 校正 position;--verify-binlog-checksum 检查损坏;5.6+ 解析 5.5 日志需加 --binlog-checksum 兼容处理 |
mysqlbinlog \| mysql 执行后数据没变 |
GTID 模式下事务被判定为「已执行」而跳过 | 加 --skip-gtids |
ERROR 1236: Could not find first log file name in binary log index file |
从库要的 binlog 已被主库删除 | 重建从库(用全备 + 位点);今后 expire_logs_days 设大、PURGE 前检查从库位点 |
| 磁盘空间不足(恢复中途失败) | 未评估恢复所需空间:数据 + 索引 + redo + 临时文件 | 恢复前确保剩余空间 ≥ 备份解压后体积 × 2~3;把 tmpdir 指到大盘;分库分表逐个恢复 |
物理恢复后启动失败,Permission denied / Can't open and lock privilege tables |
属主/权限/SELinux 未恢复 | chown -R mysql:mysql /var/lib/mysql、chmod 750、restorecon -Rv /var/lib/mysql;看 /var/log/mysqld.log |
物理恢复后启动失败,InnoDB: Ignoring the redo log due to missing MLOG_CHECKPOINT |
忘了 --prepare,或增量合并顺序/--apply-log-only 用错 |
严格按 §5.3.5 顺序重做 prepare |
| 克隆实例后主从复制报 UUID 冲突 | 物理拷贝带走了 auto.cnf |
删除新实例的 /var/lib/mysql/auto.cnf 后重启,自动生成新 UUID |
| 恢复后存储过程/触发器/事件都不见了 | 备份时没加 --routines --triggers --events |
从源库补导;今后模板固定加这三个参数 |
| 恢复后用户和权限丢失 | 只恢复了业务库,没恢复 mysql 库 |
用 -A 全备;或单独 mysqldump -B mysql,恢复后 FLUSH PRIVILEGES;;也可用 pt-show-grants 单独备份权限 |
6.5.3 关键参数速查(排错时常用)
-- 连接与包大小
SHOW VARIABLES LIKE 'max_allowed_packet'; -- 建议 64M~512M
SHOW VARIABLES LIKE 'net_read_timeout'; -- 默认 30,备份/恢复建议 3600
SHOW VARIABLES LIKE 'net_write_timeout'; -- 默认 60,建议 3600
SHOW VARIABLES LIKE 'wait_timeout'; -- 默认 28800
SHOW VARIABLES LIKE 'interactive_timeout';
-- 一致性与性能
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; -- 生产 1
SHOW VARIABLES LIKE 'sync_binlog'; -- 生产 1
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_log_file_size';
-- 字符集
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
-- 空间占用(找出大表,评估备份耗时与空间)
SELECT table_schema, table_name,
ROUND(data_length/1024/1024,2) AS data_mb,
ROUND(index_length/1024/1024,2) AS index_mb,
ROUND((data_length+index_length)/1024/1024,2) AS total_mb
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema','performance_schema','mysql','sys')
ORDER BY (data_length+index_length) DESC LIMIT 10;
# /etc/my.cnf 中与备份恢复相关的推荐配置
[mysqld]
max_allowed_packet = 128M
net_read_timeout = 3600
net_write_timeout = 3600
innodb_file_per_table = 1 # 必须开,否则无法单表恢复
log_bin = mysql-bin
server_id = 1
binlog_format = ROW
sync_binlog = 1
expire_logs_days = 15
innodb_flush_log_at_trx_commit = 1
[mysqldump]
quick
max_allowed_packet = 512M
single-transaction
default-character-set = utf8
[mysql]
default-character-set = utf8
safe-updates # 无 WHERE 的 UPDATE/DELETE 直接报错,防误删
6.6 备份恢复命令速查
| 目标 | 命令 |
|---|---|
| 全库热备(InnoDB) | mysqldump -uroot -p --single-transaction --master-data=2 -R -E --triggers -A > all.sql |
| 全库温备(含 MyISAM) | mysqldump -uroot -p --lock-all-tables --master-data=2 -A > all.sql |
| 单库备份 | mysqldump -uroot -p -B m2 > m2.sql |
| 单表备份 | mysqldump -uroot -p m2 a > a.sql |
| 只备结构 / 只备数据 | mysqldump -uroot -p -A -d / mysqldump -uroot -p -B m2 -t |
| 压缩备份 | mysqldump -uroot -p -A \| gzip > all.sql.gz |
| 恢复全库 | mysql -uroot -p < all.sql |
| 恢复到指定库 | mysql -uroot -p m2 < a.sql |
| 交互恢复 | mysql> SOURCE /myback/all.sql; |
| 查看 binlog 列表 / 当前位点 | SHOW BINARY LOGS; / SHOW MASTER STATUS; |
| 查看 binlog 事件 | SHOW BINLOG EVENTS IN 'mysql-bin.000002' FROM 245 LIMIT 20; |
| 切换 / 清理 binlog | FLUSH LOGS; / PURGE BINARY LOGS TO 'mysql-bin.000005'; |
| 解析 ROW 格式 binlog | mysqlbinlog --base64-output=decode-rows -v mysql-bin.000002 |
| 按位置恢复 | mysqlbinlog --start-position=245 --stop-position=851 mysql-bin.000002 \| mysql -uroot -p |
| 按时间恢复 | mysqlbinlog --start-datetime='...' --stop-datetime='...' mysql-bin.000002 \| mysql -uroot -p |
| binlog 实时远程归档 | mysqlbinlog -R --raw --stop-never -h主库 -urepl -p mysql-bin.000001 |
| 多线程逻辑备份 / 恢复 | mydumper -u root -p -B m2 -t 8 -c -o /myback / myloader -u root -p -d /myback -o -t 8 |
| 物理全备 | xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/myback --parallel=4 -ubkp -p |
| 物理增备 | xtrabackup --backup --target-dir=/inc/inc1 --incremental-basedir=/myback -ubkp -p |
| 物理 prepare(非最后一次) | xtrabackup --prepare --apply-log-only --target-dir=/myback --incremental-dir=/inc/inc1 |
| 物理恢复 | xtrabackup --copy-back --target-dir=/myback; chown -R mysql:mysql /var/lib/mysql |
| innobackupex 全备 / 恢复 | innobackupex --defaults-file=/etc/my.cnf /myback / --apply-log → --copy-back |
| 单表表空间导出 / 导入 | FLUSH TABLES a FOR EXPORT; → 拷 .ibd/.cfg → ALTER TABLE a DISCARD/IMPORT TABLESPACE; |
| 冷备 | systemctl stop mysqld; tar -zcvpf mysql.tar.gz -C /var/lib mysql; systemctl start mysqld |
| 校验备份 | gzip -t f.gz、zcat f.gz \| tail -1 \| grep "Dump completed"、grep "completed OK!" xtrabackup.log |
6.7 生产红线
1. 严禁在生产库上直接执行 mysql < all_backup.sql(会 DROP 掉现有数据)。
2. 严禁用 rm 删除 binlog,必须用 PURGE BINARY LOGS。
3. 严禁在主库上执行 RESET MASTER。
4. 严禁只有一份备份、只在本机保存备份。
5. 严禁跳过恢复演练——没演练过的备份视为不存在。
6. 严禁在没有 --single-transaction 的情况下对生产 InnoDB 库做 mysqldump(会锁表)。
7. 严禁高版本备份恢复到低版本(collation/语法不兼容)。
8. 严禁在备份窗口内执行 DDL。
9. 物理恢复后必须 chown -R mysql:mysql 并 restorecon。
10. 恢复期间临时调低的 innodb_flush_log_at_trx_commit / sync_binlog 必须改回 1。
11. 误操作发生后第一动作是「止损 + 保护现场(备份当前 binlog 与 datadir)」,不是马上恢复。
12. 备份脚本中的密码文件权限必须 600,备份目录必须 700。
七、练习
7.1 基础题
- 说明物理备份与逻辑备份的区别,并各举两个工具。
- 全量备份、增量备份、差异备份的区别是什么?各自的恢复链有多长?
- 冷备、温备、热备分别对应数据库的什么状态?MyISAM 能做热备吗?为什么?
- RPO 与 RTO 分别由什么决定?只做每日全备、不保留 binlog 的 RPO 是多少?
mysqldump加-B与不加-B导出的文件有什么区别?恢复方式有何不同?--single-transaction与--lock-all-tables有什么区别?对 MyISAM 表分别是什么效果?- binlog 的三种格式是什么?生产为什么推荐 ROW?
- 为什么不能用
rm删除 binlog 文件?正确做法是什么? - 判断
mysqldump备份文件完整的标志是什么?判断xtrabackup备份成功的标志是什么? - XtraBackup 增量备份合并时,
--apply-log-only(或--redo-only)什么时候要加、什么时候不能加?
7.2 实操题
- 在测试库中完成:建库
m2、建表a、插入 2 行数据 → 全备(切换 binlog)→ 再插入 1 行 → 备份 binlog →drop database m2→ 完整恢复到 3 行数据。写出全部命令。 - 编写一个 shell 脚本,实现:每天全备、按日期命名、gzip 压缩、保留 7 天、失败发邮件、写日志。
- 写出对应的 crontab 配置:每天 2:00 执行上述脚本,每 10 分钟同步 binlog 到 192.168.2.200。
- 用
mysqldump只导出m2库中orders表 2026 年 8 月的数据。 - 模拟误删:
DELETE FROM m2.a;后,用「全备 + binlog 跳过误操作」恢复,且保留误操作之后的正常数据。写出定位 position 与两段式重放的完整命令。 - 用 XtraBackup 完成「一次全备 + 两次增备 → 合并 → 恢复 → 验证」的完整链条。
- 用表空间传输把
m2.a表从 A 实例搬到 B 实例。
7.3 排错题
- 恢复时报
ERROR 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci',原因与三种解决方案。 - 备份中途报
Got error: 2013: Lost connection to MySQL server during query,可能的两个原因及对应参数。 - 执行
mysqlbinlog --start-position=1000 ... | mysql后数据没有任何变化,可能是什么原因? - 物理恢复后 MySQL 启动失败,日志显示
Can't open and lock privilege tables: Permission denied,如何处理? - 备份脚本每天都「成功」,但某天需要恢复时发现文件只有 3KB,如何在脚本层面提前发现这种问题?
八、练习参考答案
1. 物理备份直接拷贝数据文件(二进制),备份/恢复快、体积大、不可读、跨版本差;逻辑备份导出为 SQL 文本,慢、体积小、可读可编辑、跨版本跨引擎好。物理工具:cp/tar 冷备、LVM 快照、XtraBackup/innobackupex、mariabackup。逻辑工具:mysqldump、mysqlpump、mydumper、SELECT ... INTO OUTFILE。
2. 全量备份全部数据;增量备份「自上一次任意备份以来」的变化,体积最小但恢复需全备 + 所有增备(链条长,断一环即失败);差异备份「自最近一次全备以来」的全部变化,体积居中,恢复只需全备 + 最后一次差备(2 步)。MySQL 中增量的载体通常是 binlog(逻辑)或 LSN 增量(XtraBackup)。
3. 冷备 = 数据库完全停止;温备 = 运行中但加全局读锁,只读不可写(FTWRL / --lock-all-tables);热备 = 运行中读写均正常(--single-transaction 或 XtraBackup)。MyISAM 不能热备,因为它不支持事务、没有 MVCC 和 redo log,无法在不锁表的情况下取得一致性快照。
4. RPO(能丢多少数据)由备份/日志频率决定,binlog 实时归档可使 RPO≈0;RTO(多久恢复完)由备份方式与数据量决定,物理备份和热备从库能显著降低 RTO。只做每日全备且无 binlog,RPO 最坏为 24 小时。
5. 加 -B:dump 中含 CREATE DATABASE IF NOT EXISTS 与 USE,可直接 mysql < file 恢复,会恢复到原库名。不加 -B:只有建表与 INSERT,必须 mysql -uroot -p 目标库 < file 指定库,且库要先存在——好处是可以恢复到另一个库名,便于克隆和「恢复到临时库再回搬」。
6. --single-transaction 通过 START TRANSACTION WITH CONSISTENT SNAPSHOT 在 MVCC 快照中导出,全程不锁表,对 InnoDB 有效、对 MyISAM 无效(数据可能不一致);--lock-all-tables 对全库加全局读锁(FTWRL)直到导出结束,对两种引擎都有效但全库只读。两者互斥。
7. STATEMENT(记 SQL 语句,体积小但含 NOW()/UUID()/RAND() 等不确定函数时不安全)、ROW(记每行前后镜像,绝对安全、可闪回、体积大)、MIXED(默认 SBR,不安全时自动切 RBR)。生产推荐 ROW,因为:数据一致性优先;改用 RC 隔离级别的前提;只有 ROW 能做闪回恢复;Canal 等订阅工具只支持 ROW。
8. rm 删除后 mysql-bin.index 中仍保留记录,导致 SHOW BINARY LOGS 与实际不符、PURGE 失败、从库找不到日志而复制中断(ERROR 1236)。正确做法:PURGE BINARY LOGS TO 'mysql-bin.000005'; 或 PURGE BINARY LOGS BEFORE '2026-08-15 00:00:00';,并在清理前检查所有从库已消费到的位点。
9. mysqldump 文件末尾必须有 -- Dump completed on ...(tail -1 all.sql | grep "Dump completed");xtrabackup 日志末尾必须有 completed OK!(grep "completed OK!" xtrabackup.log)。只看目录里有文件或只看返回码都不可靠。
10. 合并增量时,除最后一次之外的所有 prepare 都必须加 --apply-log-only / --redo-only(只应用 redo、不回滚未提交事务,以便继续合并后续增量);最后一次绝不能加(此时才回滚未提交事务,使数据达到最终一致状态)。加错会导致数据不一致或无法再合并后续增量。
11.
mkdir /myback
mysql -uroot -p -e "create database m2; create table m2.a (id int(2),name char(10),sex char(1)); \
insert into m2.a values (1,'zhangsan','f'),(2,'lisi','m');"
mysqldump -uroot -p -B m2 -F > /myback/all.sql # 全备并切换 binlog
mysql -uroot -p -e "insert into m2.a values (3,'wangwu','f');"
mysql -uroot -p -e "FLUSH LOGS;" # 让含新数据的日志封口
cp /var/lib/mysql/mysql-bin.000002 /myback # 备份增量
mysql -uroot -p -e "drop database m2;" # 模拟误删
mysql -uroot -p < /myback/all.sql # 恢复全备(回到 2 行)
mysqlbinlog /myback/mysql-bin.000002 | mysql -uroot -p # 应用增量(回到 3 行)
mysql -uroot -p -e "select * from m2.a;" # 验证
12. 见 §6.1 的 /opt/scripts/mysql_backup.sh。要点:变量集中定义、log()/alert() 函数、前置检查磁盘与连通性、--single-transaction --master-data=2 -F -R -E --triggers、管道中用 ${PIPESTATUS[0]} 取 mysqldump 退出码、三重校验(返回码 + 体积 + gzip -t 与 Dump completed)、find -mtime +7 -delete 清理并做「至少剩 1 份」兜底、rsync 异地同步、脚本 chmod 700。
13.
0 2 * * * /bin/bash /opt/scripts/mysql_backup.sh >/dev/null 2>&1
*/10 * * * * /usr/bin/rsync -az /var/lib/mysql/mysql-bin.0* backup@192.168.2.200:/remote_backup/binlog/ >/dev/null 2>&1
注意:脚本内用绝对路径或 export PATH;crontab 里的 % 需转义(所以 date +%F 应放在脚本中);防重叠可用 flock -n /tmp/bk.lock。
14.
mysqldump -uroot -p m2 orders \
--where="create_time >= '2026-08-01' AND create_time < '2026-09-01'" \
--single-transaction > /myback/orders_2026-08.sql
15.
# ① 止损与保护现场
mysql -uroot -p -e "SET GLOBAL read_only=ON; FLUSH LOGS;"
cp -a /var/lib/mysql/mysql-bin.* /safe/
# ② 定位误操作事务的起止位置
mysql -uroot -p -e "SHOW BINLOG EVENTS IN 'mysql-bin.000002';"
mysqlbinlog --base64-output=decode-rows -v /safe/mysql-bin.000002 | grep -n -B5 -i "DELETE FROM"
# 假设:误操作 BEGIN 的 Pos = 851,其 Xid 的 End_log_pos = 1111
# ③ 恢复全备
mysql -uroot -p -e "DROP DATABASE IF EXISTS m2;"
mysql -uroot -p < /myback/all.sql
# ④ 两段式重放,跳过 851~1111 这个误操作事务
mysqlbinlog --start-position=245 --stop-position=851 /safe/mysql-bin.000002 > /myback/inc1.sql
mysqlbinlog --start-position=1111 /safe/mysql-bin.000002 > /myback/inc2.sql
grep -i -E "delete from|drop |truncate" /myback/inc1.sql /myback/inc2.sql # 人工确认
mysql -uroot -p < /myback/inc1.sql
mysql -uroot -p < /myback/inc2.sql
# ⑤ 验证后放开写入
mysql -uroot -p -e "select count(*) from m2.a; SET GLOBAL read_only=OFF;"
关键:--stop-position 取误操作事务 BEGIN 的 Pos,--start-position 取其 COMMIT(Xid) 的 End_log_pos;位置必须落在事件边界。GTID 模式下还需加 --skip-gtids。
16.
# 全备
xtrabackup --defaults-file=/etc/my.cnf --backup --target-dir=/myback -uroot -p -S /var/lib/mysql/mysql.sock
cat /myback/xtrabackup_checkpoints # to_lsn 记下
# 第一次增备
mysql -uroot -p -e "create table a.a2 (id int);"
xtrabackup --backup --target-dir=/inc/inc1 --incremental-basedir=/myback -uroot -p
# 第二次增备
mysql -uroot -p -e "create table a.a3 (id int);"
xtrabackup --backup --target-dir=/inc/inc2 --incremental-basedir=/inc/inc1 -uroot -p
# 合并(除最后一次外都加 --apply-log-only)
systemctl stop mysqld ; mv /var/lib/mysql /var/lib/mysql.bak
xtrabackup --prepare --apply-log-only --target-dir=/myback
xtrabackup --prepare --apply-log-only --target-dir=/myback --incremental-dir=/inc/inc1
xtrabackup --prepare --target-dir=/myback --incremental-dir=/inc/inc2
# 恢复
mkdir -p /var/lib/mysql
xtrabackup --copy-back --target-dir=/myback
chown -R mysql:mysql /var/lib/mysql ; restorecon -Rv /var/lib/mysql
systemctl start mysqld
mysql -uroot -p -e "use a; show tables;" # 应看到 a1 a2 a3
检查链条完整性:核对相邻备份的 to_lsn 与 from_lsn 是否首尾相接。
17.
-- A 实例
FLUSH TABLES m2.a FOR EXPORT; -- 本会话保持不退出
cp /var/lib/mysql/m2/a.ibd /var/lib/mysql/m2/a.cfg /myback/
scp /myback/a.ibd /myback/a.cfg root@B:/tmp/
-- A 实例
UNLOCK TABLES;
-- B 实例:建结构完全一致的空表
CREATE TABLE m2.a (id INT(2), name CHAR(10), sex CHAR(1)) ENGINE=InnoDB;
ALTER TABLE m2.a DISCARD TABLESPACE;
cp /tmp/a.ibd /tmp/a.cfg /var/lib/mysql/m2/ ; chown mysql:mysql /var/lib/mysql/m2/a.*
ALTER TABLE m2.a IMPORT TABLESPACE;
CHECK TABLE m2.a; SELECT COUNT(*) FROM m2.a;
前提:innodb_file_per_table=ON,两端表结构、行格式、字符集完全一致。
18. 原因:utf8mb4_0900_ai_ci 是 MySQL 8.0 新增的排序规则,5.7/MariaDB 不认识——备份从高版本恢复到了低版本。三种解决:① 在源库用 --default-character-set=utf8 或 --compatible 重新导出(推荐从源头解决);② sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' all.sql 替换后再恢复;③ 把目标库升级到 8.0。根本原则:只能低版本 → 高版本恢复。
19. ① 单条 SQL 或单行数据超过 max_allowed_packet(常见于 BLOB 大字段)→ 加 --max-allowed-packet=512M,服务端也调大;② 读写超时或网络中断 → 服务端调大 net_read_timeout、net_write_timeout(如 3600)、wait_timeout,并确认 --quick 生效、避开网络抖动;大表可用 --where 分批导出。
20. 最常见原因是 GTID 模式下这些事务的 GTID 已存在于 gtid_executed 中,MySQL 认为已执行而直接跳过 → 加 --skip-gtids。其他可能:--start-position 之后的区间本身没有该库的变更;用了 --database 过滤而语句是跨库执行的(STATEMENT 格式下会被漏掉);sql_log_bin=0 的会话产生的变更本就不在 binlog 中。
21. 属主/权限/SELinux 未恢复。处理:
chown -R mysql:mysql /var/lib/mysql
chmod 750 /var/lib/mysql
restorecon -Rv /var/lib/mysql # 数据目录非默认位置时先 semanage fcontext -a -t mysqld_db_t
tail -50 /var/log/mysqld.log # 复查错误日志
systemctl start mysqld
22. 在脚本中做三重校验并告警(见 §6.1 ⑤):① 用 ${PIPESTATUS[0]} 判断 mysqldump 真实退出码(管道下 $? 只反映 gzip);② 判断文件体积是否小于阈值 MIN_SIZE,并可与前一天体积做同比(缩小超过 50% 告警);③ gzip -t 校验压缩完整性 + zcat | tail -1 | grep "Dump completed" 校验结尾标记。另外:备份前检查磁盘剩余空间、记录 MD5、把「最新备份年龄/体积/耗时」接入监控告警,并坚持季度恢复演练。
本文档小结:备份的核心公式是「全量备份 + binlog = 可恢复到任意时间点」。小库用
mysqldump --single-transaction --master-data=2,大库用xtrabackup全量 + 增量,binlog 必须实时归档到异地。所有方案都必须经过恢复演练验证——没有验证过恢复的备份等于没有备份。
此处评论已关闭