
1. 为什么我建议你用 Docker 装 MySQL先交代一下背景。我平时帮团队搭环境、写部署文档遇到 MySQL 安装配置的需求特别多。早期大家都是在服务器上直接 apt 或者 yum 装 MySQL后来慢慢发现一个问题每换一台机器就要重新折腾一遍而且同事的本机环境和服务器环境还不一样经常出现“在我这是好的到你那就跑不起来”的尴尬局面。后来我开始转向 Docker 装 MySQL这个问题基本就消失了。Docker 的好处在于它把 MySQL 整个运行环境打包成了一个标准化的镜像不管底层的 Linux 发行版是什么版本、系统里有没有缺依赖库只要机器上装了 Docker一条命令就能把 MySQL 跑起来。这种“一次构建到处运行”的体验做开发的人应该都有感触简直太爽了。这篇文章我打算这样讲先解释用 Docker 部署 MySQL 的核心思路然后从环境准备、镜像拉取、容器启动、数据持久化、客户端连接再到常见问题排查一条龙走完。无论你是第一次接触 Docker 的小白还是已经在用 Docker 但被 MySQL 的坑绊倒过的同学这篇文章应该都能让你少走弯路。先提醒一句如果你当前这台机器上已经装了原生的 MySQL 并且占用了 3306 端口那在用 Docker 启动 MySQL 之前最好先把原生的停掉或者把 Docker 容器的端口映射改成别的比如 3307。否则端口冲突能让你排查半天。2. 准备工作Docker 环境与镜像加速2.1 先确认本机 Docker 是否就绪Docker 装 MySQL 的前置条件只有一个你有一台能正常运行 Docker 的机器。我这里说的机器包含 Linux 服务器、Windows 开发机和 macOS这三类我都实际操作过。先检查本机是否已经装好了 Dockerdocker --version如果输出类似Docker version 26.x.x这样的信息说明 Docker 已经就位。如果提示command not found那就需要先安装 Docker。安装 Docker 本身有一个很省心的点Docker 官方提供了docker desktop这个图形化工具Windows 和 macOS 用户直接下载安装包一路下一步就行。桌面版的好处是把 Docker Engine、命令行工具、可视化管理界面打包在一起对新手特别友好。Linux 用户则通过包管理器安装例如 Ubuntu/Debian 使用sudo apt install docker.ioCentOS/RHEL 使用sudo yum install docker-ce具体以官方文档为准。2.2 镜像下载慢的解决办法很多新手在拉取 MySQL 镜像时都会碰到一个现象进度条半天不走甚至直接超时失败。这主要是因为 Docker 默认拉取镜像是从国外的 Docker Hub 官方仓库走国内访问速度很不稳定。解决办法就是配置国内镜像加速器。以常见的 Linux 系统为例编辑 Docker 的守护进程配置文件sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] } EOF然后重启 Docker 服务让配置生效sudo systemctl daemon-reload sudo systemctl restart docker这里说明一下daemon.json是 Docker 守护进程的配置文件registry-mirrors参数定义了镜像加速地址。配置好之后再拉取镜像时 Docker 会优先从加速器下载速度会有明显提升。Windows 用户不需要手改文件打开 Docker Desktop 的 Settings → Docker Engine把上面的 JSON 配置直接粘贴进去点击 Apply Restart 即可。注意不同的加速地址可用性会随时间变化如果你配置的某个地址失效了换一个即可。另外加速器只对 Docker Hub 官方镜像有效如果你拉取的是某个私有仓库的镜像依然走原来的地址。2.3 版本选型MySQL 8.0 还是 5.7拉取镜像之前还有一个选择要做选哪个版本的 MySQL。目前主流的稳定版本是 MySQL 8.0它已经是很多年、很成熟的了。相比 5.78.0 在性能、安全性和功能上都有明显提升比如默认字符集是utf8mb4、支持窗口函数、支持公用表表达式CTE等。对于新项目我强烈建议直接选 8.0。但有一个场景需要留意如果你要对接的是一个非常老旧的系统那个系统里用的数据库驱动版本过低连不上 MySQL 8.0 的默认认证方式那你就得用 MySQL 5.7 做兼容。这个后面我会专门讲。镜像的 tag 建议这样选mysql:8.08.x 主版本里的最新版本推荐mysql:5.75.7 主版本老项目兼容用mysql:latest不推荐因为你无法精确控制线上和本地版本是否一致容易埋雷3. 核心实操环节拉镜像、跑容器、做持久化3.1 拉取 MySQL 镜像确认 Docker 就绪后第一步就是拉取镜像。这里以最常用的mysql:8.0为例docker pull mysql:8.0如果你在拉取过程中发现速度还是慢先检查上一步的镜像加速是否配置成功可以用docker info查看Registry Mirrors一栏是否有内容。这一条命中镜像下载速度通常能翻好几倍。拉取完成后可以用docker images确认镜像已经存在本地docker images | grep mysql正常情况下你应该能看到类似mysql 8.0 某个镜像ID的记录。3.2 启动 MySQL 容器的标准命令接下来就是整个操作的核心了。启动一个 MySQL 容器我通常用这样的命令docker run -d \ --name my-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyRoot123456 \ -e MYSQL_DATABASEmyblog \ -e MYSQL_USERtestuser \ -e MYSQL_PASSWORDTest123456 \ -v /opt/mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0逐行解释一下这些参数是什么意思为什么要这么写-d后台运行容器终端不会一直被占用。--name my-mysql给容器起一个名字后面操作时不用记容器 ID直接叫名字就行。-p 3306:3306端口映射。前面是宿主机端口后面是容器内端口。默认情况下MySQL 在容器里监听 3306 端口映射到宿主机的 3306 后外部程序就能通过127.0.0.1:3306访问数据库了。如果宿主机 3306 被占用可以改成-p 3307:3306。-e MYSQL_ROOT_PASSWORD设置 MySQL root 用户的密码这是必须的参数。如果漏了容器会创建失败。-e MYSQL_DATABASE容器首次初始化时自动创建一个数据库这个参数可以省略但我习惯写上。要注意MYSQL_DATABASE和MYSQL_USER是配套使用的如果没有指定MYSQL_USER那这个MYSQL_DATABASE也会正常创建只是没有额外用户能访问它。-e MYSQL_USER和-e MYSQL_PASSWORD额外创建一个可远程访问的普通用户密码单独设置。这是一个好习惯日常开发不要总是拿 root 去连接数据库权限太大万一连接串泄露了后果不堪设想。-v /opt/mysql-data:/var/lib/mysql数据目录挂载。这里是我理解 Docker MySQL 相关的一个重点单独在下一节细说。-v /etc/localtime:/etc/localtime:ro让容器使用宿主机的时间设置避免时区不一致导致数据库里记录的时间差 8 小时。这行在 Linux 上很常用macOS 用户一般不需要。启动后可以用docker ps查看容器运行状态。看到my-mysql的状态是Up就说明容器已经在运行了。如果容器启动失败用docker ps -a能看到容器的状态是Exited。3.3 数据持久化为什么必须挂载数据卷我第一次用 Docker 装 MySQL 的时候也偷懒过直接docker run -d -p 3306:3306 --name my-mysql -e MYSQL_ROOT_PASSWORD123456 mysql:8.0一把梭当时觉得能连上能建表就完事了。直到有一天我清容器时执行了docker rm -f my-mysql然后再一次启动发现之前建的所有库表全部消失。这个教训告诉我Docker 容器是“临时”的容器没了容器内的数据也没了。虽然 MySQL 的数据在容器崩溃时并不会立刻丢但你一旦删掉容器/var/lib/mysql这个目录连同里面的数据文件就永远拜拜了。所以挂载数据卷是必须的。上面命令中-v /opt/mysql-data:/var/lib/mysql的意思是把宿主机的/opt/mysql-data目录映射到容器内的/var/lib/mysql目录。MySQL 实际写数据就是写到/var/lib/mysql现在这个目录对应的是宿主机上的磁盘文件所以即使容器被删掉数据依然留在宿主机上。下次重新启动容器时只要挂载同一个宿主机目录数据就自动恢复了。这一点对于生产环境的重要性不用多说就算只是在本地开发能省掉重来的麻烦也是极好的。Windows 和 macOS 用户同样可以挂载把宿主机路径改成你自己想存放数据的目录比如-v D:/docker-data/mysql:/var/lib/mysql注意mysql 镜像对挂载目录的权限有要求。在某些 Linux 发行版上如果挂载目录的属主不对MySQL 会无法启动并报权限错误。解决方法是先手动创建目录并授权sudo mkdir -p /opt/mysql-data sudo chown -R 999:999 /opt/mysql-data其中 999 是 mysql 容器内部 mysql 用户的 UID。3.4 利用 docker logs 确认初始化情况容器第一次启动时MySQL 要做数据目录初始化这个时间通常需要十几秒到几十秒取决于机器性能。如何判断初始化是否完成看日志是最直观的方式docker logs -f my-mysql当你看到日志中出现一行类似mysqld: ready for connections.的信息说明 MySQL 已经完成初始化对外部连接做好了准备。这时候不要急着连接先等日志稳定几秒。然后可以通过docker exec进入容器内部验证docker exec -it my-mysql mysql -uroot -p输入前面设置的MYSQL_ROOT_PASSWORD如果进入了 MySQL 命令行提示符说明整个安装配置已经成功。看到mysql提示符后退出只需输入exit;。3.5 使用 docker compose 管理 MySQL推荐命令行敲docker run虽然能用但参数一多记不住、写错参数的情况经常发生。我个人更推荐用docker compose来管理 MySQL 容器尤其是当你需要一次性启动 MySQL 加 Redis 等多个服务时这种方式的价值就完全体现出来了。创建一个名为docker-compose.yml的文件内容如下services: mysql: image: mysql:8.0 container_name: my-mysql restart: always environment: MYSQL_ROOT_PASSWORD: MyRoot123456 MYSQL_DATABASE: myblog MYSQL_USER: testuser MYSQL_PASSWORD: Test123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - /opt/mysql-data:/var/lib/mysqldocker compose最大的优点是把你所有的配置都写进了文件里版本化保存之后任何同事拉到项目、执行docker compose up -d就能得到一个完全相同的 MySQL 环境不会再出现“我这是好的”这类事件。启动命令docker compose up -d停止并删除容器数据不删因为在挂载卷里docker compose down这个文件里我加了restart: always它的作用是让 Docker 在容器意外退出时自动拉起重启这是进程级的高可用保障非常适合跑在服务器上的数据库服务。3.6 时区与字符集配置MySQL 8.0 默认字符集已经是utf8mb4这比 5.7 时代默认latin1的坑好太多了。但你还是要注意一点如果你需要存储 emoji 表情、生僻字这类四字节字符确认建库时使用utf8mb4CREATE DATABASE myblog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;如果你在连接 MySQL 时发现中文乱码、emoji 变成问号大概率是连接层字符集设置问题。一种快速解决办法是在 JDBC 连接串或者 Navicat 的连接属性里指定characterEncodingutf8。时区方面Docker 容器默认使用 UTC 时区这会带来一个很现实的问题你在 MySQL 里用NOW()函数获取当前时间得到的时间会比北京时间少 8 小时。如果你应用层直接存这个值后面展示就会出问题。解决办法有几种在docker run命令里加-e TZAsia/Shanghai在上面 docker compose 的 environment 里加TZ: Asia/Shanghai进入 MySQL 后执行SET GLOBAL time_zone 8:00;和SET SESSION time_zone 8:00;不过要注意第三种办法重启容器后会失效前两种是环境变量层面的设置容器每次启动都会自动应用更可靠。4. 客户端连接与日常运维4.1 用 Navicat / DBeaver 远程连接MySQL 容器正常运行后你就需要从外部工具连接它了比如 Navicat、DBeaver 这类图形化工具或者程序里面的 JDBC、Go-SQL-Driver 等连接库。连接信息一般这样填主机127.0.0.1如果 MySQL 跑在远程服务器上就填服务器 IP端口3306用户名root或之前创建的testuser密码启动容器时设置的那个密码有一个非常常见的坑在容器内部用 root 连接没问题但从宿主机连接时提示Access denied for user rootlocalhost。这通常是因为 root 用户默认只允许从localhost连接。解决办法有三种使用你通过MYSQL_USER创建的普通用户那个用户默认允许从任意主机连接进入容器修改 root 用户的 host执行 SQLALTER USER root% IDENTIFIED WITH mysql_native_password BY 新密码; FLUSH PRIVILEGES;创建新用户并授权CREATE USER navicat% IDENTIFIED BY 密码; GRANT ALL PRIVILEGES ON *.* TO navicat%; FLUSH PRIVILEGES;要注意的是MySQL 8.0 默认的认证插件是caching_sha2_password如果你的客户端工具版本比较老比如旧版 Navicat可能会遇到Authentication plugin caching_sha2_password cannot be loaded的报错。解决方法是把用户的认证方式改为mysql_native_passwordALTER USER testuser% IDENTIFIED WITH mysql_native_password BY Test123456; FLUSH PRIVILEGES;这里我特别想吐槽一句如果你和我一样经常在多个项目间切换建议在 Navicat 里给每个环境保存一个连接配置并且把密码存起来。工具就是拿来提高效率的别每次连都抠密码。4.2 进入容器执行 SQL除了用图形化工具外有时候你需要快速执行几条 SQL比如排查问题、查看表结构这时可以直接进入容器docker exec -it my-mysql bash进入容器后你会看到一个 Linux shell 环境里面已经安装了 MySQL 客户端然后执行mysql -uroot -p输入密码后进入 MySQL 命令行。如果你更习惯直接一条命令搞定不交互进入 shell也可以这样docker exec -it my-mysql mysql -uroot -p -e SHOW DATABASES;参数-e后面直接跟 SQL 语句执行完自动退出特别适合脚本化操作。4.3 备份与恢复用 Docker 跑 MySQL备份操作和原生 MySQL 备份没太大区别最终还是靠mysqldump。区别在于你要通过docker exec进入容器执行。备份单个数据库docker exec my-mysql sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD myblog /opt/backup/myblog.sql这条命令会把容器内 mysqldump 的输出重定向到宿主机文件里。$MYSQL_ROOT_PASSWORD这个环境变量在容器内可用因为启动时我们通过-e传了进去。恢复数据库时把备份文件反向导入docker exec -i my-mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD myblog /opt/backup/myblog.sql注意这里-i表示保持输入流打开重定向的文件内容才会被 MySQL 客户端读取。我建议把备份命令写成一个 cron 定时任务每天凌晨执行一次这是成本最低的数据安全手段。4.4 修改 MySQL 配置文件原生 MySQL 的配置文件是/etc/my.cnf或/etc/mysql/my.cnfDocker 容器内也一样。如果你需要修改某个配置项比如最大连接数、慢查询日志开关有以下几种方式方式一进入容器直接改然后重启容器生效。这种方式适合临时调试但容器重建后修改就丢了不推荐作为持久方案。docker exec -it my-mysql bash vi /etc/mysql/conf.d/my.cnf方式二把宿主机上的配置文件挂载进去。在docker run命令中加一个-v /opt/mysql-conf/my.cnf:/etc/mysql/conf.d/my.cnf这样你只需要在宿主机编辑文件然后重启容器即可docker restart my-mysql方式三如果只是修改字符集、时区一类用环境变量和启动参数就能搞定不需要动配置文件。绝大多数场景下用默认配置就够用了不需要过度调优。注意mysql:8.0镜像的默认配置已经比较均衡新手不要在没搞懂参数含义的情况下乱改max_connections或innodb_buffer_pool_size改得不当反而会影响性能或导致容器启动失败。改任何配置前先备份原文件。4.5 容器启动失败与数据恢复处理有一次我把宿主机/opt/mysql-data目录的权限改成了 root 所有然后重启容器容器就起不来了日志里报了权限相关错误。当时心里一紧以为数据库要重装了。排查后发现MySQL 容器内部的mysql用户 UID 是 999如果挂载目录的属主不是 999 或者没有相应权限MySQL 就无法读写数据文件。解决方法很简单sudo chown -R 999:999 /opt/mysql-data然后重启容器docker restart my-mysql数据文件完好无损虚惊一场。这也从侧面说明了挂载数据卷的重要性——如果不是挂载容器被删掉的时候数据就真的没了。5. 常见问题与排查技巧实录5.1 常见错误速查表我把踩过的坑和网上高频出现的问题整理成一张速查表方便你遇到问题时快速定位错误现象可能原因解决办法Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock容器没在运行或者你在容器外直接执行了mysql命令先docker ps确认容器状态用docker exec -it my-mysql mysql -uroot -p进入容器执行Access denied for user rootlocalhostroot 用户不允许从当前主机连接或密码错误使用启动时设置的普通用户或修改 root 的 host 为%Authentication plugin caching_sha2_password cannot be loaded客户端版本太老不认识 8.0 默认的认证插件执行ALTER USER testuser% IDENTIFIED WITH mysql_native_password BY 密码;后重试Port 3306 is already allocated宿主机 3306 端口被占用改用-p 3307:3306或者停掉占用端口的进程docker pull卡住不动国内网络访问 Docker Hub 慢配置镜像加速后重启 Docker容器一直重启Restarting状态通常是数据目录权限问题或配置文件错误docker logs my-mysql查看详细报错针对解决数据全丢了没有挂载数据卷容器被删导致数据丢失以后启动时加-v挂载丢失的数据可以用备份恢复MYSQL_ROOT_PASSWORD变量已设但登录失败容器是之前用旧密码创建的环境变量只在初始化时生效删除容器和旧数据卷重新创建或者进容器用ALTER USER改密码5.2 docker logs 才是排查第一手段在遇到容器起不来的情况下我建议你养成一个习惯第一个执行的命令永远是docker logs而不是上网搜报错。因为容器内发生了什么只有日志说最清楚。docker logs --tail 100 my-mysql--tail 100表示只看最近 100 行。如果日志信息还不够用-f参数跟踪日志输出边看边操作。有一个细节值得注意mysql 镜像的错误日志同时会输出到容器日志中方便集中管理和查看。如果你挂载了宿主机目录日志文件也会写在容器内的/var/log/mysql目录下但通常看docker logs就够了。5.3 忘记 root 密码怎么办如果你忘了 root 密码网上有人说删掉容器重建一个这对于挂载了数据卷的实例来说其实可行前提是你之前把数据目录持久化了。操作方式启动一个新的临时容器挂载同一个数据卷但入口改为/bin/bash不启动 MySQL在临时容器里手动修改 MySQL 的认证数据或者更简单的思路临时以--skip-grant-tables方式启动 MySQL进入后重置密码。但这里有个更有意思的细节对于 MySQL 8.0 镜像如果数据目录为空且你没有传MYSQL_ROOT_PASSWORD容器会尝试读取MYSQL_RANDOM_ROOT_PASSWORD、MYSQL_ONETIME_PASSWORD等变量。如果这些都没有容器初始化会失败并提示你指定一个。所以你在初始化时一定要确保环境变量正确。如果已经初始化过了单纯忘记密码最省心的还是用ALTER USER重置docker exec -it my-mysql mysql -uroot如果 root 没有密码就能登进去一般不会除非初始化时设置了空密码直接执行ALTER USER root% IDENTIFIED BY NewPassword123; FLUSH PRIVILEGES;5.4 容器重启后数据是否还在这个问题的答案完全取决于你启动容器时是否挂载了数据卷。如果你用了-v /opt/mysql-data:/var/lib/mysql那么即使容器被删掉docker rm只要宿主机目录还在重新用一个新容器挂载相同目录数据就能恢复。注意是“数据能恢复”不是“容器配置能恢复”容器本身的创建参数、环境变量如果你没有用 docker compose 管理就得重新敲一遍。如果你没有挂载数据卷容器重启docker restart数据也还在因为容器并没有被删除只是重新启动。但一旦执行docker rm删除容器数据就彻底没了。这个边界很多人容易搞混我在这里特意说明白。5.5 多版本 MySQL 共存有时候你手头有老项目要用 MySQL 5.7又有新项目要用 MySQL 8.0怎么办Docker 的方式完美解决启动两个容器分别映射到宿主机不同端口。docker run -d --name mysql57 -p 3307:3306 -e MYSQL_ROOT_PASSWORDOldPass123 mysql:5.7 docker run -d --name mysql80 -p 3308:3306 -e MYSQL_ROOT_PASSWORDNewPass123 mysql:8.0这样127.0.0.1:3307是 5.7127.0.0.1:3308是 8.0互不干扰。每个容器挂载各自独立的数据目录数据也完全隔离。如果宿主机内存比较小可以一次只启动其中一个另外一个保持停止状态毕竟每个 MySQL 实例会占用几百 MB 到上 GB 内存不等。这种多实例共存的能力放在非 Docker 环境里几乎不可能这么轻量维护也是我推荐容器化的一个重要理由。6. 最后的经验总结回到标题本身Docker 装 MySQL 这件事表面上就是“拉镜像、跑容器”六字真言但真正用起来数据持久化、字符集、时区、权限、镜像加速这些细节每一项都能劝退一部分人。我个人在实际操作中的体会是宁可第一次启动时多花两分钟把参数写完整也别等后面出了问题再补救。数据卷挂载不要省镜像 tag 尽量精确到主版本MYSQL_USER和MYSQL_DATABASE能配就顺手配上TZ环境变量加上端口冲突提前确认好。一套组合拳下来这个 MySQL 容器基本可以长期稳定跑下去。最后再分享一个小技巧把常用的docker run命令或者docker-compose.yml保存到你的项目仓库里不要只留在终端历史记录里。等到三个月后你需要换一台电脑搭同样的环境时你会庆幸当初存了这份配置文件的。