MySQL 数据库备份实战:mysqldump 逻辑备份、binlog 增量与 xtrabackup 恢复演练


一、为什么要做数据库备份:先想清楚三个问题

任何"跑得稳"的 MySQL 服务,背后都有一整套备份恢复预案。备份不是把数据拷出来那么简单,动手之前先回答三个问题:

  • RPO(可容忍的数据丢失量):最多能丢多少数据?一小时、一分钟还是零丢失?这决定了全量备份的周期和是否要开 binlog 增量。
  • RTO(可容忍的恢复时间):故障后多久必须恢复?这决定了用逻辑备份(mysqldump,恢复慢、体积大)还是物理备份(xtrabackup,恢复快)。
  • 备份验证方式:备份文件不经过恢复演练,等于没有备份。定期在测试实例上还原,是检验备份是否可用的唯一标准。

备份的类型划分:

逻辑备份:mysqldump / mysqlpump —— 导出 SQL 语句,跨版本、跨架构通用,适合中小库
物理备份:Percona XtraBackup —— 直接拷贝数据文件,速度快、恢复快,适合大库
全量备份:备份当前完整数据
增量备份:基于 binlog 记录上次备份之后的所有变更,配合全量实现"恢复到任意时刻"

二、环境准备与备份基线检查

先确认数据库版本、数据目录占用和磁盘剩余空间,避免备份中途把磁盘写满。

mysql --version
mysql -uroot -p -e "SHOW VARIABLES LIKE 'datadir'; SHOW VARIABLES LIKE 'server_id'; SHOW VARIABLES LIKE 'log_bin';"
du -sh /var/lib/mysql
df -h /var/lib/mysql /backup

建议为备份单独规划挂载点(独立磁盘或对象存储挂载),不要把备份文件与数据目录放在同一块盘上,否则数据盘故障时备份同样全军覆没。

mkdir -p /backup/mysql/{full,binlog}
chown mysql:mysql /backup/mysql

三、mysqldump 逻辑备份实战

mysqldump 是最常用的逻辑备份工具。InnoDB 表使用 --single-transaction 可以在不锁表的前提下获得一致性快照(依赖 MVCC,备份期间 DDL 仍可能造成数据不一致,业务高峰期应避免执行 DDL)。

3.1 单库备份

mysqldump -uroot -p \
  --single-transaction \
  --routines --triggers --events \
  --set-gtid-purged=OFF \
  --databases myblog > /backup/mysql/full/myblog_$(date +%F).sql

3.2 全库备份并压缩

mysqldump -uroot -p \
  --single-transaction \
  --routines --triggers --events \
  --master-data=2 --flush-logs \
  --all-databases | gzip > /backup/mysql/full/all_$(date +%F_%H%M).sql.gz

# 查看备份文件中记录的主库 binlog 位点(做增量恢复的起点)
zcat /backup/mysql/full/all_2026-10-04.sql.gz | grep -m1 "CHANGE MASTER TO"

参数说明:

  • --master-data=2:在备份文件中注释记录 binlog 文件名和位点,是做增量恢复的关键锚点。
  • --flush-logs:备份前滚动一个新的 binlog,方便增量文件分段管理。
  • --set-gtid-purged=OFF:GTID 环境恢复到非 GTID 实例时避免报错。
  • --routines --triggers --events:默认不带存储过程、触发器、事件,必须显式加上,否则恢复后应用会缺函数。

3.3 只备份表结构

mysqldump -uroot -p --no-data --routines --triggers --events --all-databases > schema_only.sql

四、定时自动备份脚本与保留策略

手动备份靠不住,写一个带保留策略的脚本交给 cron。脚本要点:备份失败即发告警、只保留最近 N 份、删除前校验文件完整性。

#!/bin/bash
# /usr/local/bin/mysql_backup.sh
set -euo pipefail
DB_USER="root"
DB_PASS="你的密码"           # 生产环境建议用 my.cnf 中 [client] 段配置,避免密码进命令行
BACKUP_DIR="/backup/mysql/full"
KEEP_DAYS=7
STAMP=$(date +%F_%H%M)
LOG="/var/log/mysql_backup.log"

