从第一次接触 Docker 就被它折腾得够呛,到现在把开发环境里的 MySQL 完全容器化,这中间踩过的坑、走过的弯路还挺多的。这篇东西不是官方文档的搬运,而是我实际把 MySQL 跑在 Docker 里的完整记录——从装 Docker 开始,到镜像选型、容器启动、自定配置文件、进去执行 SQL,再到排查各种启动失败的问题,一条线走到底。如果你正准备在自己电脑上装 MySQL 但不想被原生安装的依赖、目录、权限搞到头疼,或者想在服务器上用容器统一管理数据库,这篇内容可以直接照着操作。
1. 为什么我建议 MySQL 这种"重服务"也交给 Docker 跑
很多人一听到"容器化"就觉得是生产环境、微服务才需要的东西,本地装个数据库直接下载安装包不就完事了。我以前也是这么想的,直到帮同事修过几次 MySQL 安装问题后,彻底改掉了这个习惯。
1.1 原生安装的体验到底有多折腾
Windows 上装 MySQL,看起来是"下一步下一步",但后面等着你的往往是一串莫名其妙的问题:安装目录带空格导致服务起不来、my.ini 找不到、服务管理器里 MySQL 服务消失了、卸载不干净重装的时候残留注册表报错……我在 Windows 上遇过最离谱的一次是,某次安全软件更新后 MySQL 服务直接启动失败,日志里写的错误信息跟实际情况完全对不上,最后只能重装系统级的卸载清理。
Linux 服务器上稍微好一点,但也要处理 apt 源、依赖包、配置文件权限、开机自启脚本这些事。如果你需要在同一台机器上区分测试环境和开发环境,或者版本之间来回切换,原生安装基本是噩梦——装一个 5.7 再用 8.0 就得先卸载,数据目录、配置文件全搅在一起。
1.2 容器方案解决了哪些实际问题
把 MySQL 放进 Docker 之后,我最直观的感受是三个字:可复用。一条docker run命令,或者一个docker-compose.yml文件,就能在任何装好 Docker 的机器上把一模一样的数据库环境拉起来。Windows 上遇到的问题在 Mac 上不会重现,测试环境的版本和开发环境完全一致,不再出现"我本地好好的,到服务器上就报错"这种经典问题。
数据怎么办?这也是很多人担心的。MySQL 容器本身是个"一次性"的东西,删掉重建都行,但数据必须放在宿主机上——也就是挂载数据目录,这个后面会重点讲。设置的密码通过环境变量控制,自定义配置通过文件挂载注入,端口映射表做好规划,这套组合下来,MySQL 在 Docker 里比原生安装更清晰:想换版本,换镜像标签重跑一次;想备份,直接备份宿主机的数据目录;想删环境,停容器删目录,干干净净没有残留。
我自己现在开发机上跑着两个 MySQL 容器,一个 5.7.44,一个 8.0,互不干扰,随时可以同时启动。这在原生安装下要折腾一整天,容器方案十分钟搞定。
2. 环境准备与镜像选型:跑 MySQL 之前先想清楚这两件事
动手拉镜像之前,先把底下的运行时准备好。很多新手第一步就卡在"docker 命令输入后提示找不到",或者"安装完 Docker Desktop 后一直启动不了"。这些不是 MySQL 的问题,是 Docker 本身没装对。
2.1 Docker Desktop 安装时最容易忽略的选项
如果你用的是 Windows,我强烈建议直接装 Docker Desktop,不要手动折腾 WSL 里面跑 dockerd。Docker Desktop 安装包下载没什么难度,但有两个细节容易被跳过:
第一个是安装过程中会让你选择是否启用 WSL 2 集成。WSL 2 是 Windows 上跑 Linux 容器的推荐后端,性能比之前的 Hyper-V 方案好,内存占用也更合理。这个选项一定要勾上。装完 Docker Desktop 之后,打开 Settings -> Resources -> WSL Integration,确保你常用的发行版(比如 Ubuntu)的集成开关是打开的,同时勾选"Enable integration with my default WSL distro"。
第二个是安装完成后 Docker Desktop 不会自动给你配置国内镜像加速。这个不配也能用,但拉 mysql 这种几百 MB 的镜像时速度会很感人。我在 Docker Desktop 的 Settings -> Docker Engine 里直接编辑 JSON 配置,加入了自己的镜像加速地址,保存后重启引擎生效。
Mac 用户省心很多,直接装 Docker Desktop for Mac,没有 WSL 这层概念。Linux 用户则建议直接用包管理器安装docker.io或者docker-ce,然后把自己的用户加入docker组,不然每次都要sudo。
装完之后验证一下环境:
docker --version docker run hello-worlddocker run hello-world能正常输出提示信息,说明引擎、网络、镜像拉取都没问题。如果这一步卡住,先检查 Docker Desktop 图标是否常驻运行,再检查网络和镜像加速配置,不要急着继续往下走。
2.2 mysql 镜像版本怎么挑:8.0 还是 5.7
MySQL 官方在 Docker Hub 上维护的镜像,标签规则很清晰,常用的有:
| 标签 | 说明 | 适用场景 |
|---|---|---|
mysql:8.0 | 8.0 系列最新版(当前 8.0.x) | 新项目首选,官方主推版本 |
mysql:8.4 | 8.4 LTS 版本 | 需要长期维护的生产环境可以考虑 |
mysql:5.7 | 5.7 系列最新版(现在停在 5.7.44) | 老项目兼容,Oracle 已停止更新但仍有大量存量使用 |
mysql:latest | 指向当前最新大版本 | 不建议用在正式环境,因为版本漂移不可控 |
有基础但还在纠结选哪个的读者,记住这个原则就够用了:新环境无脑上 8.0,老代码依赖 5.7 才必须用 5.7。8.0 默认存储引擎是 InnoDB,默认字符集在 8.0 里也变成了 utf8mb4(更早的 5.7 默认是 latin1,中文乱码的根源之一就是没改字符集)。
还有个实际影响很大的差异:MySQL 8.0 默认用的认证插件是caching_sha2_password,而 5.7 用的是mysql_native_password。如果你打算用老版本的客户端工具(比如某些旧版 Navicat、PHP 5.x 的 mysqli)连接 8.0,会直接报Authentication plugin 'caching_sha2_password' cannot be loaded这种错误。这个后面讲外部连接的时候再给解决方案。
拉镜像的指令很简单:
docker pull mysql:8.0 docker pull mysql:5.7我建议把两个版本都拉下来备用,反正不占太多磁盘空间。测试下来mysql:8.0镜像大小在 200 MB 左右,mysql:5.7稍小一点。拉镜像时如果速度慢,就是 2.1 里说的镜像加速没配好。
3. 启动容器:一条 docker run 命令背后的参数拆解
这是全篇的重头戏。我会把完整命令摆出来,然后逐个参数拆开讲,确保你不仅知道要写什么,更知道为什么这么写。
3.1 一条跑通的基础命令
以 MySQL 8.0 为例,我开发机上最早用的启动命令是这样的:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123456 \ -e TZ=Asia/Shanghai \ -v /d/docker/mysql8/data:/var/lib/mysql \ -v /d/docker/mysql8/conf:/etc/mysql/conf.d \ --restart=always \ mysql:8.0我已经尽量去掉不必要的参数让它保持精简,但每个留下的参数都值得说明。前后顺序无所谓,Docker 会自行解析,关键是语义要清楚。
3.2 各参数的实际作用与原理
-d:detached 模式,容器在后台运行。如果不加这个参数,前台模式会持续输出 MySQL 的日志流并把当前终端占住,Ctrl+C 就会把容器也停掉。作为数据库服务,通常都在后台跑。
--name mysql8:给容器起一个固定的名字。有了名字后面执行docker exec -it mysql8 ...、docker stop mysql8就都不需要查容器 ID 了。容器删除后这个名字才能复用,如果你删了一个叫 mysql8 的容器再创建同名的,会提示名字冲突。
-p 3306:3306:端口映射,格式是宿主机端口:容器端口。MySQL 默认监听容器的 3306 端口,映射到宿主机之后,你的程序只需要连localhost:3306就能访问到容器里的 MySQL。映射到 3307 的例子也很常见——当你已经有另一个 MySQL 占用宿主机 3306 时可以这样:
docker run -d \ --name mysql8_test \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=root123456 \ mysql:8.0-e MYSQL_ROOT_PASSWORD=root123456:设置 root 用户的初始密码。MySQL 官方镜像要求必须设置这个环境变量(或者设置MYSQL_ALLOW_EMPTY_PASSWORD=yes允许空密码,生产环境千万别干这种事)。这里有个极其重要的坑:MYSQL_ROOT_PASSWORD 只在数据目录首次初始化的时候生效。也就是说,如果/var/lib/mysql挂载目录里已经有一份初始化过的数据了,你再改这个环境变量重启容器,密码不会变。
-e TZ=Asia/Shanghai:设置容器时区。不设的话容器默认 UTC 时间,MySQL 的NOW()函数返回的和北京时间相差 8 小时。这个坑很隐蔽,往往在保存时间字段的时候才暴露。
-v /d/docker/mysql8/data:/var/lib/mysql:绑定挂载数据目录。/d/docker/...是 Git Bash 风格的 Windows 路径,也就是D:\docker\...。冒号左边是宿主机目录,右边是容器内 MySQL 的数据目录。MySQL 镜像里默认数据目录就是/var/lib/mysql,初始化时会把全部数据库文件写到这个目录。挂载之后,容器删除、重建,数据仍然留在宿主机目录里。
-v /d/docker/mysql8/conf:/etc/mysql/conf.d:挂载自定义配置目录。镜像里的 MySQL 会扫描/etc/mysql/下的所有.cnf文件,其中/etc/mysql/conf.d/是专门给用户放自定义配置的目录。你找一个宿主机目录放my.cnf,容器启动时就会自动读取。
--restart=always:自动重启策略。Docker 服务重启、宿主机重启后,这个容器会跟着自动启动;容器异常退出时 Docker 也会尝试拉起它。对数据库这种长期运行的服务来说,这个参数能让你的环境少很多"早上起来发现没启动"的尴尬。
3.3 数据目录挂载的权限问题
Linux 环境下直接挂载数据目录,可能会遇到一个 8.0 镜像特有的问题:镜像内的 MySQL 进程默认以mysql用户运行(uid/gid 在 999 或者 27 附近,不同版本不同)。如果你宿主机上的挂载目录归属是 root 或者其他用户,容器启动时会报mysqld: Can't read dir of '/var/lib/mysql/'这类权限错误。
解决方式很简单,直接给挂载目录授权:
chown -R 999:999 /your/path/mysql8/data如果你的 Linux 发行版用过别的 uid,可以在镜像里确认:
docker run --rm mysql:8.0 id mysql出来结果按实际 uid/gid 授权即可。Windows 和 Mac 上因为 Docker Desktop 做了文件系统桥接,一般不涉及这个问题,但也不要随便把目录放到系统保护路径下。
4. 自定义配置:my.cnf 挂载与常见配置项
容器跑起来只是第一步,真正让 MySQL "按你的习惯工作"靠的是配置文件。原生安装你去翻my.ini或者/etc/my.cnf,容器方案里你只需要准备一个目录和一份文本文件,启动时挂进去。
4.1 配置文件放置方式与读取顺序
MySQL 官方镜像里读取配置的位置是从/etc/mysql/目录开始的。默认的my.cnf会 include/etc/mysql/conf.d/下的所有.cnf文件,这是官方的扩展点,也是我推荐你挂载的地方。
具体做法:在宿主机创建配置目录和文件
mkdir -p /d/docker/mysql8/conf然后新建一个my.cnf文件(放在/d/docker/mysql8/conf/下面),写入你要的配置项。启动命令里的-v /d/docker/mysql8/conf:/etc/mysql/conf.d会把整个目录映射进去,文件里写的所有配置会在启动时被读取。
如果你不想走目录挂载,也可以直接挂单个文件:
-v /d/docker/mysql8/my.cnf:/etc/mysql/conf.d/my.cnf但我不推荐这种写法,因为容器内那个文件路径如果不存在,Docker 会帮你创建一个空目录(Windows 下尤其容易出幺蛾子),导致 MySQL 找不到配置。目录整体挂载最稳。
4.2 常用配置项怎么选最不容易出问题
按我的习惯,一份开发环境的my.cnf通常会包含这几组内容:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [mysql] default-character-set=utf8mb4 [client] default-character-set=utf8mb4字符集这块必须强调:MySQL 8.0 虽然默认数据库字符集是 utf8mb4,但服务器层的character_set_server在 8.0.1 之前默认还是 latin1,如果你不想每次建库都手动指定字符集,最好在配置里写清楚。utf8mb4 和 utf8mb4_unicode_ci 的组合是目前最通用的,支持表情符号和大部分 Unicode 字符。
再把常用配置项按"是否需要"分组列出来:
| 配置项 | 作用 | 我是否在开发环境开 |
|---|---|---|
max_connections=200 | 最大连接数,默认 151,开发环境够用 | 一般不开 |
innodb_buffer_pool_size=1G | InnoDB 缓冲池大小,默认 128M | 根据机器内存开,我同事的 8G 内存本开了 1G 没问题 |
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_DATE | 控制 SQL 严格模式 | 开,避免脏数据写入 |
skip-name-resolve | 跳过主机名反解析 | 本地开发可以开,能加快连接速度 |
log-bin=/var/lib/mysql/mysql-bin | 开启 binlog | 需要做数据同步时才开,一般不开 |
我不是教你把这些一股脑写进配置。以innodb_buffer_pool_size为例,如果你机器只有 4G 内存还给它分配 2G,MySQL 很可能直接不启动或者 Docker 的 OOM killer 干掉容器。原则是:理解之后再改,不确定就保持默认。
4.3 挂载后容器起不来的排查路径
加了配置挂载后容器起不来,这是新手最容易摔的地方。我遇过的典型报错是这样:
[ERROR] [MY-000067] [Server] unknown variable 'max_connections=200'看起来像是配置语法写错了。实际上这类问题的排查路径非常固定,按顺序来:
先看容器日志:
docker logs mysql8日志里会精确告诉你是哪个配置项不识别、哪个变量值非法。拿到报错后,先在宿主机上用mysqld --verbose --help验证你写的那条配置是否在该版本 MySQL 中支持(把mysql8替换成你要测试的镜像标签):
docker run --rm mysql:8.0 mysqld --verbose --help | grep "max_connections"如果镜像内部置的 mysqld 完全不认识这个变量,那就是版本差异问题,删掉这条配置或者换成 8.0 支持的写法。
配置文件空格、注释格式也要注意。[mysqld]小节里的缩进空格本身没问题,但如果你用 Windows 记事本编辑的my.cnf,保存时带上了 UTF-8 BOM,或者行尾是 CRLF,某些解析严格的情况会报错。我保存配置文件统一用 VS Code 或者 Notepad++,编码选 UTF-8 无 BOM,换行符看平台——Windows 上跑 Docker Desktop 的 Linux 容器,建议统一改成 LF 換行符,省心。
5. 进入容器执行 SQL:三种姿势和适用场景
容器跑起来,数据库也初始化好了,接下来才是日常操作:查数据、跑 SQL、导数据库。很多人以为"进容器执行 SQL"就是docker exec -it一个命令,实际上不同场景有不同的最佳姿势,这里挨个讲。
5.1 交互式进入容器内部执行 SQL
先在宿主机执行:
docker exec -it mysql8 bash这会进入容器的 shell。然后在容器内执行:
mysql -uroot -p输入密码后就到了 MySQL 命令行界面。这是最直白的方式,适合临时排查问题、看参数、跑一些交互式语句。
如果不想跳到 shell 再敲一层,可以直接用mysql客户端进 MySQL(绕开 bash 这层):
docker exec -it mysql8 mysql -uroot -p这两个命令的区别在于:前者先进容器系统再登录 MySQL,后者直接在容器内启动 mysql 客户端进程。我喜欢用后者的一个原因是,提示符少了 Bash 那层干扰,操作路径更短。
5.2 不需要交互,直接在宿主机执行单条 SQL
跑一条测试语句、查一个变量、看一个表结构,完全没必要进容器,直接后排执行:
docker exec mysql8 mysql -uroot -p123456 -e "show databases;" docker exec mysql8 mysql -uroot -p123456 -e "select now();"注意这里的-e是 MySQL 客户端的参数,不是 Docker 的。Docker 把参数原样传给容器里的mysql命令。密码直接写在命令行上会出现在 shell history 里,所以我自己更习惯只写-p让它交互输入,或者用 MYSQL_PWD 环境变量,但那个也不安全,正常开发环境图省事写-p123456问题不大,生产环境千万别这么干。
批量执行多条 SQL 的时候,写一个test.sql文件,然后:
docker exec -i mysql8 sh -c 'mysql -uroot -p123456' < /path/to/test.sql这里不是-it而是-i(保持标准输入打开),因为我们要把宿主机的文件内容作为 stdin 喂给容器里的 mysql。<符号把宿主机文件内容重定向到 docker exec 的标准输入。不过这个方案有一点麻烦:SQL 文件必须存在于宿主机能被 Docker Desktop 访问到的路径上(Windows 下任意盘符都可以),如果文件在容器内部路径里,那就直接:
docker exec mysql8 mysql -uroot -p123456 -e "source /tmp/test.sql"或者进容器后source /tmp/test.sql。我实测更常用的是把 SQL 文件复制进容器再 source:
docker cp /d/sql/backup.sql mysql8:/tmp/ docker exec mysql8 bash -c "mysql -uroot -p123456 < /tmp/backup.sql"这个做法很适合临时导入大 SQL 文件,因为docker cp不经过网络和端口,路径清楚,调试起来也直观。
5.3 从外部用客户端连接容器 MySQL
这是开发时最常用的一种方式,因为你会用 Navicat、DataGrip、DBeaver 这类图形化工具看数据,或者从应用代码里jdbc:mysql://localhost:3306/dbname指向容器。关键在于端口映射和认证方式。
如果是 MySQL 8.0,老客户端连接失败的问题前面提过,解决办法有两个。一是在启动容器时就加参数让 root 用户用mysql_native_password(不推荐,未来版本会移除这个插件)。二是进入 MySQL 里对用户单独指定认证方式:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root123456'; FLUSH PRIVILEGES;root@'%'是一个容易踩坑的细节。默认情况下 MySQL 容器里的 root 用户只允许localhost连接,外部客户端无论如何都连不上。而官方镜像的另一个行为是:如果你通过MYSQL_ROOT_PASSWORD或MYSQL_ROOT_HOST环境变量设置了 root 密码,它会自动创建root@'%'用户。所以你在启动容器时最好显式加上-e MYSQL_ROOT_HOST=%,表示允许所有主机连接。
默认环境下如果发现用127.0.0.1能连但localhost连不上,或者反过来,大概率是碰到了root@'%'和root@'localhost'两条记录并存的情况。进容器执行:
SELECT user, host FROM mysql.user;看到的结果里对应用户的 host 列是否包含%,一目了然。没有的话就补一条:
CREATE USER 'root'@'%' IDENTIFIED BY 'root123456'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'; FLUSH PRIVILEGES;5.4 容器内路径、日志与维护入口
最后补一点维护性内容。MySQL 容器内的日志默认输出到标准输出,所以想看 MySQL 自身的运行日志,不要进容器翻文件,直接从宿主机执行:
docker logs mysql8镜像内 MySQL 的日志文件路径是/var/log/mysql/,但在容器里看这个目录通常没什么东西,因为日志默认都打到 stdout 了。如果你确实想把 MySQL 的 error log 落到宿主机文件里,启动时加参数:
-v /d/docker/mysql8/logs:/var/log/mysql这样容器被删掉之后,日志也不会丢。我在排查"容器起来了但 SQL 执行报错"这类问题时,几乎全靠docker logs定位,省去了进容器逐行查日志的麻烦。
6. 实测下来最常踩的坑与排查路径
写到这里必须把实战中高频出现的坑单独列一章。这些不是我编的,是帮人排错时反复遇到的典型场景,每一个都是血泪教训。
6.1 容器起一下就退出:日志怎么看、怎么定位
典型现象:docker start mysql8之后几秒钟,容器状态变成 Exited。这类问题九成都能靠日志定位,执行:
docker logs mysql8常见输出有这么几种,对应不同处理方式:
| 日志关键字 | 含义 | 处理方式 |
|---|---|---|
[ERROR] [MY-010244] [Server] Can't create/write to file '/var/lib/mysql/is_writable' | 数据目录权限或磁盘问题 | 检查挂载目录权限、磁盘剩余空间 |
[ERROR] [MY-011300] [Server] Plugin 'caching_sha2_password' is not loaded | 认证插件配置异常 | 重查启动参数,必要时删掉数据目录重建 |
[ERROR] [MY-010334] [Server] Could not find any file in /var/lib/mysql that matches the name ./ibdata1 | 数据目录挂载到了空目录但初始化没完成 | 查看目录权限、日志前面的报错 |
[Warning] [MY-010485] [Server] Insecure configuration for --secure-file-priv | 普通参数警告 | 影响不大,按需处理 |
第一次遇到"数据目录挂载到的是空目录"时容易懵,因为没有初始化过。这时直接删掉宿主机挂载目录下的所有文件,然后重新docker run(不带-v数据目录或者是空目录),让镜像自己初始化干净的数据文件,再把挂载加回去。
6.2 端口被占用:Error starting userland proxy
这个报错基本长这样:
docker: Error response from daemon: driver failed programming external connectivity on endpoint mysql8: Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use意思就是宿主机 3306 端口已经被占用。最常见的元凶是宿主机上本来就装了一个 MySQL 服务(Windows 上一般是服务里那个 MySQL),或者你上一个容器已经占了 3306。排查方式:
netstat -ano | grep 3306Windows 用netstat -ano | findstr 3306,Mac/Linux 用lsof -i :3306。确认被占后,两种选择:停掉另一个服务,或者给新容器换端口。开发环境换端口最省事,启动命令改成-p 3307:3306即可,程序连接时改一下端口。
还有一个细节:宿主机防火墙也要放开对应端口,否则你从局域网另一台机器访问容器 MySQL 会被拦掉。这个坑隐蔽,因为本机localhost:3306能通,换局域网 IP 就不通。
6.3 中文乱码与字符集问题
字符集问题历史悠久但依然常见。症状是插入中文后 SELECT 出来是???,或者建表时提示字符集不支持。
排查和修复路径:
SHOW VARIABLES LIKE 'character_set%';如果character_set_server不是 utf8mb4,先ALTER DATABASE 你的库名 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;把库级字符集改掉。但更重要的是从根源上解决,就是我前面 4.2 节提到的,在my.cnf里把character-set-server和collation-server都配成 utf8mb4,然后重启容器。建表语句里最好也显式指定:
CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(100) ) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;这样哪怕服务器全局配置出问题,这张表也是对的。字符集问题最烦人的是改得太晚:数据已经以 latin1 存进去了,乱码的根源不在连接层而在存储层,再改字符集不一定能修复既有数据。所以从拉镜像那天起就把字符集定好,别等出问题再补救。
6.4 容器重建后数据还在吗
这是用户问我最多的问题,也是最容易产生误解的地方。直接回答:
- 如果你用了
-v /宿主/data:/var/lib/mysql挂载,容器删了,数据目录还在宿主机上,重建时重新挂载同一目录即可。 - 如果你没挂载数据目录,
docker rm容器之后,整个容器内的数据会跟着消失。
但注意:docker rm和docker stop不同。docker stop只是停止,容器还在磁盘上,数据还在,docker start就能重新起来。docker rm才是删除容器。数据卷docker volume是另一种更高级的持久化方式,这里不展开,但你记住一条原则:生产环境或不能丢的数据,永远不要让数据只存在于容器可写层。
我自己做过一次"验证性删除":启动一个没挂载数据目录的 MySQL 容器,创建一张表插了几条数据,然后docker rm掉,再docker run一个全新的同名容器——里面啥都没有。这个实验当时给我留下的印象太深了,从此之后再也没有忘挂过数据目录。
7. 进阶:从普通 run 走向 compose 与版本升级
如果你只是想在开发机上快速获得一个 MySQL 环境,前面六章的内容已经足够。但既然都走通了,我还想分享两个进阶点,它们会帮你省掉未来更大的麻烦。
7.1 docker-compose 固化你的 MySQL 环境
每次用一长串docker run命令总会遇到记忆模糊的时候,更糟糕的是多个容器多个配置来回切换很混乱。我的解决方式是写一个docker-compose.yml把整个环境固定下来。我开发机上的文件长这样:
version: "3.8" services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: root123456 TZ: Asia/Shanghai ports: - "3306:3306" volumes: - /d/docker/mysql8/data:/var/lib/mysql - /d/docker/mysql8/conf:/etc/mysql/conf.d之后启动、停止、查看状态都变成:
docker-compose up -d docker-compose down docker-compose psdocker-compose down默认不会删除数据卷,所以这个方案的风险比直接docker run还要低——误删容器也不心疼,重新up就回来了。
7.2 版本升级与数据迁移的思路
容器方案下升级 MySQL 版本,思路是这样:
先停止旧容器,备份数据目录(整个复制一份到别处),再用新版本镜像进去启动并挂载同一个数据目录,启动后执行mysql_upgrade校验数据兼容性。这一步对 5.7 升 8.0 尤其重要,因为 8.0 对数据字典、系统表做了重构,老版本数据直接挂载上来可能报错。
我实际做 5.7 升 8.0 的时候,先把 5.7 容器停掉,数据目录复制了一份,然后用 8.0 镜像挂载原目录启动,日志里出现了几个警告,但 MySQL 会自动执行升级流程。跑完后执行:
SELECT VERSION();确认版本变成了 8.0。这里要给一个忠告:升级前一定先把数据目录复制成备份,我曾经图省事直接升,结果 8.0 启动时发现数据不兼容,只能回滚到备份目录重新来。容器化给了我们极大的容错空间,别把这条路自己堵死。
用 Docker 跑 MySQL 到现在,我已经完全回不去"原生安装 + 系统服务"的使用方式了。最开始是图省事,后来发现它带来的可移植性、环境隔离、快速重建这些能力,才是真正让人上瘾的地方。如果你照着这篇文章走通了第一个容器,后面遇到其他服务(Redis、MongoDB、PostgreSQL)你会发现套路一模一样——镜像、端口映射、数据卷挂载、配置文件注入,四板斧走天下。容器化是个复利型技能,MySQL 只是第一站。