MySQL 8.0系统库不能直接物理拷贝,因其表已完全迁入InnoDB数据字典,元数据与ibdata1、mysql.ibd、事务日志及系统表空间ID强绑定;单独拷贝仅保留结构文件而缺失底层一致性校验要素,导致启动时因LSN不匹配、Space ID错位或路径越界等硬性校验失败,唯一安全路径是mysqldump逻辑导出+初始化+重载。

备份命令必须用 mysqldump 而不能只靠 cp 数据目录
直接拷贝 /var/lib/mysql 下的文件在 MySQL 运行时极大概率损坏,因为 InnoDB 有缓存和事务日志。必须用 mysqldump(或 mysqlpump)走逻辑导出。注意:若数据库含大量大表,--single-transaction 是必需参数,否则会锁表;若含 MyISAM 表,则需加 --lock-all-tables,但会阻塞写入。
实操建议:
- 用
mysqldump --defaults-file=/etc/mysql/debian.cnf --single-transaction --routines --events --triggers --all-databases避免明文写密码(/etc/mysql/debian.cnf是 Debian/Ubuntu 默认信任配置,CentOS 可建~/.my.cnf) - 加上
--skip-comments减小 dump 文件体积(注释对恢复无用) - 导出前先检查磁盘空间:
df -h /backup,避免 dump 中途失败
gzip 压缩必须在管道中完成,不能分两步
如果先生成 SQL 文件再 gzip,临时文件可能占满磁盘;且多一次 I/O,慢且不安全。正确做法是让 mysqldump 输出直接进管道,由 gzip 实时压缩。
实操建议:
- 用
mysqldump ... | gzip > /backup/full_$(date +\%Y\%m\%d).sql.gz,注意date的%要转义(\%),否则 shell 会报错 - 不要用
gzip -9—— 压缩率提升有限,CPU 和时间成本显著增加;默认gzip(即-6)足够 - 若服务器 CPU 紧张,可加
ionice -c2 -n7降低 IO 优先级,减少对线上服务影响
清理旧备份要用 find 而不是 rm -rf * 或日期字符串比对
用 ls | head -n -7 | xargs rm 类操作在文件名含空格或特殊字符时会崩溃;用 date -d "7 days ago" 手动算日期再匹配,又容易因时区/命名格式不一致漏删。最稳的方式是按修改时间(-mtime +7)清理。
实操建议:
- 用
find /backup -name "full_*.sql.gz" -mtime +7 -delete,-mtime +7表示“修改时间超过 7×24 小时”,正好满足“保留最近 7 天”需求 - 首次运行前加个
-print测试:find /backup -name "full_*.sql.gz" -mtime +7 -print,确认列出的是真要删的文件 - 不要把备份目录设在
/tmp或/var/tmp—— 这些路径可能被系统自动清理,或挂载为 tmpfs(内存盘)
crontab 时间设置和权限问题最容易导致脚本静默失败
很多脚本本地能跑,放进 crontab 就没备份、也没报错。根本原因是 cron 环境变量极简(PATH 只有 /usr/bin:/bin),且不读取用户 shell 配置(如 ~/.bashrc),mysqldump 找不到、date 报错、甚至 gzip 不在 PATH 里。
实操建议:
- 所有命令写绝对路径:
/usr/bin/mysqldump、/bin/date、/bin/gzip、/usr/bin/find - 在 crontab 条目开头显式声明 PATH:
PATH=/usr/local/bin:/usr/bin:/bin - 重定向 stderr 到日志:
2>> /var/log/mysql-backup.log,否则失败时你什么也看不到 - 测试 cron 是否生效:先设成每分钟执行一次,等 2 分钟后检查日志和备份文件是否存在
真正麻烦的不是写脚本,而是备份文件权限不对(比如 root 写的,运维查不了)、磁盘突然写满、或者某天 MySQL 临时不可用却没告警——这些得靠后续加监控和校验,单靠定时脚本兜不住。


















