"数据丢了怎么办"这个问题,答案只有一个:从备份恢复。但很多人的备份其实是假的——脚本写了,crontab 也配了,可从没验证过能不能恢复;或者备份脚本一直在静默失败,日志里全是报错,直到某天真的需要恢复时才发现最近一次成功的备份是三个月前。
这篇文章从备份策略讲起,给出一个可以直接用的 mysqldump 自动备份脚本(含压缩、校验、日志、错误处理、保留周期),然后讲 crontab 配置、异地备份、恢复演练的完整步骤,以及大库场景下的 xtrabackup 物理备份和基于 binlog 的时间点恢复。所有命令都区分 RHEL/CentOS/Rocky 与 Debian/Ubuntu 两套。文中的数字是经验值,最终要按你的数据量和业务容忍度调整。
一、先把备份策略定清楚
全量、增量与 binlog
全量备份:某一时刻的完整数据副本。mysqldump 导出的是逻辑备份(SQL 语句),xtrabackup 产出的是物理备份(数据文件副本)。
增量备份:只备份上次备份之后变化的部分。通常基于 binlog 实现。
binlog(二进制日志):记录所有数据变更操作。开启后可以基于全量备份 + binlog 重放恢复到任意时间点。
最实用的组合是:定期全量 + 持续保存 binlog。 比如每天一次全量,binlog 保留 7 天。这样理论上可以恢复到 7 天内的任意一秒。
3-2-1 备份原则
这是业界通用的备份准则:
- 3 份副本:原始数据 + 至少 2 份备份
- 2 种不同介质:比如本地磁盘 + 对象存储,或者本地 + 另一台机器
- 1 份异地:至少一份在物理上分离的地方(不同机房、不同城市、或云上对象存储)
为什么异地很重要:火灾、水淹、盗窃、机房整体故障、云厂商区域故障,这些都会让同机房的备份和数据一起没。另外,误操作(比如 rm -rf)通常会同时删掉数据和同机备份。
保留周期
一个常见的经验配置:
- 本地保留 7 天:用于快速回滚近期误操作
- 异地保留 30 天:应对更晚才发现的逻辑错误
- 月度归档保留 12 个月:合规需求或历史追溯
具体保留多久取决于你的业务:数据变更频繁、问题发现快的可以短一些;有合规要求的按合规来。关键是明确写出来并让脚本自动执行,而不是靠人记得清理。
RPO 与 RTO
这两个指标决定了你的备份方案该投入多少:
RPO(恢复点目标):能容忍丢失多长时间的数据。RPO = 1 小时意味着最坏情况丢一小时的数据。每天一次全量 + binlog 的 RPO 可以做到接近 0(只要 binlog 已落盘并同步走)。
RTO(恢复目标时间):需要多快恢复服务。RTO 决定了你用逻辑备份还是物理备份——mysqldump 恢复 100G 数据可能要几小时,xtrabackup 恢复同样大小只要几十分钟(主要是拷贝文件的时间)。
先把这两个数字定下来,再选方案。 很多争论(用不用 xtrabackup、多久备一次)本质上都是 RPO/RTO 的取舍。
二、mysqldump 的关键参数
最基础的用法
mysqldump -uroot -p mydb > /backup/mydb.sql
但这个命令在生产上有严重问题:它会在备份期间锁表(对 MyISAM 是锁全表,对 InnoDB 在不加参数时也可能因为一致性需要而加锁),导致业务写入阻塞。
正确的基础命令
mysqldump -uroot -p \
--single-transaction \
--routines \
--triggers \
--events \
--set-gtid-purged=OFF \
--databases mydb > /backup/mydb.sql
逐个解释:
| 参数 | 作用 | 说明 |
|---|---|---|
--single-transaction |
在单个事务内做一致性快照 | InnoDB 必备,不锁表。只对 InnoDB 有效 |
--routines |
导出存储过程和函数 | 默认不导出,容易漏 |
--triggers |
导出触发器 | 默认导出,显式写上更清楚 |
--events |
导出事件调度器 | 默认不导出 |
--set-gtid-purged |
控制是否写 GTID 信息 | 见下文 |
--master-data=2 |
记录备份时刻的 binlog 位点 | 用于时间点恢复,以注释形式写入 |
--databases |
导出多个库,带 CREATE DATABASE | 不带这个参数则不含建库语句 |
--all-databases |
导出所有库 | 包含系统库,恢复时要小心 |
--default-character-set=utf8mb4 |
指定字符集 | 避免乱码 |
--hex-blob |
二进制字段用十六进制导出 | 有 BLOB 时建议加 |
--no-data |
只导出结构 | 用于结构对比 |
--where |
按条件导出部分数据 | 大表分批导出用 |
--single-transaction 的原理与限制
它开启一个事务,利用 InnoDB 的 MVCC 拿到一致性快照,备份期间不阻塞读写。但有重要限制:
- 只对 InnoDB 表有效。 如果库里有 MyISAM 表,它们仍会被锁,且拿不到一致快照。先用第 061 篇的 SQL 检查有没有非 InnoDB 表
- 备份期间不能有 DDL。
ALTER TABLE、DROP TABLE、TRUNCATE会导致一致性快照失效,甚至让mysqldump报错。所以备份要避开 DDL 窗口 - 长事务会拖慢备份。备份开始时如果有长时间未提交的事务,
mysqldump可能等待
--set-gtid-purged 怎么选
这个参数在开启 GTID 的实例上很关键:
--set-gtid-purged=ON(默认):导出文件里带SET @@GLOBAL.GTID_PURGED,恢复时会设置 GTID 集合。用于搭建从库时使用--set-gtid-purged=OFF:不写 GTID 信息,恢复后 GTID 集合不受影响。普通备份恢复用这个--set-gtid-purged=AUTO:如果实例开了 GTID 就等同 ON
普通备份场景建议显式写 OFF。 如果用 ON 的文件恢复到已有数据的实例,可能因为 GTID 冲突导致后续复制出错。
未开启 GTID 的实例则不需要这个参数,可以用 --master-data=2 记录 binlog 位点:
mysqldump -uroot -p --single-transaction --master-data=2 --routines --triggers --events mydb > backup.sql
恢复后可以从文件头部看到位点:
head -30 backup.sql | grep -i 'CHANGE MASTER'
分库备份 vs 整库备份
推荐分库备份。 好处是:单库恢复不用动其他库、单个文件更小更好传输、某个库失败不影响其他库。
# 列出所有业务库(排除系统库)
mysql -uroot -p -N -e "SELECT schema_name FROM information_schema.schemata \
WHERE schema_name NOT IN ('mysql','sys','performance_schema','information_schema')"
风险提示:把密码直接写在命令行会被 ps 看到并进入 history。正确做法是配置 ~/.my.cnf:
[client]
user=backup_user
password=你的密码
host=localhost
chmod 600 ~/.my.cnf
或者用 MySQL 自带的凭据存储:
mysql_config_editor set --login-path=backup --host=localhost --user=backup_user --password
# 之后用 mysql --login-path=backup 连接
备份账号需要什么权限
不要直接用 root。创建一个专用备份账号:
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY '强密码';
GRANT SELECT, RELOAD, PROCESS, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, REPLICATION CLIENT
ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;
SELECT:读数据RELOAD:执行FLUSH TABLES、FLUSH LOGSPROCESS:查看当前会话(配合--single-transaction)LOCK TABLES:非 InnoDB 表需要REPLICATION CLIENT:读取 binlog 位点SHOW VIEW、EVENT、TRIGGER:导出视图、事件、触发器
三、完整的备份脚本
下面是一个可直接使用的脚本,包含压缩、校验、日志、错误处理和保留清理。
#!/bin/bash
# /opt/scripts/mysql_backup.sh
# MySQL 自动备份脚本:分库全量备份 + gzip 压缩 + 校验 + 日志 + 保留清理
set -uo pipefail
# ============ 配置区 ============
BACKUP_ROOT="/data/backup/mysql"
DATE=$(date +%Y%m%d)
TIME=$(date +%H%M%S)
DAY_DIR="${BACKUP_ROOT}/${DATE}"
LOG_FILE="${BACKUP_ROOT}/backup.log"
KEEP_DAYS=7 # 本地保留天数
MYSQL_CNF="/root/.my.cnf" # 凭据文件,权限必须 600
EXCLUDE_DB="mysql sys performance_schema information_schema"
# ===============================
mkdir -p "${DAY_DIR}"
log() {
echo "[$(date '+%F %T')] $*" | tee -a "${LOG_FILE}"
}
log "===== 备份开始 ====="
# 1. 检查凭据文件权限
if [ ! -f "${MYSQL_CNF}" ]; then
log "ERROR: 凭据文件 ${MYSQL_CNF} 不存在,退出"
exit 1
fi
PERM=$(stat -c %a "${MYSQL_CNF}")
if [ "${PERM}" != "600" ] && [ "${PERM}" != "400" ]; then
log "WARN: 凭据文件权限为 ${PERM},建议改为 600"
fi
# 2. 确认数据库可连接
if ! mysql --defaults-file="${MYSQL_CNF}" -e "SELECT 1" >/dev/null 2>&1; then
log "ERROR: 无法连接 MySQL,备份中止"
exit 1
fi
# 3. 逐个库备份
FAILED=0
DB_LIST=$(mysql --defaults-file="${MYSQL_CNF}" -N -e \
"SELECT schema_name FROM information_schema.schemata" 2>/dev/null)
for DB in ${DB_LIST}; do
# 跳过系统库
case " ${EXCLUDE_DB} " in
*" ${DB} "*) continue ;;
esac
OUTFILE="${DAY_DIR}/${DB}_${TIME}.sql.gz"
log "开始备份库: ${DB}"
if mysqldump --defaults-file="${MYSQL_CNF}" \
--single-transaction \
--routines --triggers --events \
--set-gtid-purged=OFF \
--default-character-set=utf8mb4 \
--hex-blob \
--databases "${DB}" 2>>"${LOG_FILE}" | gzip > "${OUTFILE}"; then
# 压缩完成后做完整性校验
if gzip -t "${OUTFILE}" 2>/dev/null; then
SIZE=$(du -h "${OUTFILE}" | cut -f1)
MD5=$(md5sum "${OUTFILE}" | awk '{print $1}')
echo "${MD5} ${OUTFILE}" >> "${DAY_DIR}/checksums.txt"
log "备份成功: ${DB} -> ${OUTFILE} (${SIZE})"
else
log "ERROR: ${OUTFILE} 压缩文件校验失败"
FAILED=$((FAILED+1))
fi
else
log "ERROR: 库 ${DB} 备份失败"
FAILED=$((FAILED+1))
fi
done
# 4. 清理过期备份(按目录名日期删除,务必确认路径正确)
if [ -n "${BACKUP_ROOT}" ] && [ -d "${BACKUP_ROOT}" ]; then
find "${BACKUP_ROOT}" -maxdepth 1 -type d -name '20*' -mtime +${KEEP_DAYS} \
-print -exec rm -rf {} \; >> "${LOG_FILE}" 2>&1
log "已清理 ${KEEP_DAYS} 天前的备份"
fi
if [ ${FAILED} -eq 0 ]; then
log "===== 备份完成,全部成功 ====="
else
log "===== 备份完成,有 ${FAILED} 个库失败 ====="
exit 1
fi
脚本要点说明:
用 set -uo pipefail 而不是 set -e:因为 mysqldump | gzip 的管道中,set -e 的行为容易误判,这里改成显式检查每个命令的返回值更可靠。
gzip -t 做完整性校验:能检测出压缩过程中断、磁盘写满等问题导致的残缺文件。这一步不能省,否则你可能存了一堆解压就报错的"备份"。
md5sum 写入 checksums 文件:恢复前可以先校验文件是否被篡改或损坏。
find ... -mtime +N:按修改时间删除 N 天前的目录。rm -rf 是高危操作,脚本里加了路径非空判断,但仍然要确认 BACKUP_ROOT 配置正确。建议第一次运行前把 -exec rm -rf {} \; 改成 -print 先看看会删什么。
退出码:有失败就返回非 0,这样配合监控可以告警。
部署脚本
sudo mkdir -p /opt/scripts /data/backup/mysql
sudo vi /opt/scripts/mysql_backup.sh # 粘贴上面的内容
sudo chmod 700 /opt/scripts/mysql_backup.sh # 含凭据路径,权限收紧
sudo chown root:root /opt/scripts/mysql_backup.sh
手动先跑一次验证:
sudo /opt/scripts/mysql_backup.sh
echo $? # 应为 0
ls -lh /data/backup/mysql/$(date +%Y%m%d)/
tail -30 /data/backup/mysql/backup.log
一定要手动跑通再放进 crontab。 直接放 crontab 的话,环境变量缺失导致的失败往往要几天后才发现。
四、crontab 定时配置
sudo crontab -e
每天凌晨 2 点执行:
0 2 * * * /opt/scripts/mysql_backup.sh >> /data/backup/mysql/cron.log 2>&1
每 6 小时一次(数据变更频繁时):
0 */6 * * * /opt/scripts/mysql_backup.sh >> /data/backup/mysql/cron.log 2>&1
crontab 的几个坑:
环境变量问题。 cron 的环境 PATH 很短,可能找不到 mysqldump、gzip。解决办法:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * /opt/scripts/mysql_backup.sh >> /data/backup/mysql/cron.log 2>&1
或者在脚本里写绝对路径(用 which mysqldump 查)。这是 crontab 最常见的失败原因——手动跑成功、放进 cron 就失败。
百分号要转义。 crontab 里 % 有特殊含义(表示换行),如果命令里有 date +%Y%m%d 要写成 date +\%Y\%m\%d。我们把它写在脚本里就避开了这个问题。
确认 cron 服务在跑:
# RHEL/CentOS/Rocky
sudo systemctl status crond
# Debian/Ubuntu
sudo systemctl status cron
看执行记录:
# RHEL/CentOS/Rocky
sudo grep CRON /var/log/cron
# Debian/Ubuntu
sudo grep CRON /var/log/syslog
备份时间要避开业务高峰和 DDL 窗口。 凌晨 2 点通常是业务低峰,但如果你的备份要跑几小时,要确认不会延续到早高峰。可以用 time 测一下实际耗时。
五、异地备份
用 rsync 同步到另一台机器
# 安装 rsync
# RHEL/CentOS/Rocky
sudo dnf install -y rsync
# Debian/Ubuntu
sudo apt-get install -y rsync
配置 SSH 免密(在数据库服务器生成密钥,把公钥放到备份机):
ssh-keygen -t ed25519 -f /root/.ssh/backup_rsa -N ''
ssh-copy-id -i /root/.ssh/backup_rsa.pub backupuser@备份机IP
安全建议:为备份专用账号创建单独的密钥,并在备份机的 authorized_keys 里用 command= 和 from= 限制它能执行的命令和来源 IP。
同步脚本:
rsync -avz --delete \
-e "ssh -i /root/.ssh/backup_rsa -o StrictHostKeyChecking=yes" \
/data/backup/mysql/ backupuser@备份机IP:/data/mysql-backup/
--delete 会删除目标端多余的文件,用之前先加 --dry-run 看看会发生什么:这个参数配错目录会删掉备份机上的数据。
同步到对象存储
主流云厂商都提供对象存储,配合命令行工具上传:
# 以 AWS S3 兼容接口为例,具体工具按你的云厂商选择
aws s3 sync /data/backup/mysql/ s3://your-bucket/mysql-backup/ \
--storage-class STANDARD_IA
上传要走内网端点(云厂商一般提供内网域名),否则备份大的话公网流量费用可观,速度也慢。
异地保留 30 天的实现
异地端的清理逻辑独立实现(不要在源端用 rsync 的 --delete 来管理异地保留周期)。在备份机上另跑一个清理任务:
find /data/mysql-backup/ -maxdepth 1 -type d -name '20*' -mtime +30 -exec rm -rf {} \;
同样要先去掉 -exec 用 -print 验证一遍。
加密备份(含敏感数据时)
如果备份里包含用户手机号、身份证等敏感信息,异地存储应该加密。用 GPG 对称加密:
# 加密
gpg --batch --yes --passphrase-file /root/.backup-pass \
--symmetric --cipher-algo AES256 -o backup.sql.gz.gpg backup.sql.gz
# 解密
gpg --batch --yes --passphrase-file /root/.backup-pass \
--decrypt -o backup.sql.gz backup.sql.gz.gpg
密钥管理是难点:密码文件权限必须 600,且不要把密码文件和备份放在同一个地方(否则加密就没意义了)。更规范的做法是非对称加密(用公钥加密,私钥离线保管),这样备份机上只有公钥,即使被攻破也解不开备份。
六、恢复演练:备份能不能用,只有恢复了才知道
逻辑备份的恢复步骤
1. 先找对文件并校验:
ls -lh /data/backup/mysql/20261010/
cd /data/backup/mysql/20261010/
md5sum -c checksums.txt # 校验完整性
gzip -t xxx.sql.gz # 校验压缩文件
2. 恢复到测试实例(强烈建议先这么做):
# 在测试机上创建空库,导入验证
gunzip < /data/backup/mysql/20261010/mydb_020000.sql.gz | mysql -uroot -p
# 或者先解压再导入
gunzip -c backup.sql.gz > /tmp/restore.sql
mysql -uroot -p < /tmp/restore.sql
3. 验证数据:
USE mydb;
SELECT COUNT(*) FROM 核心表;
SELECT MAX(id), MAX(created_at) FROM 核心表;
SHOW TABLES;
4. 正式恢复:先备份当前状态!
这是最重要的一步:恢复操作本身是有风险的,执行前必须先把当前数据库再备份一次(哪怕它有问题),避免恢复失败后连原来的数据都没了。
mysqldump -uroot -p --single-transaction --all-databases > /tmp/before_restore.sql
风险提示:导入备份会覆盖现有数据。如果备份文件里有 DROP TABLE 或 CREATE DATABASE 语句(用 --databases 导出时就包含 CREATE DATABASE 和 USE,不含 DROP),会直接影响目标库。恢复前务必确认目标实例是对的,最好先在测试实例演练一遍。
5. 大文件导入加速:
SET GLOBAL foreign_key_checks = 0;
SET GLOBAL unique_checks = 0;
SET GLOBAL sql_log_bin = 0; -- 不把恢复过程写进 binlog
导入完成后记得改回来。sql_log_bin = 0 在从库上要特别谨慎,它会导致从库数据与主库不一致。
单表恢复
如果只需要恢复一张表,可以从全量备份里抽取:
# 用 sed 抽取单表的结构和数据(表结构在 CREATE TABLE 到下一个 CREATE 之间)
gunzip -c backup.sql.gz | sed -n '/^-- Table structure for table `user`/,/^-- Table structure for table `order`/p' > /tmp/user.sql
更可靠的方式是用 mysqldump 备份时就做分表导出,或者用 mydumper(天然按表分文件)。
定期演练
建议每季度至少做一次完整恢复演练,并记录:
- 恢复耗时(这就是你的 RTO 实测值)
- 遇到的问题(缺文件、权限、磁盘空间、版本不兼容)
- 数据校验结果
演练可以在独立的测试环境做,不影响生产。不做演练的备份等于没有备份——这句话听起来夸张,但它是无数事故的总结。
七、大库用 xtrabackup 做物理备份
mysqldump 是逻辑备份,导出和导入都要做格式转换,大数据量时非常慢。经验上:
- 数据量小于 10G:
mysqldump完全够用,简单可靠 - 10G 到 100G:两者都可以,
mysqldump恢复可能要几十分钟到几小时 - 超过 100G:建议
xtrabackup(Percona XtraBackup),备份和恢复都快得多
安装
# RHEL/CentOS/Rocky
sudo dnf install -y percona-xtrabackup-80 # 版本号按 MySQL 版本选
# Debian/Ubuntu
sudo apt-get install -y percona-xtrabackup-80
版本必须匹配:MySQL 5.7 用 xtrabackup 2.4,MySQL 8.0 用 8.0 版本。版本不匹配会报各种奇怪的错误。
全量备份
xtrabackup --backup \
--user=backup_user --password=xxx \
--target-dir=/data/backup/xtrabackup/full_$(date +%Y%m%d)
增量备份
xtrabackup --backup \
--user=backup_user --password=xxx \
--target-dir=/data/backup/xtrabackup/inc_$(date +%Y%m%d%H) \
--incremental-basedir=/data/backup/xtrabackup/full_20261010
恢复
# 1. 准备(应用 redo log,让数据文件一致)
xtrabackup --prepare --target-dir=/data/backup/xtrabackup/full_20261010
# 增量要先准备全量再加 --apply-log-only,然后依次应用增量
xtrabackup --prepare --apply-log-only --target-dir=/data/backup/xtrabackup/full_20261010
xtrabackup --prepare --apply-log-only --target-dir=/data/backup/xtrabackup/full_20261010 \
--incremental-dir=/data/backup/xtrabackup/inc_2026101012
# 2. 停止 MySQL,替换数据目录
sudo systemctl stop mysqld
sudo mv /var/lib/mysql /var/lib/mysql_old # 保留旧数据以便回滚
sudo xtrabackup --copy-back --target-dir=/data/backup/xtrabackup/full_20261010
sudo chown -R mysql:mysql /var/lib/mysql
sudo systemctl start mysqld
风险提示:--copy-back 要求目标数据目录为空。上面的流程用 mv 保留了旧目录(这是回滚手段),不要直接 rm -rf。确认新库正常后再清理旧目录。
xtrabackup 的优缺点
优点:备份快(直接拷文件)、恢复快、支持增量、备份期间不阻塞写入(虽然仍有短暂的 FTWRL 锁)。
缺点:产出的文件大(等于数据量)、不能跨版本恢复、不能只恢复单表(8.0 部分版本支持单表导出)、操作比 mysqldump 复杂、与 MySQL 版本强绑定。
八、binlog 增量与时间点恢复
开启 binlog
[mysqld]
server-id = 1
log_bin = /data/mysql-bin/mysql-bin
binlog_format = ROW
expire_logs_days = 7 # MySQL 8.0 用 binlog_expire_logs_seconds
max_binlog_size = 1G
sync_binlog = 1
SHOW VARIABLES LIKE 'log_bin';
SHOW BINARY LOGS;
binlog_format = ROW 是推荐设置,它记录行的前后镜像,恢复和数据同步都比 STATEMENT 更可靠。
时间点恢复的场景
典型场景:有人误执行了 DELETE FROM orders WHERE ...,没有 WHERE 的 UPDATE,或者 DROP 了表。需要恢复到误操作之前的那一刻。
步骤:
1. 立即停止写入(保护现场)
这一步极其重要:新的写入会与恢复冲突,也会覆盖你想要找回的数据。 具体做法是:把应用切到维护模式、停掉应用服务、或者对表加锁。
-- 紧急情况下先锁表(只锁需要的表)
LOCK TABLES orders WRITE;
2. 找到误操作的时间点
如果有监控或应用日志,先确定大致时间。然后用 mysqlbinlog 查看:
mysqlbinlog --start-datetime='2026-10-10 09:00:00' \
--stop-datetime='2026-10-10 10:00:00' \
--base64-output=DECODE-ROWS -v \
/data/mysql-bin/mysql-bin.000123 | less
-v 参数把 ROW 格式的 binlog 解码成可读的伪 SQL,方便定位。
3. 用最近一次全量备份恢复到基础状态
gunzip < /data/backup/mysql/20261010/mydb_020000.sql.gz | mysql -uroot -p
4. 重放 binlog 到误操作前
mysqlbinlog --start-datetime='2026-10-10 02:00:00' \
--stop-datetime='2026-10-10 09:35:00' \
/data/mysql-bin/mysql-bin.000122 /data/mysql-bin/mysql-bin.000123 \
| mysql -uroot -p
--stop-datetime 就是误操作发生的时刻。多个 binlog 文件要按顺序、一次性传给 mysqlbinlog(不要分开执行,否则事务边界会出错)。
5. 导出误删的那部分数据,再导回生产库
这是关键的最佳实践:不要在恢复出来的实例上直接对外服务,而是把误删的数据单独导出,再导入生产库。 这样能避免"恢复到 09:35"导致 09:35 之后所有正常写入丢失。
mysqldump -uroot -p --where="id BETWEEN 1000 AND 2000" mydb orders > /tmp/orders_restore.sql
mysql -uroot -p mydb < /tmp/orders_restore.sql
单表的时间点恢复限制
基于全量备份的时间点恢复,最小粒度是整个备份集。 也就是说,你没法只恢复一张表到某个时间点——除非你的备份本身就是按表做的(用 mydumper 或者在备份脚本里分表导出)。
这也是为什么推荐分库、甚至分表备份:误删单表时,只恢复那一张表的代价远低于恢复整个库。
常见问题
备份文件应该放在哪里?
绝对不要和数据库放在同一块盘上。 理由有两个:盘坏了备份和数据一起没;备份写满磁盘会导致数据库无法写入甚至崩溃(这是真实的常见事故)。正确做法是单独挂载一块盘,或者放到另一台机器/对象存储。
为什么 crontab 里脚本不执行?
最常见的三个原因:环境变量 PATH 太短找不到命令(手动跑成功、cron 失败就是这个);脚本没有执行权限(chmod +x);脚本里的相对路径在 cron 的工作目录下不成立(用绝对路径)。检查 /var/log/cron 或 /var/log/syslog 看有没有执行记录,并把脚本的输出重定向到日志文件看具体报错。
备份会锁表吗?
用 --single-transaction 且全是 InnoDB 表时,不会阻塞读写。但如果库里有 MyISAM 表、或者备份期间有人执行 DDL,就可能出现锁等待甚至备份失败。建议把 DDL 操作安排在非备份窗口,并在备份脚本里记录失败原因。
备份文件权限要注意什么?
备份文件包含数据库的全部数据,权限必须收紧:文件 600、目录 700,属主 root 或专门的备份用户。不要放在 Web 目录下(否则可能被直接下载),也不要给其他用户读权限。另外传输时用 scp/rsync over SSH 或 HTTPS,不要用明文协议。
多久备份一次合适?
看你能接受丢多少数据(RPO)。每天一次全量 + binlog 是最常见的配置,RPO 可以做到分钟级甚至秒级。数据变更非常频繁且 RPO 要求高的,可以每 6 小时一次全量,或者用 xtrabackup 做"周日全量 + 每日增量"。
怎么确认备份真的可用?
唯一可靠的方法是恢复演练:定期(比如每季度)把备份文件恢复到一个测试实例,跑一遍数据校验(行数、最大 ID、关键业务查询)。只检查"文件大小非 0"是完全不够的——一个截断的文件也可能有几百 MB。
备份占用太多磁盘怎么办?
几个办法:用 gzip 或 zstd 压缩(SQL 文本通常能压到 1/5 到 1/10);缩短本地保留周期,把长期归档放对象存储(成本低得多);开启 binlog 后可以减少全量备份频率;定期归档冷数据以缩小备份体积。
主从架构下在哪里做备份?
在从库上做备份,完全不影响主库性能。步骤是:先在从库停复制(STOP SLAVE),记录当前的 binlog 位点,做备份,再启动复制(START SLAVE)。这样拿到的备份是一致性的,且能对应到明确的位点。
总结
- 备份方案由 RPO 和 RTO 倒推:每天全量 + binlog 保留 7 天是通用配置,能做到分钟级 RPO;RTO 要求高或数据量超过 100G 时用
xtrabackup物理备份,恢复速度快一个量级。遵循 3-2-1 原则(3 份副本、2 种介质、1 份异地)。 - mysqldump 必带
--single-transaction --routines --triggers --events,GTID 实例的普通备份要加--set-gtid-purged=OFF(搭建从库才用 ON)。它对 InnoDB 有效且不锁表,但备份期间不能有 DDL,且库里不能有 MyISAM 表。 - 脚本要包含校验和错误处理:
gzip -t验证压缩文件完整性、md5sum留档、失败时返回非 0 退出码便于告警。凭据不要写在命令行(会被ps和历史记录看到),用权限 600 的~/.my.cnf或mysql_config_editor。 - crontab 最常见的坑是环境变量:手动跑成功、定时任务失败,八成是 PATH 问题。在 crontab 里显式设置
PATH或在脚本里用绝对路径,并把输出重定向到日志文件。放进 cron 之前一定先手动跑通。 - 备份绝不能放在数据库同一块盘上:盘坏了备份一起没,备份写满磁盘还会导致数据库崩溃。异地用 rsync(注意
--delete先--dry-run验证)或对象存储(走内网端点省流量费)。敏感数据异地存储要加密,且密钥不要和备份放一起。 - 恢复前先备份当前状态,并优先在测试实例演练。时间点恢复的标准流程是:停止写入保护现场 → 用
mysqlbinlog -v定位误操作时刻 → 恢复全量备份 → 重放 binlog 到误操作前 → 只导出误删的那部分数据再导回生产,避免丢失误操作之后的正常写入。 - 不做演练的备份等于没有备份:每季度做一次完整恢复演练并实测 RTO。保留周期(本地 7 天 + 异地 30 天)要用脚本自动执行,清理用的
rm -rf先去掉-exec用-print验证一遍再正式启用。
企业QQ咨询




