必须用flock加文件锁防止并发执行,时间戳和文件名需在锁内生成并加入PID或随机因子,备份后须校验SQL头尾、库数量及文件大小,失败写入独立告警日志。

脚本执行时多个实例同时跑导致备份错乱
自动化备份脚本被 cron 多次触发、或手动误执行,会导致多个 mysqldump 进程并发写入同一目录甚至同名文件,轻则覆盖备份、重则 gzip 压缩损坏、日志混杂无法定位失败原因。这不是“概率小”的问题——只要 crontab 配置不当(比如每 5 分钟一次但单次耗时 8 分钟),就必然发生。
- 用
flock加文件锁是最直接有效的方案:在脚本开头加flock -n /tmp/mysql_backup.lock -c "...",锁不可得时立即退出,不等不卡不重试 - 避免用进程名或 PID 文件判断:
pgrep -f mysql_backup.sh易漏判(如参数含空格)、易误杀(匹配到日志行)、且存在竞态窗口 - 锁文件路径必须全局唯一且可写:
/tmp/mysql_backup.lock比$BACKUP_DIR/backup.lock更可靠——后者若备份目录挂载失败或权限异常,锁机制就失效了 - 不要依赖
set -e自动终止:它对管道中的子命令退出码不敏感,mysqldump | gzip中 gzip 失败时脚本可能仍返回 0
crontab 配置错误引发的隐性并发
看似“每天一次”的 cron 实际可能每分钟都在尝试启动,只因语法写错或环境变量缺失。典型表现是 ps aux | grep mysqldump 突然看到 5 个进程,而 crontab -l 显示的是 0 2 * * * ——问题往往出在脚本未声明 SHELL 或 PATH。
- 在 crontab 条目里显式指定解释器和路径:
PATH=/usr/local/bin:/usr/bin:/bin SHELL=/bin/bash 0 2 * * * /path/to/mysql_backup.sh - 绝对不用
@daily:它底层展开行为依赖 cron 实现,某些嵌入式或容器环境不兼容;统一用0 2 * * *更可控 - 加执行前校验:脚本开头插入
if [ -f /tmp/mysql_backup.lock ] && kill -0 $(cat /tmp/mysql_backup.lock) 2>/dev/null; then exit 1; fi(仅作辅助,主锁仍用flock) - 禁止在脚本中使用
sleep等待重试:并发冲突不是靠“错开时间”解决的,而是靠“拒绝第二个进入”
备份目录写入冲突与时间戳失效
多个脚本实例即使没同时 dump,也可能把不同时间生成的备份文件都写进同一个 /data/mysql_backup/$(date +%Y%m%d) 目录,导致恢复时分不清哪个是最后成功备份。更糟的是,若两个实例恰好在同一秒启动,date +%Y%m%d-%H%M%S 会生成完全相同的文件名,后启动的直接覆盖前一个。
- 时间戳必须在加锁之后、正式备份之前获取:
timestamp=$(date +'%Y%m%d-%H%M%S')放在flock内部,而非脚本顶部 - 备份文件名必须包含随机因子或 PID:
full_${timestamp}_$$或full_${timestamp}_$(openssl rand -hex 3),彻底规避命名碰撞 - 禁止将备份直接写入共享目录根路径:先写入临时子目录(如
${BACKUP_DIR}/tmp_${timestamp}),成功后再mv到目标位置,确保原子性 - gzip 压缩阶段也要检查输出是否为空:
if ! gunzip -t "$backup_file" >/dev/null 2>&1; then rm "$backup_file"; exit 1; fi,防止残留损坏文件干扰后续判断
监控与故障自检不能只看 exit code
脚本返回 0 只代表“没崩溃”,不代表“备份完整”。曾有案例:mysqldump 因网络闪断只导出半张表,gzip 仍成功压缩,脚本静默返回 0,结果一周后才发现某库备份始终是 2KB。
- 必须校验 SQL 文件头和尾:
head -n1 "$backup_file" | grep -q "MySQL dump" && tail -n5 "$backup_file" | grep -q "Dump completed" - 统计导出的数据库数量:
zgrep -o "Current database: \`[^']\+\`" "$backup_file" | wc -l,与mysql -Nse "SHOW DATABASES;"结果比对 - 记录并比对文件大小下限:对核心库设阈值,如
uudb库历史平均 1.2G,若本次zcat "$backup_file" | grep "Database: uudb" -A 100 | wc -c小于 500MB 就告警 - 所有校验失败必须写入独立告警日志,并用
logger -t mysql-backup "CRITICAL: backup integrity check failed"推送到系统日志,便于集中采集
真正难处理的不是“怎么加锁”,而是当锁生效后,你得立刻知道——这次运行到底有没有拿到有效数据。很多团队花大力气防并发,却让损坏备份在磁盘上躺三天才被发现。


















