乐于分享
好东西不私藏

Docker 安装 MySQL 实用教程:版本怎么选、数据怎么保住、坑怎么绕

Docker 安装 MySQL 实用教程:版本怎么选、数据怎么保住、坑怎么绕
版本选择、数据目录映射、完整 compose 配置、认证与升级的坑,一篇备齐。

用 Docker 装 MySQL,一条 docker run 十秒钟就能跑起来,也正因为太容易,最常见的事故随之而来:数据写在容器内部,哪天 docker rm 清理容器,库就跟着没了。所以这篇文章很想说的一件事——数据目录必须映射到容器外面,其余所有配置都围绕这条铁律展开。默认你已经装好 Docker 并配好了国内镜像加速(没配的看之前写的docker 安装篇),下面的命令三个平台通用。

SECTION 01
版本怎么选:先看这张表

MySQL 从 2023 年起改成了双轨发布:LTS 长期支持版求稳,Innovation 创新版尝鲜。2026 年的版本格局是这样的:

镜像 tag
状态(2026 年 7 月)
怎么用
mysql:8.4
LTS,支持到 2032 年
新项目默认选它
,从 8.0 升级的目标版本
mysql:9.7
新一代 LTS,2026 年发布
想要新特性可以上,生态驱动兼容性先验证
mysql:8.0
2026 年 4 月已 EOL(终版 8.0.46),不再有安全补丁
仅用于对齐还没升级的生产环境
mysql:5.7
2023 年 10 月已 EOL
仅用于遗留系统兼容测试,别用于新东西

一个容易忽略的点:别在生产里用 mysql:latest 或 mysql:lts 这种浮动 tag。为什么?这类 tag 指向的版本会随官方发布悄悄漂移,某天重新拉取镜像,等于在你不知情的情况下做了一次数据库大版本升级——而 MySQL 的数据目录升级是单向的,升上去就降不回来。写死到具体版本号,比如 mysql:8.4 甚至 mysql:8.4.6,升级这件事应该由你主动发起。

SECTION 02
一条命令,起一个能用的 MySQL

先看这个容器和宿主机之间到底有哪些通道要打通——端口、数据目录、配置目录、初始化脚本目录、环境变量,一共五个,如图 1:

MySQL 容器与宿主机:五个要打通的通道宿主机应用 / 客户端 → localhost:3306对外服务端口/data/mysql84/data/数据目录(本篇的铁律)/data/mysql84/conf/my.cnf自定义配置(字符集、缓冲池…)/data/mysql84/init/*.sql初始化脚本(建库建表灌数据)-e MYSQL_ROOT_PASSWORD=…环境变量(只在首次初始化生效)MySQL 容器(mysql:8.4)mysqld 监听 3306容器内端口/var/lib/mysqlibd 文件、redo log 全在这/etc/mysql/conf.d启动时自动读取此目录下 .cnf/docker-entrypoint-initdb.d首次初始化时按文件名顺序执行entrypoint 读取环境变量建 root 密码、默认库、普通账号-p 3306:3306-v 数据映射出来-v 配置挂进去-v 脚本挂进去-e 传进去容器是随时可以扔掉重建的壳,数据、配置、脚本都活在宿主机上
图 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

最高频的坑放在最前面:MYSQL_ROOT_PASSWORD 这类环境变量只在数据目录为空、容器首次初始化时生效。数据目录一旦有了内容,之后再改环境变量、删容器重建,密码都不会变——因为账号信息本身就存在数据目录里。改密码要进数据库用 ALTER USER,而不是改启动参数。
SECTION 03
数据放哪:容器删了,库还得在

数据的存放一共三种做法,只有第一种是错的,后两种各有适用场景:

数据存放的三种做法✗ 不映射(默认)docker run mysql:8.4数据写进匿名卷docker rm -v 或清理命令一执行,数据无声消失教程演示都嫌危险任何有价值的数据都别这样跑✓ 命名卷 named volume-v mysql_data:/var/lib/mysqlDocker 统一管理,性能最优delete 容器不影响卷docker volume ls 可见Windows / macOS 首选(绕开跨系统文件性能损耗)代价:文件位置不直观✓ 目录映射 bind mount-v /data/mysql:/var/lib/mysql数据就在你指定的目录里备份 = 直接打包这个目录迁移 = 拷目录到新机器Linux 服务器首选(位置明确,运维顺手)本文示例均采用这种判断标准只有一条:执行 docker rm 删掉容器再重建,数据还在不在?前者不在(✗),后两者都在(✓)
图 2 · 三种做法:匿名卷是事故源头,命名卷和目录映射按平台选

我们花一分钟亲手验证一次「容器删了数据还在」:

# 写一条数据进去
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 没建出来」这类问题就都通了。

SECTION 04
正经用法:docker compose 完整配置

长期跑的实例别用裸命令,参数散在 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 = utf8mb4
buffer pool 和容器内存限制要一起算:如果你用 deploy.resources 或 -m 给容器限了 1G 内存,buffer pool 又设 1G,MySQL 总占用(缓冲池 + 连接 + 各类缓存)必然超限,容器会被 OOM killer 反复干掉,症状是「数据库隔一阵就自己重启」。经验值:buffer pool 不超过容器内存限制的 70%。

init 目录里的 .sql / .sh 文件会在首次初始化时按文件名顺序执行,适合放建表语句和种子数据——命名成 01-schema.sql02-seed.sql 控制顺序。和环境变量一个道理,数据目录非空时它们不会再执行。docker compose up -d 起服务,healthcheck 变 healthy 后,依赖它的应用容器再用 depends_on: condition: service_healthy 接入,避免应用比数据库先起、连一堆失败。

SECTION 05
版本之间的坑:认证、大小写、升级路径

认证插件是跨版本第一坑。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 替你做出升级决定。

SECTION 06
备份与恢复:两手都要有

映射了数据目录不等于有了备份——目录和容器在同一块盘上,盘坏了一起坏。日常用逻辑备份,一行命令可以直接进 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 跨版本可用(也是逐级升级时最稳的搬运方式),冷备打包的数据目录恢复速度快但只能回到同版本。备份文件记得放到另一台机器或对象存储上,最后再补一句常识:没做过恢复演练的备份等于没有备份。

收尾放一张报错速查表,都是这套流程里真实会撞上的:

症状
原因与处理
改了 MYSQL_ROOT_PASSWORD 不生效
环境变量只在数据目录为空时生效,进库用 ALTER USER 改
Unable to load authentication plugin
驱动太老不认 caching_sha2_password,升驱动或按 SECTION 05 降级账号认证
different lower_case_table_names settings
初始化后改了大小写参数,改回原值;确要变更只能导出重建
port is already allocated
宿主机 3306 被占(常见是本机装过 MySQL),改映射 -p 3307:3306
容器隔一阵自动重启
多半是 OOM:buffer pool 超出容器内存限制,按 70% 原则调低
中文存进去变问号
字符集没配 utf8mb4,按 SECTION 04 的 my.cnf 配置后重建库表
拿 5.7 数据目录起 8.4 镜像失败
跨了大版本,先用 8.0 镜像过渡升级,或 mysqldump 导出导入
步骤依据与时效说明
· 环境变量、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 参考手册为准;升级只能逐级、不可降级。