必须用 mysqldump 备份,禁用 docker cp 直接拷贝 /var/lib/mysql;需在容器内执行、加 --single-transaction 保证一致性,密码用单引号包裹,导出文件必须落至宿主机,迁移优先选用逻辑导入而非卷拷贝。

备份必须用 mysqldump,别碰 docker cp /var/lib/mysql
直接拷贝 MySQL 数据目录(/var/lib/mysql)在容器里是高危操作:容器运行时文件系统处于活跃写入状态,docker cp 不保证一致性,导出的文件大概率无法直接恢复,甚至 mysql --initialize 都会报错。官方镜像默认启用 InnoDB,而 mysqldump 加 --single-transaction 能拿到一致性快照,不锁表、不中断服务。物理拷贝只适合停机维护窗口明确、且版本完全一致的冷迁移场景。
实操要点:
-
mysqldump必须在容器内执行,宿主机无需装 MySQL 客户端 - 密码要用单引号包裹,如
-p'root123',避免 shell 解析特殊字符(比如$、!) - 加
--routines --triggers --events才能导出存储过程、触发器和事件调度器 - 导出全库用
--all-databases;只导一个库,直接写库名,别加--databases(它会多包一层CREATE DATABASE,恢复时容易冲突)
备份文件必须落地到宿主机,不能留在容器里
容器不是存储设备。把 backup.sql 写进容器的 /tmp 或 /root,等于把备份存在“随时可能被 docker rm -f 清空”的地方。真正有效的备份,一定是通过重定向或 docker cp 拿到宿主机路径上。
推荐做法:
- 管道直压:
docker exec mysql-db mysqldump -uroot -p'xxx' myapp | gzip > /backups/myapp_$(date +%Y%m%d_%H%M%S).sql.gz - 不压缩就分两步:先
docker exec导出到容器内临时路径(如/tmp/backup.sql),再docker cp mysql-db:/tmp/backup.sql /backups/ - 宿主机目标路径(如
/backups/)建议提前chmod 755,确保容器内mysqldump进程有写权限(尤其用非 root 用户时)
迁移时优先选逻辑导入,别依赖卷拷贝
用 docker volume ls 查到命名卷名,再 docker run --rm -v mysql_data:/volume -v $(pwd):/backup busybox tar cf /backup/mysql_data.tar /volume 看起来很酷,但实际踩坑率极高:源目标 MySQL 版本差一个小版本(如 8.0.33 → 8.0.34)、字符集配置不一致、甚至 SELinux 上下文残留,都可能导致 mysqld 启动失败或数据损坏。而 mysqldump + mysql 导入是跨版本、跨平台、可验证的通用链路。
迁移关键步骤:
- 源端导出:
docker exec mysql-src mysqldump -uroot -p'xxx' --single-transaction --routines mydb > mydb.sql - 目标端导入前,确认新容器已启动且能连:
docker exec mysql-dst mysql -uroot -p'xxx' -e "SELECT 1" - 导入命令必须用
-i参数:docker exec -i mysql-dst mysql -uroot -p'xxx' mydb - 大文件导入超时?在目标容器的
my.cnf里加max_allowed_packet = 512M并重启容器
自动化脚本里最容易漏掉的三件事
写完 cron 脚本,测试一次成功就以为万事大吉?生产环境里,90% 的备份失效不是因为命令写错,而是这三处没处理:
- 脚本开头没加
set -e:某个docker exec失败(比如容器名输错),后续命令照常执行,结果生成空.sql.gz文件,你还以为备份成功了 - 没检查磁盘空间:
df /backups | awk 'NR==2 {print $5}' | sed 's/%//' | [[ $(cat) -gt 90 ]] && exit 1这类判断必须加,否则某天gzip因磁盘满静默失败 - 没清理旧备份:光生成新文件不删老的,三个月后
/backups/占满整个分区。用find /backups -name "*.sql.gz" -mtime +7 -delete保留最近一周即可
备份本身不难,难的是让每次执行都可验证、可追溯、可兜底。只要导出路径、压缩方式、保留策略、失败退出这四点写死在脚本里,剩下的就是定期 ls -lt /backups 看一眼时间戳——比查监控还直观。


















