用 mysqldump + mail 无法直接管道发二进制附件,必须通过临时文件中转并用 mutt 发送;python 脚本调用 subprocess 更可控,需显式路径、超时控制、异常清理和 cron 环境适配。
用 mysqldump + mail 一行命令完成备份发邮箱
能,但得拆成两步:先生成备份文件,再作为附件发送。直接管道进 mail 会丢二进制内容,附件变乱码——这是最常踩的坑。
典型做法是用临时文件中转,配合 uuencode 或更稳妥的 mutt:
mysqldump -u user -p'pass' db_name | gzip > /tmp/db_$(date +\%F).sql.gzecho "DB backup from $(date)" | mutt -s "MySQL backup $(date +\%F)" -a "/tmp/db_$(date +\%F).sql.gz" -- admin@example.com
mutt 默认支持二进制附件,mail 命令不保证;如果系统没装 mutt,别硬凑 uuencode,容易在某些邮件客户端里打不开。
Python 脚本里调用 subprocess 发送压缩备份
比 shell 更可控,尤其要加密码校验、失败重试或清理临时文件时。关键不是“怎么发”,而是“怎么不让脚本卡住或漏删临时文件”。
- 用
subprocess.run(..., timeout=300)防止mysqldump卡死 - 压缩和发信分两个
run调用,别用shell=True拼长命令——SQL 密码可能含特殊字符,被 shell 解析出错 - 用
try/finally确保os.remove()清理/tmp/db_*.sql.gz,哪怕发信失败
示例片段:
try:
subprocess.run(['mysqldump', '-u', 'user', '-p'+pwd, 'db_name'],
stdout=open(tmp_sql, 'wb'), check=True)
subprocess.run(['gzip', tmp_sql])
subprocess.run(['mutt', '-s', f'DB backup {today}', '-a', tmp_sql+'.gz', '--', 'admin@x.com'])
finally:
if os.path.exists(tmp_sql+'.gz'):
os.remove(tmp_sql+'.gz')
定时任务里跑备份发邮箱,crontab 权限和路径最容易翻车
脚本在终端能跑,放进 crontab 就失败,90% 是环境变量或工作目录不对。比如 mysqldump 找不到,或 mutt 不在 /usr/bin 而 cron 只认 /bin。
- 在 crontab 里显式写全路径:
/usr/bin/mysqldump、/usr/bin/mutt - 用
cd /path/to/script && ./backup_mail.py固定工作目录 - 加
PATH=/usr/local/bin:/usr/bin:/bin到 crontab 头部,避免命令找不到 - 把 stderr 重定向到日志:
2>> /var/log/backup_mail.log,不然失败了你根本不知道
附件超大被邮箱拒收?别硬塞,改用 split 或对象存储
多数企业邮箱单附件上限 25MB,而生产库导出常超百 MB。这时候硬压缩没用,gzip 对 SQL 文本压缩率有限,且拆分后还得手动合并,不实用。
- 优先考虑用
aws s3 cp或rclone推送到私有对象存储,邮件只发下载链接 - 真要分卷,用
split -b 20M db.sql.gz db_part_,再用mutt -a多次附加(注意总附件数别超限制) - 避免用
zip替代gzip:Python 的zipfile库默认不支持流式写入,大文件易内存爆掉
真正麻烦的不是发不出去,而是没人检查邮箱是否真的收到了、解压是否正常——这一步没法自动化,得留个手动验证环节。










