先交代个背景。我最早在服务器上装 MySQL走的是“下载官方仓库包 → apt/yum 安装 → 手动改配置 → 启动服务”这条路结果被依赖问题折腾得够呛CentOS 7 的默认源里只有 MySQL 5.6想用 8.0 就得换源Ubuntu 上编译安装又碰上 cmake 和 boost 版本不对一折腾就是一下午。后来切到 Docker 安装 MySQL一条 docker run 就能拉起一个数据库迁移服务器也变成了“导出 SQL、拷过去、再跑一条 docker run”这么简单。这个转变不是我一个人体会到的好处你只要跟着把这套流程走一遍大概率也会回不去。这篇文章专门写给想在 Docker 里装 MySQL 的人。不管你是 Linux 服务器上要起个正式环境还是 Windows/macOS 笔记本上要搞个本地开发库下面这些步骤、参数、踩坑点基本都能直接拿来用。全文不会只给你命令我会把每条命令为什么这么写、每个参数背后是什么原理、报错时怎么一步步排查都讲清楚。1. 为什么放弃传统安装、改用 Docker 跑 MySQL1.1 传统安装的磨人经历先说我自己踩过的坑这样你就明白为什么要在 Docker 上花时间。第一次在一台 CentOS 7 服务器上装 MySQL 8.0我老老实实去官方下载 rpm 包。装的时候提示依赖libaio、numactl-libs没装好我一条条补上。装完以后想用systemctl start mysqld启动又发现 selinux 拦截了数据目录的访问得设置上下文或者临时关掉 selinux。然后是首次登录要去找临时密码找完以后必须立刻改密码改密码还得满足 validate_password 组件的强度要求。等我把这些全搞定已经过去两个小时了。这还只是安装阶段。后面真正让我崩溃的是版本管理同一个服务器上有旧项目依赖 MySQL 5.7新项目想用 8.0用传统方式装两套数据库光端口冲突、配置文件隔离就够写一篇血泪史。更别提升级系统、换服务器这种操作每次都是一次“开盲盒”。1.2 Docker 到底改变了什么Docker 解决的核心问题是环境一致性。MySQL 的镜像把数据库程序、依赖库、配置文件模板、初始化脚本全部打进了一个可复用的包里你拉下来就能跑程序运行需要的环境都在这个隔离空间里不会污染宿主机宿主机上已有的环境也不会影响它。具体到日常使用好处有几点装快一条docker pull mysql:8.0 docker run就能得到一个可用的 MySQL 8.0耗时基本取决于网速。版本隔离MySQL 5.7 和 8.0 可以在同一台机器上用不同容器同时跑端口错开就行互不干扰。可复现同样的镜像、同样的环境变量、同样的挂载目录在任何机器上启动出来的数据库行为一致。迁移方便容器本身就是“程序 配置”的集合配合数据目录挂载异地部署就是复制粘贴加一条命令。1.3 什么时候不适合用 Docker 跑 MySQL虽然我用 Docker 装 MySQL 用得很顺手但也要泼一盆冷水Docker 不是万能的。如果你的 MySQL 承载的是高并发线上业务要拿满物理机的 CPU、内存、IO 性能还要做细粒度的内核参数调优比如调整innodb_buffer_pool_size、innodb_flush_log_at_trx_commit、打开文件数限制等直接用物理机或云数据库可能更合适。Docker 本身性能损耗其实不大但在容器里调优等于在虚拟机抽象之上再套一层排查问题链路更长。另外如果你们的运维体系完全没有容器化基础团队里也没有人懂 Docker 的网络和存储原理那强行上 Docker 可能比传统装法更容易出事故。工具的收益建立在理解它边界的基础上不理解边界就容易踩坑。2. Docker 本身没装好后面全是白搭Linux、Windows/macOS 的准备工作2.1 Linux 下最不容易翻车的安装方法在 Ubuntu 或 Debian 上我建议直接用 Docker 官方提供的安装脚本简单、快速基本不会因为手动配源而出错curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本执行完Docker Engine 会自动装好并设置开机自启。你只需要验证一下sudo systemctl start docker sudo systemctl enable docker sudo docker version如果是在 CentOS 或 RHEL 系上官方脚本同样适用只是底层包管理逻辑会自动切到 yum/dnf。验证完以后把当前用户加进 docker 组这样以后不用每个命令都加 sudo 前缀sudo usermod -aG docker $USER newgrp docker docker info这里必须强调加 docker 组相当于给你一个 root 级后门因为 docker 命令可以通过挂载宿主机目录来读取任意文件。个人开发机无所谓但生产服务器要谨慎评估别图省事。2.2 Docker Desktop 的虚拟化和 WSL2 那些坑Windows 和 macOS 用户一般会装 Docker Desktop。这一步卡住的人非常多尤其是 Windows。常见的两个报错我帮你把根因和排查路径都理出来。第一个是virtualization support not detected意思是没检测到虚拟化支持。Docker Desktop 在 Windows 上要跑 Linux 容器底层依赖 Hyper-V 或 WSL2这两个都需要 CPU 开启虚拟化功能。去任务管理器 → 性能 → CPU看右下角“虚拟化”是“已启用”还是“已禁用”。如果是禁用那就得进 BIOS/UEFI找到 Intel VT-x 或 AMD-V 选项并开启。有些轻薄本在 BIOS 里叫Intel Virtualization Technology有些叫SVM Mode找和 Virtualization 相关的词就行。开了以后重启再启动 Docker Desktop。第二个是failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen。这个报错通常不是配置问题而是Docker Engine 根本没起来。我遇到过的情况是 WSL2 内核版本太老Docker Desktop 调用后端时连不上。处理方式相对明确先去“控制面板 → 程序 → 启用或关闭 Windows 功能”确认“适用于 Linux 的 Windows 子系统”和“虚拟机平台”都勾上了。执行wsl --update把 WSL2 内核更新到最新版。重启电脑重开 Docker Desktop。如果还不行在 PowerShell 里执行wsl --shutdown后再启动 Docker Desktop有时是旧 WSL 实例挂死了。macOS 上相对省心但老款的 Intel Mac 如果开了太多后台软件Docker Desktop 也可能启动失败通常重启一下 Docker Desktop 就好不用深究。2.3 镜像加速源配置这一步不配好后面拉 MySQL 镜像可能等到怀疑人生。Docker 默认从 Docker Hub 拉镜像国内网络环境经常超时。解决办法是给 Docker 配置镜像加速源。以 Linux 为例编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }改完以后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker docker info看到Registry Mirrors列表里出现你的配置地址就说明生效了。Windows/macOS 的 Docker Desktop 里直接在 Settings → Docker Engine 里维护同一份 JSON 就行。镜像源不要只配一个多写几个其中一个挂了 Docker 会自动切换。3. 镜像版本选择不是拉个 latest 就完事3.1 MySQL 官方镜像有哪些 tag打开 Docker Hub 搜 mysql你会看到两个主要来源一个是 Oracle 官方的mysql一个是有一定用户量的mysql/mysql-server。日常用mysql就够了。官方mysql镜像常用的 tag 有这些tag对应版本使用建议latest当前最新大版本的滚动标签不推荐版本不可控8.0MySQL 8.0 系列的最新小版本新项目推荐8.0.36固定的 8.0.36 版本追求可复现时最稳5.7MySQL 5.7 系列最新小版本老项目兼容用5.7.44固定的 5.7.44老项目固定版本注意mysql:8.0这种 tag 本质是“滚动标签”每次你重新docker pull都可能拉到一个更新版本的小版本。比如你上周用的是 8.0.33这周重新 pull 可能就变成 8.0.36。小版本升级通常兼容性还行但如果你严格追求生产环境可复现建议直接写具体版本号比如mysql:8.0.36。3.2 拉取镜像的实操过程已经确认 Docker 就绪后拉镜像本身很简单docker pull mysql:8.0但我要多说一句不要急着在 docker run 的时候让 Docker 自动去拉镜像。手动 pull 的好处是你能明确看到镜像下载进度、确认网络没问题还能在拉完后检查一下镜像信息。如果放在 docker run 里自动拉一旦网络差你会看到容器一直处于Created状态但起不来排查的时候容易误判为配置错误。拉取完成后验证docker images你应该能看到类似这样的输出REPOSITORY TAG IMAGE ID CREATED SIZE mysql 8.0 abc123def456 2 weeks ago 565MB3.3 凭什么不推荐 latest我见过很多教程直接docker run mysql:latest这样不是不能用而是有个隐患隔一段时间你docker pull一次镜像内容可能已经变了。最典型的是 MySQL 5.7 时代升 8.0 时很多用户因为latest标签突然指向 8.0代码里连的还是老 MySQL 协议结果连接全部报错排查半天才发现是镜像版本变了。版本选择这条我给的结论很简单新项目直接选mysql:8.0或具体小版本老项目要兼容 5.7 语法就选mysql:5.7及对应小版本生产环境写固定小版本开发环境随意。4. 启动 MySQL 容器命令参数拆解到每条都明白4.1 一个最小可用的启动命令逐段拆解先给你一个日常开发最常用的启动命令docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyStr0ng!Pssw0rd \ mysql:8.0逐段解释docker run -d以后台方式启动容器终端不会卡在前台日志里。--name mysql8给容器起名字。后面docker logs mysql8、docker exec -it mysql8 bash都靠这个名字定位比容器 ID 好记。-p 3306:3306端口映射。左侧是宿主机端口右侧是容器内 MySQL 监听的端口。宿主机端口占用时就改成3307:3306这样外部连接localhost:3307实际连到了容器内的 3306。-e MYSQL_ROOT_PASSWORD...设置 root 用户的初始密码。这是官方镜像的一次性初始化配置只在数据目录首次初始化时起作用。mysql:8.0指定镜像名和 tag。启动完以后先看容器状态docker ps正常情况下你应该能看到STATUS是Up。如果看到Exited别慌看日志docker logs mysql8官方镜像初始化时要执行一系列操作初始化数据目录、创建 root 用户、设置密码日志里会有一堆[System] [MY-010116] ...之类的内容。只要最后出现ready for connections或者mysqld: ready for connections就说明数据库已经可连了。4.2 环境变量不止 MYSQL_ROOT_PASSWORD官方 mysql 镜像支持多个环境变量合理使用可以减少你后续的额外操作环境变量作用MYSQL_ROOT_PASSWORD设置 root 超级用户密码MYSQL_DATABASE首次启动时自动创建一个数据库MYSQL_USER和MYSQL_PASSWORD搭配创建一个普通用户MYSQL_PASSWORD上述普通用户的密码MYSQL_ROOT_HOST允许 root 从哪个主机连接默认localhost举个例子你希望容器启动后直接有一个业务库和一个专属账号避免拿 root 去写业务代码docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRootStr0ng!Pss \ -e MYSQL_DATABASEapp_db \ -e MYSQL_USERapp_user \ -e MYSQL_PASSWORDUserPss123 \ mysql:8.0这样容器首次初始化时会自动创建app_db数据库和app_user用户并且给这个用户授予app_db的全部权限。注意这里还有一个隐藏细节环境变量只在数据目录为空时生效。也就是说如果你之前挂载了一个已经有数据的目录改环境变量不会改变已有 root 密码这其实是个安全特性别误以为环境变量是天上的常驻配置。4.3 启动失败的第一现场docker logs容器启动失败的排查顺序我总结成一句话先 docker logs别急着改参数。遇到Exited状态先执行docker logs mysql8常见的失败原因端口被占用日志里会看到bind: Address already in use。用netstat -tlnp | grep 3306或ss -tlnp | grep 3306查谁占用了端口改宿主机映射端口。数据目录权限错误日志会出现[ERROR] [MY-010457] ... InnoDB: Operating system error number 13 in a file operation这通常和挂载目录权限有关下面讲持久化时细说。密码强度校验失败MySQL 8.0 默认启用了 validate_password 组件如果你把 root 密码设成123456这种弱密码初始化脚本可能报错退出日志里会出现validate_password相关字眼。解决办法是先把密码设复杂大小写数字特殊符号8 位以上至少能通过启动这一步。等稳定运行以后要弱密码再通过配置文件或 SQL 调整策略。4.4 给容器设个自愈能力restart 策略服务器重启后Docker 里默认不会自动拉起 MySQL 容器你每次都要手动docker start mysql8。省事的做法是启动命令加一个参数docker run -d \ --name mysql8 \ --restartalways \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRootStr0ng!Pss \ mysql:8.0--restartalways的含义是只要 Docker 守护进程启动就会把这个容器拉起来容器崩溃退出时也会自动重启。实际生产环境里我建议用--restartunless-stopped它和always的区别是如果你手动docker stop了容器重启 Docker 时不会自动把它拉起来。这更符合直觉因为你手动停掉的容器往往是出于维护目的。你还可以限制容器的资源占用避免 MySQL 把服务器内存吃满--memory1g --cpus2限制不是微调性能而是保护宿主机的其他服务。5. 数据持久化删容器可以删数据绝对不行5.1 容器可写层数据消失的根源在最开始的docker run命令里我没有加任何数据挂载参数。这种情况下MySQL 的数据/var/lib/mysql会写在容器的可写层里。容器可写层有一个致命特点它依赖容器本身而存在。如果你执行了docker rm mysql8容器一旦删除可写层里的数据会一起删掉没有任何回收站。就算你不删除容器执行docker rm之前的docker stop并不会让数据消失但一旦你为了升级镜像需要删掉旧容器重建新容器数据就没了。听起来很可怕但这是 Docker 容器的基本设计容器是一个可丢弃的进程运行时环境数据必须放到外部存储里。所以数据持久化不是可选项而是必选项。哪怕你只是搭一个临时测试库我也建议你顺手挂载一个数据目录因为你永远不知道这个“临时库”会不会跑半年。5.2 bind mount 挂载数据目录的操作与权限处理最直观的持久化方式是bind mount把宿主机的目录直接映射到容器内docker run -d \ --name mysql8 \ --restartunless-stopped \ -p 3306:3306 \ -v /data/mysql8/datadir:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDRootStr0ng!Pss \ -e MYSQL_DATABASEapp_db \ mysql:8.0这里-v /data/mysql8/datadir:/var/lib/mysql的含义是宿主机的/data/mysql8/datadir目录挂载到容器的/var/lib/mysqlMySQL 的所有数据文件ibdata1、ib_logfile、各个库的目录都会直接写在这个宿主机目录里。权限问题是bind mount最容易踩的坑。容器内的 MySQL 进程以mysql用户运行这个用户的 UID 是 999。如果宿主机挂载的目录权限属于 rootUID 0MySQL 进程没有写权限启动会直接报错。解决办法是修改宿主机的目录属主sudo mkdir -p /data/mysql8/datadir sudo chown -R 999:999 /data/mysql8/datadir然后再启动容器。macOS 上因为文件系统权限模型不太一样有时候chown 999:999不生效你可以在挂载前先给目录设置一个可写权限或者用 Docker Desktop 的 File Sharing 配置把目录加进去。这属于平台差异遇到权限报错时优先检查这一步。5.3 升级场景挂载好了数据才能跟得上数据挂载好以后升级 MySQL 的流程就变得非常清晰docker stop mysql8 docker rm mysql8 docker run -d \ --name mysql8 \ --restartunless-stopped \ -p 3306:3306 \ -v /data/mysql8/datadir:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDRootStr0ng!Pss \ mysql:8.1旧容器的数据目录原封不动地挂载到新容器MySQL 自身的数据文件格式如果兼容就能无缝升级。但要注意MySQL 跨大版本升级5.7 → 8.0不能直接这样操作官方要求先跑mysql_upgrade或通过逻辑备份恢复否则可能损坏数据文件。容器化只是把升级过程简化不代表可以无视版本兼容性。6. 字符集、时区与认证方式三个让中国人头大的 MySQL 细节6.1 字符集utf8mb4 到底比 utf8 强在哪很多项目遇到中文乱码根因不是业务代码而是 MySQL 的字符集设置。MySQL 的utf8字符集实际上是utf8mb3的别名它最多支持 3 个字节的 UTF-8 编码。问题是 emoji 和一些生僻字需要 4 个字节utf8存不下去会报Incorrect string value或者出现乱码。解决办法是用utf8mb4它能完整覆盖 UTF-8 编码。MySQL 8.0 的默认字符集其实已经改成了utf8mb4但 MySQL 5.7 及更早版本默认还是latin1或utf8mb3。为了在不同版本间保持一致建议在启动容器时显式指定docker run -d \ --name mysql8 \ -p 3306:3306 \ -v /data/mysql8/datadir:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDRootStr0ng!Pss \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci--character-set-serverutf8mb4设定服务器默认字符集--collation-serverutf8mb4_unicode_ci设定排序规则。utf8mb4_unicode_ci是按照 Unicode 标准排序适合绝大多数场景如果你的项目主要在中文环境也可以选utf8mb4_general_ci排序略宽松但速度稍快。对日常项目来说utf8mb4_unicode_ci更稳妥。另外建库时指定字符集也是好习惯CREATE DATABASE app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;数据库级指定以后业务表如果不显式指定都会继承这个默认值从源头杜绝乱码。6.2 时区差 8 小时不是玄学MySQL 的TIMESTAMP类型在存储时会转成 UTC 时间查询时再根据当前会话的时区转换回来。如果你的 MySQL 容器默认时区是 UTC而你本地是东八区查出来的时间就会差 8 小时。排查方法很简单进容器看一下docker exec -it mysql8 mysql -uroot -p执行SELECT NOW(); SHOW VARIABLES LIKE %time_zone%;如果NOW()返回的时间比北京时间慢 8 小时说明时区确实不对。处理办法可以在启动容器时加--default-time-zone08:00完整命令docker run -d \ --name mysql8 \ -p 3306:3306 \ -v /data/mysql8/datadir:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDRootStr0ng!Pss \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci \ --default-time-zone08:00也可以更彻底把容器系统时区也改掉挂载/etc/localtime-v /etc/localtime:/etc/localtime:ro这个方法在 Linux 上常用macOS 上因为路径差异不一定有效。最稳妥的还是在 MySQL 内部指定default-time-zone不依赖系统时区。6.3 MySQL 8.0 认证插件与老客户端的兼容问题MySQL 8.0 把默认认证插件从mysql_native_password改成了caching_sha2_password。新版的官方客户端、新版的 Navicat、DataGrip 等都能支持但老版本的客户端尤其是 5.x 时代的驱动连不上报错通常是Authentication plugin caching_sha2_password cannot be loaded这时候有几个解决方案升级客户端或驱动这是最推荐的做法安全性最好。如果暂时没法升级在 MySQL 里给用户改成老认证插件ALTER USER app_user% IDENTIFIED WITH mysql_native_password BY UserPss123; FLUSH PRIVILEGES;或者在启动容器时直接改默认认证插件--default-authentication-pluginmysql_native_password第三种方式我不建议默认使用。mysql_native_password的安全强度不如caching_sha2_password除非你确实有老客户端刚需否则没必要为了兼容性降低安全性。这是我个人的底线判断。7. 连接失败排查手册ERROR 2002、1130、Access denied7.1 先分清是容器问题还是网络问题连接 MySQL 失败的报错五花八门但排查思路只有一条逐层缩小范围。第一层容器本身是否在跑。docker ps如果没在跑看docker logs mysql8找失败原因。如果在跑进入容器内部测试本机连接docker exec -it mysql8 mysql -uroot -p输入密码能登录说明 MySQL 进程本身和账号密码都没问题问题出在网络或客户端。不能登录问题就在账号权限、密码或认证插件上。第二层宿主机上能否通过 TCP 连接。mysql -h127.0.0.1 -P3306 -uroot -p这里必须写-h127.0.0.1不要写-hlocalhost因为 MySQL 客户端对localhost会优先走 Unix socket而你的 MySQL 跑在容器里容器和宿主机之间并没有共享默认的 socket 路径走 socket 连不上会产生误导这就直接引出了下面这个经典报错。7.2 ERROR 2002socket 连接不上的真相报错原文长这样ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)这个报错让很多人以为 MySQL 没启动。实际上你的 MySQL 确实在容器里跑得好好的问题在于你用了mysql命令却没加-h客户端默认通过/tmp/mysql.sock这个 socket 文件连接而这个 socket 文件在宿主机上不存在。理解这一点后解决思路就很清晰了如果你要进容器内执行 SQL就docker exec -it mysql8 mysql -uroot -p这是走容器内 socket没问题。如果你要在宿主机上连接就显式指定 TCPmysql -h127.0.0.1 -P3306 -uroot -p如果你实在想让宿主机也能通过 socket 连可以在启动容器时把 socket 目录挂载出来-v /data/mysql8/mysql.sock:/var/run/mysqld然后在宿主机用mysql --socket/data/mysql8/mysql.sock/mysqld.sock连接。但这一步并不是必须的日常开发大多数时候直接 TCP 就够了没必要额外挂 socket 目录。7.3 1130 Host not allowed to connect 的权限处理换了 TCP 连接以后你又可能遇到ERROR 1130 (HY000): Host 192.168.1.100 is not allowed to connect to this MySQL server意思是你的 root 用户默认只允许从localhost连接宿主机或远程机器的 IP 不在允许列表里。容器环境下通常有两种解决办法。第一种启动容器时用环境变量放开 root 的远程访问-e MYSQL_ROOT_HOST%%表示允许任意主机。注意这只是“允许 root 从任意主机连接”密码还是需要的但安全上仍然有风险生产环境建议指定具体网段或 IP比如192.168.1.%。第二种已经启动容器了不想重建直接在容器内 SQL 里处理CREATE USER root% IDENTIFIED BY RootStr0ng!Pss; GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; FLUSH PRIVILEGES;我更推荐第二种因为MYSQL_ROOT_HOST环境变量只在首次初始化时生效后面改它没用而 SQL 随时可以执行方便动态管理。7.4 网络不通的最后一公里排除掉 MySQL 本身的问题后还连不上就要查操作系统层面的网络。在 Linux 宿主机上先确认端口有没有在监听ss -tlnp | grep 3306有输出说明 MySQL 容器端口映射正常。接着检查防火墙sudo firewall-cmd --list-all # 或者 sudo ufw status如果有拦截规则放行 3306 端口sudo firewall-cmd --permanent --add-port3306/tcp sudo firewall-cmd --reload还有一类坑出现在 Docker Desktop 的 Windows 上宿主机localhost:3306能连但局域网其他机器连不上。这是因为 Docker Desktop 默认的网络实现和 Linux 原生不太一样端口映射通常只绑定了宿主机本地地址。这种情况需要查 Docker Desktop 的端口绑定设置不要把宿主机防火墙作为唯一怀疑对象。我见过不少人是这样浪费时间的Windows 防火墙开了端口云服务器安全组也开了 3306结果发现容器启动时的-p参数把左侧写成了127.0.0.1:3306只绑定了回环地址外网永远连不进来。所以也要回头检查docker ps里PORTS列那一行具体绑定的 IP。8. 日常运维三板斧备份、日志、Compose 管理8.1 mysqldump 备份恢复实操Docker 里跑 MySQL备份逻辑和传统方式差不多只是命令要通过docker exec进容器执行。备份所有数据库docker exec mysql8 sh -c exec mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD /backup/mysql_all_$(date %F).sql这个命令把容器内的mysqldump备份内容重定向到了宿主机的/backup目录。注意我用了sh -c exec ...这是为了确保语法在容器默认 shell 里能正确执行。密码从环境变量$MYSQL_ROOT_PASSWORD读取不会直接暴露在宿主机命令行里。备份单个数据库docker exec mysql8 sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD app_db app_db.sql恢复数据库docker exec -i mysql8 sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD app_db app_db.sql注意恢复时用了-i把宿主机文件通过标准输入传给容器内的 mysql 命令。数据量大的时候恢复会比备份慢很多这很正常。8.2 docker logs 与错误日志运行中的 MySQL 出问题先看容器日志这是第一手现场docker logs -f mysql8-f是 follow 模式持续输出新日志。如果想看最近 100 行docker logs --tail 100 mysql8如果你挂载了数据目录MySQL 的错误日志也可能直接写在数据目录下文件名通常是.err后缀比如/data/mysql8/datadir/abc123.err。当 Docker 日志被 log rotate 清掉一部分时这个文件是更完整的备选。需要说明的是官方镜像默认把错误日志输出到 stderr所以正常情况下docker logs已经能覆盖大部分排查需求放在宿主机上tail -f /data/mysql8/datadir/*.err是从侧面补充两条路径都了解一下排查时思路更宽。8.3 docker compose 一键拉起开发库随着你管理的容器越来越多用裸docker run会变成一场灾难。比如你有 MySQL、Redis、后端服务三个容器每个都要配端口、环境变量、挂载目录、重启策略光记命令就够呛。这时候用 Docker Compose 来管理 MySQL 是最自然的演进。新建一个docker-compose.ymlservices: mysql: image: mysql:8.0 container_name: mysql8 restart: unless-stopped ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: app_db MYSQL_USER: app_user MYSQL_PASSWORD: ${MYSQL_PASSWORD} command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-time-zone08:00 volumes: - ./mysql_data:/var/lib/mysql - ./mysql_conf:/etc/mysql/conf.d对应的.env文件MYSQL_ROOT_PASSWORDRootStr0ng!Pss MYSQL_PASSWORDUserPss123在docker-compose.yml所在目录执行docker compose up -d一条命令拉起所有服务查看状态docker compose ps日志docker compose logs -f mysql。相比一个个 docker runCompose 的好处是配置即代码整个开发环境的定义都在这个 yml 文件里新同事拿到项目后执行一条命令就能得到完全一致的 MySQL 环境不用再对着文档敲十分钟命令。如果你只是装一个 MySQL裸 docker run 完全够用。但只要你预感到服务器上会出现第二个、第三个容器我建议直接上 Compose省得后面还得再写一份声明式配置迁移。最后分享一个我个人的习惯不要在命令行里直接写数据库密码尤其是docker run这种会把整条命令记录到 shell history 的操作。我在实际项目里会把.env文件加进.gitignore密码只放在服务器本地如果要用环境变量注入也优先从.env读取而不是硬编码在 compose 文件里。这套习惯在一个人维护的小项目里看不出价值但当你把数据库配置提交到仓库、别人也能看到时你就会庆幸自己从一开始就留了这个心眼。