最可控易排查的MySQL自动备份方式是mysqldump+自定义Shell脚本+crontab;归档(压缩+清理)必须在脚本内完成,不可依赖crontab行内命令替换,因cron默认/bin/sh不支持$()语法,且需用#!/bin/bash开头、.my.cnf存密(chmod 600)、校验退出码、流式压缩及精准find清理。

直接上结论:用 mysqldump + 自定义 Shell 脚本 + cron 是最可控、最易排查的方式;归档(压缩+清理)必须在脚本内完成,不能依赖 cron 行内写法——否则 date、find 等命令会因 shell 环境差异失败。
为什么不能把 mysqldump 直接写进 crontab 行里
看似简洁的写法:0 2 * * * /usr/bin/mysqldump -uuser -ppass db > /backups/db_$(date +\%F).sql,实际运行时会出错。cron 默认使用 /bin/sh,不支持 $() 命令替换(尤其在 Ubuntu 的 dash 默认 shell 下),$(date +\%F) 会被原样传给 mysqldump,导致文件名含非法字符或备份失败。
- 必须把所有逻辑(时间生成、路径拼接、压缩、清理)封装进独立的
.sh脚本 - 脚本第一行务必写
#!/bin/bash,避免 dash 解析错误 - crontab 中只调用脚本绝对路径,不带任何参数扩展
如何安全传入 MySQL 凭据(避开密码明文)
把密码写在脚本里或 crontab 里是高危操作,且容易被 ps aux 或进程日志泄露。推荐用 MySQL 配置文件方式,由 mysqldump 自动读取:
- 创建
/home/$USER/.my.cnf(注意权限必须是600):[client] host=localhost user=backup_user password=your_strong_password
-
mysqldump会自动识别该文件,调用时只需mysqldump your_db_name > ...,无需-u/-p - 确保
backup_user在 MySQL 中只有必要权限:GRANT SELECT, LOCK TABLES ON your_db.* TO 'backup_user'@'localhost';
归档逻辑必须自己控制:压缩 + 保留天数
归档不是“备份完再 zip”,而是要保证原子性:压缩成功才删源文件,且清理动作必须明确限定范围,否则可能误删。
- 用
gzip -c流式压缩,避免生成中间大文件:mysqldump db | gzip -c > "$BACKUP_DIR/$DB_NAME-$(date +%Y%m%d_%H%M%S).sql.gz" - 清理旧文件时,用
find ... -mtime +7 -delete比-exec rm更安全(避免空格路径问题) - 不要用
rm -f *.sql这类模糊匹配,必须绑定到具体前缀和扩展名,例如:find "$BACKUP_DIR" -name "myapp-*.sql.gz" -mtime +14 -delete
日志与失败通知不能靠 cron 重定向
>> log 2>&1 只能捕获 stdout/stderr,但无法判断 mysqldump 是否真的成功退出(比如磁盘满时 gzip 会失败,但 mysqldump 仍返回 0)。
- 脚本中必须检查每条关键命令的
$?:if [ $? -ne 0 ]; then echo "dump failed" >&2; exit 1; fi - 日志写入用
logger -t mysql-backup,这样能进journalctl -t mysql-backup,比纯文件更可靠 - 真要发通知,别用 mail(Ubuntu 默认没配 MTA),改用 curl 调企业微信/钉钉机器人 webhook,一行就能加
最容易被忽略的是:MySQL 用户的 max_allowed_packet 和备份目录的磁盘配额。大库备份中途断掉,不会报错,只会生成截断的 .sql.gz——得靠校验 gzip 文件头或解压测试来发现,不能只看文件是否存在。


















