用 Docker 装 MySQL,一条 docker run 十秒钟就能跑起来,也正因为太容易,最常见的事故随之而来:数据写在容器内部,哪天 docker rm 清理容器,库就跟着没了。所以这篇文章很想说的一件事——数据目录必须映射到容器外面,其余所有配置都围绕这条铁律展开。默认你已经装好 Docker 并配好了国内镜像加速(没配的看之前写的docker 安装篇),下面的命令三个平台通用。
MySQL 从 2023 年起改成了双轨发布:LTS 长期支持版求稳,Innovation 创新版尝鲜。2026 年的版本格局是这样的:
| 新项目默认选它 | ||
一个容易忽略的点:别在生产里用 mysql:latest 或 mysql:lts 这种浮动 tag。为什么?这类 tag 指向的版本会随官方发布悄悄漂移,某天重新拉取镜像,等于在你不知情的情况下做了一次数据库大版本升级——而 MySQL 的数据目录升级是单向的,升上去就降不回来。写死到具体版本号,比如 mysql:8.4 甚至 mysql:8.4.6,升级这件事应该由你主动发起。
先看这个容器和宿主机之间到底有哪些通道要打通——端口、数据目录、配置目录、初始化脚本目录、环境变量,一共五个,如图 1:
把这五个通道翻译成命令,就是一个可以直接用的启动配置:
docker run -d --name mysql84 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD='换成你的强密码' \
-e TZ=Asia/Shanghai \
-v /data/mysql84/data:/var/lib/mysql \
-v /data/mysql84/conf:/etc/mysql/conf.d \
--restart unless-stopped \
mysql:8.4
# 进容器验证(提示输入上面设置的密码)
docker exec -it mysql84 mysql -uroot -p -e "SELECT VERSION();"
+-----------+
| VERSION() |
| 8.4.x |
+-----------+逐个说这几个参数为什么这么写。-v /data/mysql84/data:/var/lib/mysql 是那条铁律,下一节专门讲。TZ=Asia/Shanghai 让容器时区和业务对齐,否则 NOW() 会差八小时。--restart unless-stopped 保证宿主机重启后数据库自己爬起来。Windows / macOS 用户把 /data/mysql84 换成自己的路径即可,比如 D:\mysql84\data 或 ~/mysql84/data。
ALTER USER,而不是改启动参数。数据的存放一共三种做法,只有第一种是错的,后两种各有适用场景:
我们花一分钟亲手验证一次「容器删了数据还在」:
# 写一条数据进去
docker exec -it mysql84 mysql -uroot -p -e "CREATE DATABASE demo; CREATE TABLE demo.t (id INT); INSERT INTO demo.t VALUES (42);"
# 把容器整个删掉,再用同样的数据目录重建
docker rm -f mysql84
docker run -d --name mysql84 -p 3306:3306 -v /data/mysql84/data:/var/lib/mysql --restart unless-stopped mysql:8.4
# 数据完好(注意:重建时连 -e 密码都不用传,密码本来就存在数据目录里)
docker exec -it mysql84 mysql -uroot -p -e "SELECT * FROM demo.t;"
| 42 |注意重建命令里没有 MYSQL_ROOT_PASSWORD——这正是上一节那个坑的另一面:数据目录非空时,环境变量整个被跳过,账号密码库表全部来自目录本身。理解了这一点,「为什么改了密码参数不生效」「为什么 MYSQL_DATABASE 没建出来」这类问题就都通了。
长期跑的实例别用裸命令,参数散在 shell 历史里迟早对不上账。写成 compose 文件,配置即文档。下面这份把配置文件、初始化脚本、健康检查都补齐了:
# docker-compose.yml
services:
mysql:
image: mysql:8.4
container_name: mysql84
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 换成你的强密码
MYSQL_DATABASE: appdb # 首次初始化时自动建库
MYSQL_USER: app # 业务账号,别让应用拿 root 连库
MYSQL_PASSWORD: 业务账号密码
TZ: Asia/Shanghai
volumes:
- /data/mysql84/data:/var/lib/mysql
- /data/mysql84/conf:/etc/mysql/conf.d
- /data/mysql84/init:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s # 初始化窗口期不算失败配置文件放在 /data/mysql84/conf/my.cnf,容器启动时自动加载。给一份务实的起步配置:
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci # 5.7 用 utf8mb4_general_ci
innodb_buffer_pool_size = 1G # 默认仅 128M;独占机器给物理内存的 50%~70%
max_connections = 500
slow_query_log = ON
long_query_time = 1
[client]
default-character-set = utf8mb4deploy.resources 或 -m 给容器限了 1G 内存,buffer pool 又设 1G,MySQL 总占用(缓冲池 + 连接 + 各类缓存)必然超限,容器会被 OOM killer 反复干掉,症状是「数据库隔一阵就自己重启」。经验值:buffer pool 不超过容器内存限制的 70%。init 目录里的 .sql / .sh 文件会在首次初始化时按文件名顺序执行,适合放建表语句和种子数据——命名成 01-schema.sql、02-seed.sql 控制顺序。和环境变量一个道理,数据目录非空时它们不会再执行。docker compose up -d 起服务,healthcheck 变 healthy 后,依赖它的应用容器再用 depends_on: condition: service_healthy 接入,避免应用比数据库先起、连一堆失败。
认证插件是跨版本第一坑。5.7 默认 mysql_native_password,8.0 起默认换成 caching_sha2_password,于是老驱动(老版 JDBC、PHP mysqli、Navicat 老版本)连 8.x 容器时报 Unable to load authentication plugin。首选方案永远是升级驱动;实在动不了老应用,可以给特定账号单独降级认证方式:
# 8.0:直接改账号认证插件即可
ALTER USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY '密码';
# 8.4:native 插件默认被禁用,得先在启动命令里打开开关
# compose 里给 service 加:command: --mysql-native-password=ON
# 9.x:mysql_native_password 已彻底移除,没有开关,只能升驱动表名大小写必须在第一次启动前想清楚。Linux 容器里 lower_case_table_names 默认 0(表名区分大小写),而很多从 Windows 环境迁过来的项目按 1(不区分)写的 SQL。8.0 起这个参数只能在初始化数据目录时设定,事后改配置直接拒绝启动,报 different lower_case_table_names settings。要设就在首次启动时设:command: --lower-case-table-names=1。已经初始化过的实例想改?导出数据、清空数据目录、带参数重建、再导入,没有捷径。
换镜像 tag 就是就地升级,要按路径走。用旧数据目录直接起新版本镜像,mysqld 启动时会自动升级数据字典——这是单向门,升完的数据目录降不回旧版本。规矩有三条:升级前先备份;只能逐级升(5.7 → 8.0 → 8.4 → 9.x),拿 5.7 的数据目录直接喂 8.4 是起不来的;升级后观察日志确认 Server upgrade ... completed 再放流量。这也再次解释了为什么别用 latest:你不会想让一次 docker compose pull 替你做出升级决定。
映射了数据目录不等于有了备份——目录和容器在同一块盘上,盘坏了一起坏。日常用逻辑备份,一行命令可以直接进 crontab:
# 备份全部库(--single-transaction:InnoDB 不锁表在线备份)
docker exec mysql84 mysqldump -uroot -p'密码' --single-transaction --routines --triggers --all-databases > backup_$(date +%F).sql
# 恢复(-i 不是 -it,从标准输入灌回去)
docker exec -i mysql84 mysql -uroot -p'密码' < backup_2026-07-17.sql
# 冷备:停容器后直接打包数据目录,恢复 = 解包 + 重建容器
docker stop mysql84 && tar czf mysql-data-$(date +%F).tgz -C /data/mysql84 data && docker start mysql84两种方式互补:mysqldump 产出的 SQL 跨版本可用(也是逐级升级时最稳的搬运方式),冷备打包的数据目录恢复速度快但只能回到同版本。备份文件记得放到另一台机器或对象存储上,最后再补一句常识:没做过恢复演练的备份等于没有备份。
收尾放一张报错速查表,都是这套流程里真实会撞上的:
· 环境变量、conf.d、docker-entrypoint-initdb.d 的行为依据 Docker Hub 官方 mysql 镜像文档;仅首次初始化生效的语义见镜像 entrypoint 脚本说明。
· 版本支持状态截至 2026 年 7 月:MySQL 8.0 已于 2026 年 4 月随 8.0.46 终版 EOL,官方建议升级至 8.4 LTS 或 9.7;5.7 已于 2023 年 10 月 EOL(来源:MySQL 官方 EOL 公告与 8.0 Release Notes)。
· 认证插件默认值变化(8.0 起 caching_sha2_password、8.4 默认禁用 native、9.x 移除)与 lower_case_table_names 的初始化限制,见 MySQL 参考手册对应章节。
· mysqldump 参数与就地升级流程以 MySQL 8.4 参考手册为准;升级只能逐级、不可降级。
夜雨聆风