279 lines
9.3 KiB
Markdown
279 lines
9.3 KiB
Markdown
---
|
||
tags: [MySQL, mysqldump, XtraBackup, PITR, 备份恢复]
|
||
create time: 2026-05-16 00:00
|
||
---
|
||
|
||
# 备份与恢复
|
||
|
||
## 概述
|
||
|
||
备份是数据安全最后一道防线。本章覆盖逻辑备份(mysqldump)、物理备份(Percona XtraBackup)、以及基于 binlog 的时间点恢复(PITR)三大核心方案。
|
||
|
||
## 备份策略全景
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph "逻辑备份"
|
||
LD["mysqldump<br/>导出 SQL"]
|
||
LF["全量 + 增量<br/>依赖 binlog"]
|
||
end
|
||
|
||
subgraph "物理备份"
|
||
PD["Percona XtraBackup<br/>热拷贝ibd文件"]
|
||
PF["压缩 + 加密<br/>支持部分备份"]
|
||
end
|
||
|
||
subgraph "选择指南"
|
||
LS["数据量 < 50GB<br/>停机窗口可接受"]
|
||
PS["数据量 > 50GB<br/>需要 0 停机"]
|
||
end
|
||
|
||
LD --> LS
|
||
PD --> PS
|
||
|
||
style LS fill:#00B6BC,color:#fff
|
||
style PS fill:#00D866,color:#fff
|
||
```
|
||
|
||
## mysqldump 逻辑备份
|
||
|
||
### 全量备份
|
||
|
||
```bash
|
||
# 最基础的全量备份
|
||
mysqldump -u root -p --all-databases > all_dbs_$(date +%F).sql
|
||
|
||
# 推荐的参数组合(保持一致性和完整性)
|
||
mysqldump \
|
||
-u root -p \
|
||
--single-transaction # MVCC 快照,不锁表
|
||
--flush-logs # 切换 binlog,便于后续恢复
|
||
--master-data=2 # 记录 binlog position(注释形式)
|
||
--routines # 包含存储过程和函数
|
||
--triggers # 包含触发器
|
||
--events # 包含事件调度器
|
||
--databases app_db report_db > backup_$(date +%F).sql
|
||
```
|
||
|
||
### 还原
|
||
|
||
```bash
|
||
# 全量还原
|
||
mysql -u root -p < backup_2026-05-16.sql
|
||
|
||
# 只还原某个表(先导出再导入)
|
||
mysqldump -u root -p app_db --table users > users.sql
|
||
mysql -u root -p app_db < users.sql
|
||
```
|
||
|
||
### mysqldump 的优缺点
|
||
|
||
| 优点 | 缺点 |
|
||
|------|------|
|
||
| 简单可靠,几乎不会出错 | 大库慢(导入/导出)|
|
||
| 文本格式,可读可编辑 | 导出的数据和原始可能有差异(如字符集转换)|
|
||
| 自带权限和表结构定义 | 不支持增量备份 |
|
||
| 跨版本迁移方便 | 备份期间虽然有 `--single-transaction`,但 `--flush-logs` 仍需短暂锁表 |
|
||
|
||
## Percona XtraBackup(物理备份)
|
||
|
||
### 全量备份
|
||
|
||
```bash
|
||
# 全量备份(热备,不停机)
|
||
xtrabackup --backup \
|
||
--target-dir=/data/backups/full/ \
|
||
--user=root --password=xxx \
|
||
--parallel=4 # 多线程并行拷贝
|
||
|
||
# 备份后的准备阶段(使备份可用于恢复)
|
||
xtrabackup --prepare --target-dir=/data/backups/full/
|
||
|
||
# 增量备份(基于上一次备份)
|
||
xtrabackup --backup \
|
||
--target-dir=/data/backups/inc1/ \
|
||
--incremental-basedir=/data/backups/full/ \
|
||
--user=root --password=xxx
|
||
|
||
# 合并增量到全量(减少恢复时间)
|
||
# 先对全量做 prepare,只应用已提交的事务,不回滚未提交的
|
||
xtrabackup --prepare --target-dir=/data/backups/full/ --apply-only
|
||
# 再将增量备份合并进来
|
||
xtrabackup --prepare --target-dir=/data/backups/full/ --incremental-dir=/data/backups/inc1/
|
||
|
||
# 如果有多个增量层,依次合并
|
||
xtrabackup --prepare --target-dir=/data/backups/full/ --incremental-dir=/data/backups/inc2/
|
||
```
|
||
|
||
### 恢复
|
||
|
||
```bash
|
||
# 1. 停止 MySQL
|
||
systemctl stop mysqld
|
||
|
||
# 2. 清空数据目录(或用新目录)
|
||
rm -rf /var/lib/mysql/*
|
||
|
||
# 3. 拷贝备份数据
|
||
cp -a /data/backups/full/. /var/lib/mysql/
|
||
|
||
# 4. 设置权限
|
||
chown -R mysql:mysql /var/lib/mysql
|
||
|
||
# 5. 启动 MySQL
|
||
systemctl start mysqld
|
||
```
|
||
|
||
### XtraBackup 的优缺点
|
||
|
||
| 优点 | 缺点 |
|
||
|------|------|
|
||
| **热备份**:备份期间业务不受影响 | 学习曲线较陡 |
|
||
| 速度快:直接拷贝 ibd 文件 | 需要安装额外软件 |
|
||
| 支持增量备份 | 不能跨 major version(8.0→8.0)|
|
||
| 恢复速度远快于 mysqldump | 占用较多磁盘空间(物理拷贝)|
|
||
|
||
## PITR — 时间点恢复
|
||
|
||
结合全量备份 + binlog,可以将数据库恢复到任意精确时刻。
|
||
|
||
> [!QUESTION] 思考:如果凌晨 3 点的备份完成时 binlog position 是 `mysql-bin.000005:1234`,而 10 点误操作发生在 `mysql-bin.000006` 中,该怎么办?
|
||
> 答案是:binlog 链不能断——从 `mysql-bin.000005:1234` 开始到当前为止的所有 binlog 文件都必须保留。任何缺失都会导致 PITR 失败。
|
||
|
||
```mermaid
|
||
timeline
|
||
title PITR 恢复流程
|
||
00:00 : 凌晨全量备份 (XtraBackup)
|
||
06:00 : 应用正常运行
|
||
10:00 : ⚠️ 误删表!DELETE FROM users;
|
||
10:01 : 检测到异常,开始恢复
|
||
恢复过程 : 1. 恢复全量备份到 00:00
|
||
: 2. 重放 binlog 到 09:59:59
|
||
: 3. 跳过误操作语句
|
||
: 4. 恢复到 10:00 之前的状态
|
||
```
|
||
|
||
```bash
|
||
# 第一步:恢复全量备份
|
||
# (前面已讲过 XtraBackup 恢复步骤)
|
||
|
||
# 第二步:找到误操作的准确位置
|
||
# 方法 A:用时间定位(推荐,最直观)
|
||
mysqlbinlog --stop-datetime='2026-05-16 09:59:59' /var/lib/mysql/mysql-bin.000006 > safe_events.sql
|
||
|
||
# 方法 B:搜索误操作语句,获取 position
|
||
mysqlbinlog /var/lib/mysql/mysql-bin.000006 | grep -B5 "DROP DATABASE\|DELETE FROM users"
|
||
# 找到对应的 DELETE 所在事务的 stop_position,在此之前停止
|
||
|
||
# 第三步:重放 binlog 到误操作之前(基于 position)
|
||
mysqlbinlog \
|
||
--start-position=1234 \
|
||
--stop-position=4567 \
|
||
/var/lib/mysql/mysql-bin.000005 > before_incident.sql
|
||
|
||
# 第四步:跳过误操作行,手动编辑 SQL 后重放其余部分
|
||
# 方法一:导出到文件后用 sed/grep 过滤掉误操作的 DDL/DML
|
||
mysqlbinlog /var/lib/mysql/mysql-bin.000005 > all_events.sql
|
||
# 用编辑器打开 all_events.sql,定位到误操作的 BEGIN/COMMIT 区间并删除
|
||
|
||
# 方法二:精确用 position 范围分段重放
|
||
mysqlbinlog \
|
||
--stop-position=4567 \
|
||
/var/lib/mysql/mysql-bin.000005 | mysql -u root -p # 重放到误操作前
|
||
|
||
mysqlbinlog \
|
||
--start-position=5000 \
|
||
/var/lib/mysql/mysql-bin.000005 | mysql -u root -p # 从安全点继续重放
|
||
|
||
# MySQL 8.0+ GTID 模式(推荐,更简洁)
|
||
# 包含 uuid 下 1~100 的 GTID,排除第 101 个事务(即误操作的那个)
|
||
mysqlbinlog \
|
||
--include-gtids='uuid:1-100' \
|
||
--exclude-gtids='uuid:101' \
|
||
mysql-bin.000005 | mysql -u root -p
|
||
```
|
||
|
||
> [!WARNING] PITR 的关键约束
|
||
> - **必须有完整的 binlog 链**:全量备份时的 binlog position 之后所有的 binlog 都不能丢
|
||
> - **`binlog_format` 必须是 ROW 或 MIXED**:STATEMENT 模式下 PITR 不可靠(相同的 SQL 在不同上下文中可能产生不同结果)
|
||
> - **GTID 需要 `gtid_executed` 一致**:使用 `--include-gtids` / `--exclude-gtids` 要求服务端启用了 `enforce_gtid_consistency=ON`
|
||
> - **恢复到某个时间点会覆盖该时间点之后的所有数据**:做好评估,建议先在测试库验证恢复流程
|
||
|
||
## 备份验证与自动化
|
||
|
||
> [!QUESTION] 思考:如果一周后发生灾难,你发现昨晚的备份根本打不开——那时候还来得及补救吗?
|
||
> **未经测试的备份等于没有备份**。定期验证是备份策略中最重要的环节。
|
||
|
||
### 验证方法
|
||
|
||
```bash
|
||
# mysqldump SQL 文件验证:在测试库导入一次
|
||
mysql -u root -p test_db < backup_latest.sql
|
||
# 检查关键表的数据量和行数是否符合预期
|
||
|
||
# XtraBackup 验证:prepare 阶段就是第一次验证
|
||
xtrabackup --prepare --target-dir=/data/backups/latest/
|
||
# prepare 成功 = 备份文件完整性合格
|
||
# 更进一步:恢复到从库或测试机做一次完整演练(至少每季度一次)
|
||
```
|
||
|
||
### 自动化方案
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# /opt/scripts/mysql-backup.sh — 每日备份脚本
|
||
set -euo pipefail
|
||
|
||
BACKUP_DIR="/data/backups"
|
||
DATE=$(date +%F)
|
||
RETENTION_DAYS=7
|
||
|
||
# 1. XtraBackup 全量备份
|
||
xtrabackup --backup \
|
||
--target-dir="${BACKUP_DIR}/${DATE}/" \
|
||
--user=backup_user --password=$(cat /etc/backup_pass) \
|
||
--parallel=4
|
||
|
||
# 2. 压缩备份目录(节省磁盘空间)
|
||
tar czf "${BACKUP_DIR}/${DATE}.tar.gz" -C "${BACKUP_DIR}" "${DATE}"
|
||
rm -rf "${BACKUP_DIR}/${DATE}"
|
||
|
||
# 3. 清理超过保留期的旧备份
|
||
find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +${RETENTION_DAYS} -delete
|
||
|
||
# 4. 备份元数据记录(方便追踪和恢复时定位)
|
||
echo "$(date +%F %T) | full | ${DATE} | OK" >> "${BACKUP_DIR}/backup.log"
|
||
```
|
||
|
||
```cron
|
||
# crontab -e — 每天凌晨 2 点执行
|
||
0 2 * * * /opt/scripts/mysql-backup.sh >> /var/log/mysql-backup-cron.log 2>&1
|
||
```
|
||
|
||
### 快速恢复清单
|
||
|
||
> [!CHECKLIST] PITR 快速恢复步骤
|
||
> 1. [ ] 确认误操作的准确时间
|
||
> 2. [ ] 找到最新可用的全量备份及其 binlog position
|
||
> 3. [ ] 停止应用写操作(防止二次损害)
|
||
> 4. [ ] 恢复全量备份到干净目录
|
||
> 5. [ ] 用 `--stop-datetime` 重放 binlog 到误操作前
|
||
> 6. [ ] 启动 MySQL 并验证关键表数据
|
||
> 7. [ ] 切换流量到新实例
|
||
> 8. [ ] 通知相关人员,更新 incident 记录
|
||
|
||
## 备份频率建议
|
||
|
||
| 数据类型 | 全量备份频率 | 增量备份频率 | binlog 留存 |
|
||
|---------|-----------|-----------|-----------|
|
||
| 核心交易数据 | 每天一次 | 每小时 | ≥ 7 天 |
|
||
| 用户数据 | 每天一次 | 每天 | ≥ 7 天 |
|
||
| 日志/归档数据 | 每周一次 | 按需 | 按需 |
|
||
| 测试环境 | 按需 | — | 按需 |
|
||
|
||
## 关联笔记
|
||
|
||
- [[hhs/MySQL/07-高可用与分布式/29-Binary Log]] — Binlog 是 PITR 的基础
|
||
- [[hhs/MySQL/07-高可用与分布式/30-主从复制详解]] — 从库可以作为只读备份节点
|
||
- [[hhs/DEV/Go-Database]] — Go 应用中处理数据库恢复错误的最佳实践
|