先说几个容易被忽视的陷阱——MySQL备份失败告警配置这件事,看似简单,实则暗藏许多坑。不少团队以为cron任务设置好了就万事大吉,结果备份失败两周都没人察觉,最后排查发现是脚本自己把错误日志给吞掉了。

只靠 cron MAILTO,备份失败你可能永远不知情
cron 的 MAILTO 机制表面上看很方便,但它只会转发 stdout 和 stderr。而 mysqldump 的关键错误默认走 stderr —— 问题恰恰就出在这里。很多脚本里习惯性地写 > backup.sql 2>/dev/null,这一操作下去,整个失败过程就彻底人间蒸发了。
更让人头疼的是,cron 环境的 PATH 极其精简,mail 命令经常找不到。所以正确的做法是在脚本里显式调用 /bin/mailx,并且发送前先校验一下它是否存在:which mailx >/dev/null || { echo "mailx not found"; exit 1; }
- 优先选用
mailx,它不依赖交互配置,在 cron 下稳定性更好 - 避免使用
mutt,它在非交互环境下容易卡住 - 发送命令示例:
/bin/mailx -s "MySQL Backup FAIL on $(hostname)" admin@example.com - 不要把 stderr 重定向到
/dev/null,至少保留到临时文件,方便后续排查
判断失败,只能靠 $?,别信文件大小
mysqldump 成功返回 0,失败返回非 0 码。常见的有:2=连接失败、3=权限不足、4=找不到表、11=临时 I/O 错误。用 if [ $? -ne 0 ] 是唯一可靠的判断方式。
有人喜欢用 ls -l 或 stat -c%s 判断文件大小是否大于0——这完全不可靠。因为 gzip 的头信息会让空文件也有 20 多个字节。还有更隐蔽的坑:--force 参数会让 mysqldump 忽略错误继续执行,最后生成一个空 SQL 文件,但返回码却是 0。
- 删掉所有
--force参数,一个不留 - 如果用了管道压缩(比如
mysqldump | gzip),退出码会变成gzip的。必须用${PIPESTATUS[0]}来捕获mysqldump的真实状态 - 想要确认 SQL 文件是否真的空数据?
head -c 1 backup.sql | wc -c,如果结果是 0,才说明首字节确实不可读
告警内容必须带上下文,否则等于没报
"备份失败"四个字,对运维人员而言毫无价值。凌晨三点被叫起来,需要立刻知道:哪台机器、哪个库、什么原因失败、磁盘还剩多少、最近一次成功备份是什么时候。信息不全,等于白报警。
钉钉或邮件正文里,以下信息必须包含:
$(hostname)和$(date)—— 服务器和时间是基础mysqldump --version—— 版本信息有助于排查兼容性df -h /backup—— 尤其关注/backup分区的磁盘使用率ls -lh /backup/*.sql | tail -3—— 最近几个文件的状态- 最近 5 行 stderr(先过滤敏感字段:
sed '/-p|password|@/d')
错误日志的截取范围要精准:从最后一次 mysqldump 开始,到下一个时间戳前为止,别把旧日志也带进去混淆视听。
密码和路径必须脱敏,告警不是安全漏洞
直接把 mysqldump -u root -p123456 或者完整的报错日志往钉钉群里一发,等于把数据库凭据贴在公告栏上。首当其冲的责任方就是告警脚本本身。
- 改用
--defaults-file=/root/.my.cnf,并确保文件权限设为chmod 600 - stderr 发送前必须过滤:
sed '/-p|password|@/d' - 钉钉 payload 里,禁止出现任何明文密码、IP 后缀(比如
@10.0.2.5)、绝对路径(比如/root/.my.cnf)
真正容易被忽略的往往不是怎么发告警,而是告警邮件或消息里有没有暴露不该暴露的信息。很多团队的告警流程,恰恰是第一个信息泄露面。
