这个场景解决什么
一个能上线的每日备份任务,包含的远不止 mysqldump 那一行:防重入的锁、超时保护、留存策略、完成校验、日志留痕、失败告警位,缺一项都可能在某天凌晨出事——备份文件是空的、两份任务同时跑到把库压垮、或者旧备份把磁盘写满。
基本信息
| 场景 | 每天 02:30 备份 MySQL 8.0 全库,保留 14 天,失败要有痕迹 |
|---|---|
| 要点 | 锁防重入、超时中止、先写临时文件再原子替换、留存清理、日志留痕 |
| 验收 | 次日核对文件大小、压缩完整性与一份恢复演练结果 |
| 依赖 | flock、timeout、gzip、mysqldump 都在 PATH 中 |
典型使用场景
- 数据库例行全备:业务低峰执行,产出可直接恢复的 SQL 压缩包
- 上线前临时备份:改表结构或做大版本升级前留一份可回退的现场
- 多库分备:按库拆分备份并各自控制留存周期,缩短单次备份时长
- 异地归档:备份完成后由另一条任务同步到对象存储或异地机房
- 演练取材:定期用最近一次备份恢复到测试库,验证备份真的能用
备份脚本
备份脚本
#!/usr/bin/env bash
set -euo pipefail
LOG=/var/log/app/mysql-backup.log
DEST=/backup/mysql
log(){ echo "$(date -Is) $*" >> "$LOG"; }
[[ -n "$DEST" && "$DEST" != "/" ]] || { log 'fatal: bad DEST'; exit 1; }
mkdir -p "$DEST"
exec 9>/var/lock/mysql-backup.lock
flock -n 9 || { log 'skip: previous run still active'; exit 0; }
DAY=$(date +%F); TMP="$DEST/.dump-$DAY.sql.gz.part"
trap 'rm -f "$TMP"' EXIT
rm -f "$DEST/dump-$(date -d '14 days ago' +%F).sql.gz"
timeout 7200 mysqldump --single-transaction --all-databases | gzip -6 > "$TMP"
gzip -t "$TMP"
mv -f "$TMP" "$DEST/dump-$DAY.sql.gz"
log "done size=$(du -h "$DEST/dump-$DAY.sql.gz" | cut -f1)"crontab 与次日验收
crontab 与次日验收
30 2 * * * /opt/scripts/mysql-backup.sh >> /var/log/app/mysql-backup.cron.log 2>&1
# 次日验收
ls -lh /backup/mysql/ | tail -n 5
tail -n 5 /var/log/app/mysql-backup.log
gunzip -c /backup/mysql/dump-$(date +%F).sql.gz | head -n 20 # 看头部是否正常备份任务检查清单
| 检查项 | 怎么做 | 漏掉的后果 |
|---|---|---|
| 防重入 | flock 独占锁,抢不到直接退出 | 两份备份并发,库被压垮且文件互相覆盖 |
| 超时保护 | timeout 限制总时长 | 任务挂死,下一轮任务又被锁拦住 |
| 原子落盘 | 先写临时文件,校验后 mv | 出现半截文件被当成有效备份 |
| 完整性校验 | gzip -t 或比对文件大小 | 备份文件损坏却无人发现 |
| 留存策略 | 按日期精确删除过期文件 | 磁盘被历史备份写满 |
| 恢复演练 | 定期恢复到一个测试实例 | 真有故障时才发现备份不可用 |
只看进程退出码远远不够:要校验压缩包完整性(gzip -t)、定期做恢复演练。备份只有在成功恢复过一次之后,才算真正的备份。
--single-transaction 的一致性保证只对 InnoDB 有效;库里有 MyISAM 表时需要 --lock-tables 才能保证一致性,但会阻塞写入。备份前先确认引擎构成,再选参数。
如果这条命令是直接写在 crontab 里而不是脚本文件里,百分号要转义成反斜杠加百分号,否则 cron 会把它当成换行;写进脚本文件则不需要转义。