log() { echo "$(date '+%F %T') $*" >> "$LOG"; }

log "备份开始"
mysqldump -u"$DB_USER" -p"$DB_PASS" \
  --single-transaction --routines --triggers --events \
  --master-data=2 --all-databases | gzip > "$BACKUP_DIR/all_$STAMP.sql.gz" \
  && log "备份完成: all_$STAMP.sql.gz" \
  || { log "备份失败!"; exit 1; }

# 校验 gzip 完整性
gzip -t "$BACKUP_DIR/all_$STAMP.sql.gz" || { log "备份文件损坏"; exit 1; }

# 清理 7 天前的备份
find "$BACKUP_DIR" -name "all_*.sql.gz" -mtime +"$KEEP_DAYS" -delete
log "清理完成,当前备份: $(ls "$BACKUP_DIR" | wc -l) 份"
# crontab -e  每天凌晨 2 点执行
0 2 * * * /usr/local/bin/mysql_backup.sh

# 每 5 分钟滚动一次 binlog,并定期把增量传走(见第五节)

五、binlog 增量备份:把数据恢复到任意时刻

逻辑备份只能恢复到备份时刻,两次全量之间的数据要靠 binlog 补上。先确认已开启 binlog:

mysql -uroot -p -e "SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';"

若未开启,编辑 /etc/my.cnf 的 [mysqld] 段并重启:

[mysqld]
server-id = 1
log-bin = /var/log/mysql/mysql-bin
binlog_format = ROW
expire_logs_days = 7
max_binlog_size = 256M
gtid_mode = ON
enforce_gtid_consistency = ON

增量备份脚本(配合全量备份,全量后先 flush logs 让增量从新文件开始):

#!/bin/bash
# /usr/local/bin/mysql_binlog_backup.sh —— 建议每小时执行一次
mysqladmin -uroot -p"$DB_PASS" flush-logs
BINLOG_DIR="/var/log/mysql"
BACKUP_DIR="/backup/mysql/binlog"
cd "$BINLOG_DIR"
for f in mysql-bin.*; do
  [ "$f" = "mysql-bin.index" ] && continue
  [ -f "$BACKUP_DIR/$f" ] && continue
  cp "$f" "$BACKUP_DIR/" && echo "$(date +%F_%T) 已备份 $f" >> /var/log/mysql_backup.log
done

六、xtrabackup 物理备份:大库的正确打开方式

数据量上了 100G,mysqldump 导出再导入耗时以小时计,此时用 Percona XtraBackup 做物理备份更合适,它利用 InnoDB 崩溃恢复机制做到在线热备,备份与恢复都快一个量级。

6.1 安装(Ubuntu 22.04 示例)

wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb
dpkg -i percona-release_latest.generic_all.deb
percona-release setup ps80
apt update && apt install -y percona-xtrabackup-80

xtrabackup --version

6.2 全量备份与 prepare

xtrabackup --backup \
  --target-dir=/backup/mysql/xtra_full \
  --user=root --password=你的密码

# prepare:对备份应用重做日志使其一致,恢复前必须执行
xtrabackup --prepare --target-dir=/backup/mysql/xtra_full

6.3 增量备份(基于上次全量或增量)

xtrabackup --backup \
  --target-dir=/backup/mysql/xtra_inc1 \
  --incremental-basedir=/backup/mysql/xtra_full \
  --user=root --password=你的密码

# 合并增量:依次把增量应用到全量上
xtrabackup --prepare \
  --target-dir=/backup/mysql/xtra_full \
  --incremental-dir=/backup/mysql/xtra_inc1

6.4 恢复

# 停止 MySQL 并清空数据目录
systemctl stop mysql
mv /var/lib/mysql /var/lib/mysql.bak
mkdir -p /var/lib/mysql

# 把备份拷回数据目录并修复权限
xtrabackup --copy-back --target-dir=/backup/mysql/xtra_full
chown -R mysql:mysql /var/lib/mysql

systemctl start mysql
mysql -uroot -p -e "SELECT 1;"

七、恢复演练:误删数据后的完整救援流程

以一个真实场景为例:上午 10:00 误执行了 DELETE FROM orders WHERE status='pending',凌晨 2:00 有全量备份,binlog 增量备份到 9:00。目标:恢复到 9:59:59。

7.1 恢复全量备份

zcat /backup/mysql/full/all_2026-10-04.sql.gz | mysql -uroot -p

7.2 从全量位点应用 binlog 到误操作前一刻

# 第一步:找出全量备份记录的位点
grep -m1 "CHANGE MASTER TO" /backup/mysql/full/all_2026-10-04.sql
# 示例输出: CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000018', MASTER_LOG_POS=154;

# 第二步:应用 mysql-bin.000018 从位点 154 开始
mysqlbinlog --start-position=154 /backup/mysql/binlog/mysql-bin.000018 | mysql -uroot -p

# 第三步:应用后续 binlog 文件,并止于误操作之前
mysqlbinlog \
  --start-position=4 \
  --stop-datetime="2026-10-04 09:59:59" \
  /backup/mysql/binlog/mysql-bin.000019 /backup/mysql/binlog/mysql-bin.000020 \
  | mysql -uroot -p

# 更精确:先用 --stop-position 或 --stop-datetime 试出 DELETE 语句的准确位置
mysqlbinlog --start-position=154 /backup/mysql/binlog/mysql-bin.000019 | grep -n "DELETE FROM orders" | head -5

7.3 恢复后校验

mysql -uroot -p -e "SELECT COUNT(*) FROM orders WHERE status='pending';"
mysql -uroot -p -e "CHECK TABLE orders;"

注意:恢复演练一定要在独立测试实例上做,恢复完成后对比业务表行数与业务侧对账,确认无误再谈正式恢复。

八、常见报错与排错方法

报错 / 现象原因解决办法
mysqldump: Got error: 1045: Access denied账号缺少 LOCK TABLES、RELOAD 等权限GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, RELOAD, PROCESS ON *.* TO 'backup'@'localhost'; 建议用专用备份账号而非 root
mysqldump: Error 1412: Table definition changed备份期间执行了 DDL,快照一致性被破坏备份期间避免 DDL;或用 --single-transaction 且把 DDL 窗口错开
zcat: invalid compressed data备份文件未完整写入(磁盘满 / 中断)检查 df -h;脚本中加入 gzip -t 完整性校验;保留多天版本以便回退
ERROR 1781: GTID_NEXT cannot be set to UUID:... GTID 环境备份/恢复时 GTID 集不一致备份加 --set-gtid-purged=OFF;或恢复前 SET GLOBAL gtid_purged=... 同步 GTID 集
xtrabackup: failed to read redo log未执行 prepare 或 prepare 不完整恢复前必须 xtrabackup --prepare;增量备份先合并再 prepare
mysqlbinlog: unknown variable 'default-character-set=utf8mb4'my.cnf 中的字符集参数被 mysqlbinlog 误读改用 mysqlbinlog --no-defaults --set-charset=utf8mb4 ...
恢复后外键/自增列错乱只导出了表数据未按依赖顺序导入全库备份默认处理依赖;单表恢复用 --foreign-key-checks=0 并随后修复

九、把备份体系固化:Checklist

□ 已开启 binlog,ROW 格式,保留 7 天
□ 每天凌晨全量备份(mysqldump 或 xtrabackup)
□ 每小时增量备份 binlog
□ 备份文件异地存放(rclone 推对象存储 / 另一台机器)
□ 脚本带完整性校验和失败告警
□ 每月至少一次在测试实例上完整恢复演练
□ 备份文件加密(敏感数据用 gpg 或 openssl 加密后落盘)

数据库备份不是"做了就行",而是"能恢复才算数"。把备份、增量、异地、演练做成一条完整的链路,才能在故障真正来临时从容应对。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Nginx 反向代理进阶实战:WebSocket 转发、真实 IP 透传与灰度发布

Linux 硬盘健康检测实战:SMART 体检、坏道排查与数据提前预警

评 论
更换验证码