
1. 把思路理清Docker 跑 MySQL 到底图什么1.1 本地开发为什么要用 Docker 而不是直接装 MySQL先说结论本地要快速跑一个 MySQL我建议直接打开 Docker拉一个官方镜像一分钟左右你就能得到一个干净的数据库服务。相比去官网下载 MySQL 安装包、一路点下一步再忍受服务占用、版本冲突、卸载不干净容器方式在本地开发里明显更省心。这个方案特别适合需要反复创建/销毁数据库、同时测多个版本、或者不想让开发机变成一团乱麻的人。我在实际项目里最常见到的场景是这样项目 A 要用 MySQL 5.7项目 B 要 8.0两个项目还要同时启动联调。如果都用原生安装你得在一台机器上折腾两个实例端口、数据目录、系统服务、环境变量全要手工隔离装的时候容易卸载的时候你就会发现一堆残留文件和服务。用 Docker 就简单很多一个容器对应一个实例镜像 tag 不同就是版本不同端口可以自定义数据卷相互隔离删掉容器重来完全不心疼。也有一部分朋友觉得 Docker 多了一层虚拟化性能会有损耗。对本地开发来说这种担心基本可以忽略。MySQL 跑在容器里只是进程级隔离加上 Docker Desktop 在 Windows/Mac 上有 WSL2 或 Hypervisor 支撑日常增删改查、建表、跑脚本完全感受不到差异。真正的性能瓶颈通常出现在数据量极大、并发极高、需要细粒度内核参数调优的场景那是生产环境要专门考虑的事。1.2 这方案适合谁不适合谁适合的人群很明确前端或后端开发需要在本地起一个数据库测试同学要快速搭一套环境跑自动化用例数据工程师要临时验证 SQL学生要学习 MySQL 语法运维想在一个干净环境里复现问题。对这些场景来说Docker 版 MySQL 的“可抛弃、可重建、可多版本共存”三个特性几乎是量身定做的。不适合的场景也有如果你电脑内存只有 4G 甚至更低跑 Docker Desktop 已经很吃力再起 MySQL 容器可能会让整个系统变卡如果你需要非常底层的性能分析比如直接调内核 IO 调度、做裸设备映射原生安装会更直接再就是公司安全策略禁止在开发机用容器或者项目必须依赖系统级的 MySQL 服务这些都不适合硬上 Docker。搞清楚边界后面遇到问题才不会慌。这一部分的核心思路就是容器化真正的价值不是“更高级”而是“可复制、可隔离”。别人怎么搭的一个 compose 文件就能复现你本地怎么跑的换个电脑十分钟搞定。后面所有的命令、模板、避坑方法都是从“可复现”这个出发点展开的。2. 从安装 Docker 到能拉镜像这一步决定后面顺不顺2.1 Docker Desktop 安装与虚拟化检查要在本地基于 Docker 构建 MySQL前提是先有一台能正常跑容器的 Docker 环境。Windows 上主流方案是 Docker Desktop它依赖 WSL2 或者 Hyper-V。很多新手在安装 Docker Desktop 后启动失败提示 “virtualization support not detected” 或者 “Docker Desktop failed to start”其实多数不是软件问题而是虚拟化没开或者 Windows 功能没启用。碰到这种情况我建议按顺序排查先进 BIOS 确认 CPU 虚拟化已开启Intel 对应 VT-xAMD 对应 SVM再到 Windows 的“启用或关闭 Windows 功能”里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”最后确认 WSL2 内核已更新。如果以前用过旧版 Hyper-V还需要以管理员身份运行bcdedit /set hypervisorlaunchtype auto重启后再试。Mac 上安装 Docker Desktop 会比较省心直接到官网下载 dmg 拖进 Applications 就行但也要注意系统版本是否匹配。安装完成后不要急着拉 MySQL先用命令确认 Docker 引擎正常。打开终端或 PowerShell 执行docker version docker compose version docker psdocker ps不报错说明客户端和守护进程已经连通。如果只有docker version能看但docker ps报无法连接多半是 Docker Desktop 还在后台启动中等几秒再试。另外一个很实际的经验是Docker Desktop 默认资源设置里内存最好不要低于 2GBCPU 至少给 2 核。只跑一个 MySQL 不觉得如果你同时还要跑 Redis、应用容器、前端构建资源给太小会频繁卡顿。2.2 镜像选择mysql:8.0、mysql:5.7 还是其他分支镜像选对后面省一半事。Docker Hub 上最常见的 MySQL 镜像是官方mysql系列老项目常用mysql:5.7新项目建议直接上mysql:8.0。这两个版本在认证方式、默认字符集、SQL 行为上都有差异。MySQL 8.0 默认使用caching_sha2_password认证插件很多旧版客户端和旧驱动连不上MySQL 5.7 默认是mysql_native_password兼容性更好但已经停止功能更新只做安全维护。我的建议是除非你明确要兼容老项目否则本地开发默认选mysql:8.0。原因很简单现在新部署的数据库基本都基于 8.0你本地用 5.7 测出来的 SQL、排序规则、字符集行为上了生产环境可能就不一样。如果工作中确实要多版本测试就同时跑两个容器分别映射 3306 和 3307 端口互不干扰。还有一个看起来不起眼但很关键的习惯不要用latest标签。latest会跟随镜像发布会变今天拉的是 8.0过几个月可能变成 8.4 甚至 9.x。MySQL 大版本升级不是简单地换个二进制就能启动数据目录格式不兼容会直接导致容器起不来。固定 tag比如mysql:8.0能保证你半年后重新拉镜像行为和今天基本一致。2.3 镜像拉取慢的问题提前解决本地首次拉 MySQL 镜像需要下载几百 MB如果网络环境不理想会非常折磨人。Docker Desktop 里可以配置镜像加速地址Settings - Docker Engine在 JSON 配置中添加registry-mirrors字段然后 Apply Restart。国内很多云厂商都提供镜像加速服务选一个你网络环境下实际可用的地址填进去就行。需要注意的是镜像加速只对 Docker Hub 官方仓库拉取有效如果自定义镜像来自其他仓库该配置不起作用。拉取完镜像后可以用docker image ls确认mysql:8.0已经存在。到这里环境准备就完成了下一步才是真正“快速构建 MySQL 数据库”的环节。3. 快速构建 MySQL一条命令跑起来三个步骤不丢数据3.1 最简启动命令和每个参数的意义先给一个能直接复制使用的最简命令docker run -d \ --name mysql-dev \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ --restart unless-stopped \ mysql:8.0这条命令看起来简单但是每个参数都值得细说。-d表示后台运行容器不会把日志直接打在终端里。--name mysql-dev给容器起个固定名字后面docker logs、docker exec、docker stop都可以直接用这个名字。-e MYSQL_ROOT_PASSWORDRoot123456是给 MySQL 初始化 root 用户密码这个环境变量只在第一次初始化数据目录时生效不是每次启动都改密码很多人误以为改这个值就能重置密码结果折腾半天。-p 3306:3306是端口映射宿主机 3306 映射到容器内 3306。如果你本机已经装了原生 MySQL端口会被占用这时需要改成-p 3307:3306客户端连接端口也随之改成 3307。-v mysql-data:/var/lib/mysql是数据卷挂载容器内的 MySQL 数据文件会写到命名卷mysql-data里容器删除后数据还在这是整个命令里最容易漏但最关键的参数。--restart unless-stopped表示容器非手动停止时遇到电脑重启或 Docker 重启会自动拉起对本地开发很友好。3.2 端口冲突与本地连接方式运行完上面的命令后用docker ps能看到容器状态。如果显示Up说明 MySQL 已经在监听。连接时我建议用127.0.0.1而不是localhost因为某些客户端会把localhost解析成 Unix socket而 Docker 容器里默认不一定暴露了 socket 路径。命令行连接mysql -h127.0.0.1 -P3306 -uroot -p如果你本机没有安装 MySQL 客户端也可以直接进入容器执行docker exec -it mysql-dev mysql -uroot -p这里的-it是为了进入交互终端。很多教程只教你一条docker run不教你如何连进去结果用户以为容器起好就结束了其实还需要客户端工具。进入容器后先执行SHOW DATABASES;验证权限和连接没问题再开始建库建表。另外docker port mysql-dev可以查看容器端口映射情况排查端口异常时非常有用。3.3 数据持久化命名卷还是目录挂载上一条命令里我用了命名卷mysql-data这是我最推荐的本地方案。命名卷由 Docker 管理你不需要关心数据实际落在磁盘哪个路径迁移和清理都用 Docker 命令完成。查看数据卷位置docker volume inspect mysql-data输出里的Mountpoint就是宿主机上的实际存储目录。在 Windows 上这个路径通常在 WSL2 的虚拟磁盘内部不会直接显示为一个普通文件夹这是正常现象。另一种方案是绑定挂载也就是把宿主机某个目录直接映射进去比如-v /Users/me/mysql-data:/var/lib/mysql。好处是备份和管理直观你把整个目录拷走就是一份数据坏处是不同操作系统路径写法不同而且如果目录权限不对MySQL 初始化时会报错。Linux 下还容易出现 uid/gid 不匹配导致容器内的 mysql 用户无法写入挂载目录。对于本地快速搭建我更推荐命名卷简单、安全、不容易踩权限坑。还有一点要反复强调容器删除不等于数据卷删除。docker rm mysql-dev只会删掉容器只要你没有手动执行docker volume rm mysql-data数据还在。下次重新创建容器时再把同一个数据卷挂上去旧数据就回来了。这是 Docker 设计里非常优秀的一点也是新手最容易误解的一点。3.4 初始化数据库docker-entrypoint-initdb.d 目录MySQL 官方镜像支持在首次启动时自动执行/docker-entrypoint-initdb.d目录下的.sh、.sql、.sql.gz脚本。如果你有初始化建库、建用户、导入基础数据的需求不需要等容器起来后手动执行直接在宿主机建一个init目录把 SQL 文件放进去再挂载到容器里。先创建一个init.sql文件内容示例如下CREATE DATABASE IF NOT EXISTS app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER app% IDENTIFIED BY app123456; GRANT ALL PRIVILEGES ON app_db.* TO app%; FLUSH PRIVILEGES;启动命令变成docker run -d \ --name mysql-dev \ -e MYSQL_ROOT_PASSWORDRoot123456 \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ -v $(pwd)/init:/docker-entrypoint-initdb.d \ mysql:8.0注意脚本只会在数据目录为空的第一次启动时执行。如果你之前已经跑过容器数据卷里已经有初始化痕迹了再往init目录里加 SQL 不会自动执行。这时要么用docker exec手动执行 SQL要么确认数据不需要后把容器和数据卷都删掉重新 run。对CREATE USER app%这种写法本地开发没问题%表示允许任意主机连接。但生产环境一定要限制网段比如app192.168.1.%否则等于把数据库裸奔在网络里。这个习惯从第一天就要养成不要因为本地方便就随手开放所有来源。4. 用 Docker Compose 管理 MySQL字符集、时区、健康检查和备份恢复4.1 一个可以直接抄的 docker-compose.yml命令行适合临时验证但如果你打算把 MySQL 跑成固定的本地开发依赖我更推荐用 Docker Compose。Compose 文件是文本可以放进项目仓库队友拉下来后一个docker compose up -d就能把数据库环境起好不用再复制粘贴一大段docker run命令。下面是一份我常用的docker-compose.yml你可以直接抄。services: mysql: image: mysql:8.0 container_name: mysql-dev restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: Root123456 MYSQL_DATABASE: app_db MYSQL_USER: app MYSQL_PASSWORD: app123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql - ./init:/docker-entrypoint-initdb.d - ./conf:/etc/mysql/conf.d command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-time-zone8:00 - --max-connections200 healthcheck: test: [CMD, mysqladmin, ping, -h, 127.0.0.1, -uroot, -p$$MYSQL_ROOT_PASSWORD] interval: 10s timeout: 5s retries: 5 volumes: mysql-data:MYSQL_DATABASE和MYSQL_USER这两个环境变量很有用首次初始化时会自动创建对应的数据库和用户和前面手写init.sql的效果类似。需要注意MYSQL_USER只会对MYSQL_DATABASE授权也就是说app用户默认只能访问app_db不能访问其他库这是符合最小权限原则的。command部分会把参数传给 mysqld相当于命令行指定启动项。放到这里的好处是配置跟着项目走。healthcheck部分用mysqladmin ping检查容器是否真正可用而不是只看进程是否启动。第一次初始化 MySQL 可能需要几十秒没有健康检查的话你可能会在容器还没 ready 时就急着连接然后被一堆连接错误劝退。4.2 自定义 my.cnf 我建议改哪几个参数除了在command里传参更灵活的方式是挂载自定义配置目录。在项目目录下建一个conf文件夹里面放my.cnf[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci max_connections200 innodb_buffer_pool_size512M sort_buffer_size2M sql_modeSTRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION这里我解释一下为什么要关注这几项。character-set-server和collation-server决定服务端默认字符集和排序规则不设的话可能出现中文乱码或排序不对的问题。max_connections默认是 151本地开发时如果不小心有一堆连接没释放很容易撞上限改成 200 能多扛一点。innodb_buffer_pool_size是 InnoDB 的缓冲池大小对性能影响最大但也不要贪大物理内存只有 8G 时给 512M 足够了给 2G 反而可能让 Docker Desktop 总内存吃紧。sort_buffer_size是每个排序操作分配的缓冲区增大对ORDER BY和索引不友好的查询有帮助但它是 per-connection 分配的设置太大容易内存爆炸。需要注意conf目录下的配置文件会覆盖镜像默认配置如果你在多个文件里写了同一个参数生效规则可能让你困惑。最简单的做法是只放一个my.cnf不要拆成多个片段本地开发没必要追求复杂的配置管理。4.3 备份与恢复别等数据出问题才想起来本地数据库虽然不像生产数据那么珍贵但开发到一半的测试数据、造出来的业务数据一旦丢了也很影响心情。备份 MySQL 容器最简单的方式是用官方mysqldump工具。执行一条在宿主机上的命令docker exec mysql-dev sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --routines --triggers --set-gtid-purgedOFF --databases app_db app_db_backup_$(date %Y%m%d).sql这条命令有几个细节--single-transaction是 InnoDB 下做一致性备份的前提不会锁住表--routines和--triggers把存储过程和触发器一起备份--set-gtid-purgedOFF是为了后续恢复到普通 MySQL 实例时避免 GTID 报错--databases app_db会在备份文件里带上CREATE DATABASE语句恢复时不用提前建库。恢复时执行docker exec -i mysql-dev sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD app_db_backup_20250101.sql这里的-i表示把宿主机文件内容通过标准输入传给容器内的 mysql 命令。有人会奇怪为什么不是docker exec ... mysql file.sql因为重定向符号在宿主机上生效必须用-i把文件流送进容器里否则容器内看不到宿主机文件路径。如果你用的是 Windows PowerShell直接执行这些 shell 管道命令可能有问题建议在 WSL 或 Git Bash 环境下运行。4.4 连接数据库从宿主机、从另一个容器、从可视化工具本地起好 MySQL 容器后最常见的连接方式有三种。第一种是宿主机命令行连接前面已经说过mysql -h127.0.0.1 -P3306 -uroot -p。第二种是项目里的后端服务连接只要服务和 MySQL 在同一个 Docker 网络下就不需要走端口映射了而是用服务名作为主机名比如容器名mysql-dev连接地址写mysql-dev:3306。这也是 Compose 一个很大的便利它自动创建了一个默认网络同一个 compose 文件里的服务互相可以通过服务名访问。第三种是图形化工具连接比如 MySQL Workbench、Navicat、DBeaver、dbx 这些。连接参数基本都类似主机填127.0.0.1端口填映射到宿主机那个端口用户名填root或你创建的app密码对应填上。如果用的 MySQL 8.0 且客户端比较旧连接时会遇到认证插件问题或 SSL 问题解决方法我会在下一个大章节专门说。这里先提醒一句本地连接图形化工具JDBC 字符串可以简化成jdbc:mysql://127.0.0.1:3306/app_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiuseSSLfalse是本地开发常用的简化方式生产环境不要这样裸奔。allowPublicKeyRetrievaltrue是为了支持 MySQL 8 的caching_sha2_password认证在非 SSL 连接下也能安全地获取公钥有些工具第一次连接会因此失败加上这个参数就好了。5. 本地 MySQL 容器常见问题排查这些坑我基本都踩过5.1 Docker Desktop 提示 virtualization support not detected这个提示几乎每天都有新手问。报错全称类似 “Docker Desktop failed to start because virtualization support not detected”第一反应不要慌95% 的情况是虚拟化没开或者 Windows 功能不完整。先去看 BIOS 设置Intel 平台开启 VT-xAMD 平台开启 SVM这一步优先级最高。如果 BIOS 里已经开了再去 Windows 功能里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”安装完 WSL2 内核更新包后重启。还有一个我踩过的细节如果电脑上曾经安装过 VirtualBox 或 VMware它们和 Hyper-V/WSL2 可能互相冲突。要么把虚拟化方式统一要么关闭其中一个。不要同时开多套虚拟机平台否则 Docker Desktop 起来后各种奇怪问题比如网络不通、容器卡死、CPU 飙升。另外如果错误提示出现在 Windows 更新之后可以检查一下bcdedit /set hypervisorlaunchtype auto这个命令需要管理员终端改完重启。5.2 3306 端口被占用容器一直起不来端口冲突的典型报错是Error response from daemon: driver failed programming external connectivity on endpoint mysql-dev: Bind for 0.0.0.0:3306 failed: port is already allocated。这种问题一看日志就明白但新手往往盯着docker run命令纠结以为写错了参数。其实只要换一个宿主机端口就行容器内部永远听3306变的只是映射到宿主机的端口。排查占用可以使用netstat -ano | findstr :3306Windows 下会显示占用进程的 PID再到任务管理器里确认是哪个软件。Mac 或 Linux 使用lsof -i :3306。如果是你自己之前装的另一个 MySQL 服务占用了端口我不建议直接强制杀进程更好的做法是把 Docker 映射改成 3307。后续所有连接工具、JDBC 连接串都把端口换成 3307 即可。这个改动只影响外部访问容器内部逻辑完全不变。5.3 容器重启以后连不上要么还在初始化要么密码没变很多人启动容器后立刻连接结果报Cant connect to MySQL server on 127.0.0.1。其实容器进程可能已经起来了但 MySQL 还在做数据目录初始化尤其是第一次启动时要生成系统表、初始化权限、执行 initdb 脚本这个过程需要几十秒。解决办法是看日志docker logs -f mysql-dev日志里出现ready for connections或者port: 3306 MySQL Community Server的时候才代表可以连接。Compose 配置里的 healthcheck 也是为解决这个问题存在的你在docker ps里看 STATUS 列如果显示healthy就放心连。另一个容易误解的问题修改MYSQL_ROOT_PASSWORD并不会重置已存在数据卷里的 root 密码。这个环境变量只作用于首次初始化。如果忘记密码不要着急删数据卷可以启动一个临时容器挂载同一个数据卷在命令里加--skip-grant-tables进入容器后用 SQL 更新密码。具体的临时容器启动方式比较复杂但思路是数据没有丢就不要用删数据卷这种粗暴方式来解锁。5.4 MySQL 8 连接报 SSL 错误或 Public Key Retrieval 错误MySQL 8.0 默认认证插件是caching_sha2_password很多旧版本的工具和驱动不认识这个插件于是出现Public Key Retrieval is not allowed或The server requested authentication method unknown之类的报错。还有一种情况是客户端默认忍不住要协商 SSL本地环境证书又对不上于是报SSL connection error。解决问题的第一选择是升级客户端或 JDBC 驱动让它们支持 MySQL 8。如果暂时不方便升级再考虑从数据库侧兼容。例如在 MySQL 里把用户改回旧的密码插件ALTER USER root% IDENTIFIED WITH mysql_native_password BY Root123456; FLUSH PRIVILEGES;在本地开发环境这样做问题不大但生产环境不建议因为mysql_native_password已经进入弃用流程。对于 Java 项目也可以保持数据库不动只改连接串useSSLfalseallowPublicKeyRetrievaltrue。对于图形化工具通常在连接配置里找到 SSL 选项选择disable或no即可。工具之间选项名不一样但思路一样本地连接优先允许非 SSL让认证过程走简单链路。5.5 中文乱码与排序问题字符集和排序规则选错中文乱码的本质通常是三层字符集不一致客户端连接字符集、数据库/表字符集、字段字符集。从 MySQL 8.0 开始服务端默认就是utf8mb4比老版本默认utf8mb3好很多。但还是建议在启动容器时就固定character-set-serverutf8mb4和collation-serverutf8mb4_unicode_ci避免不同镜像版本之间的默认值差异。建表时也显式声明不依赖服务端默认值CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;排序问题更隐蔽。如果你执行SELECT * FROM user ORDER BY name发现中文排序结果不符合预期大部分原因是排序规则选得不对。utf8mb4_general_ci速度快一些但对 Unicode 排序支持不精确utf8mb4_unicode_ci规则更完善中文拼音排序通常更自然。MySQL 8.0 还引入了utf8mb4_0900_ai_ci基于 Unicode 9.0 的标准精度更高。如果业务对中文排序有明确要求要测试不同 collation 下的行为不要想当然。字符集层面还有一个连接串参数要写对。Java JDBC 里需要characterEncodingutf8因为utf8在 MySQL 中实际映射到utf8mb4Python/Go 等客户端也要确保连接参数指定 utf8mb4。连接后可以执行SET NAMES utf8mb4确认。5.6 容器删了数据卷还在磁盘空间越占越大Docker 用起来爽但它是磁盘空间吞噬者。镜像、容器层、命名卷、构建缓存每一个都可能占用几个 GB 到几十 GB。当你频繁创建销毁 MySQL 容器时镜像层和废弃卷会累积。查看占用情况docker system df如果VOLUME SPACE很大可能是以前跑测试库留下的数据卷。清理前先docker volume ls看看有哪些卷确认没有需要保留的数据后再执行docker volume prune。这个命令会删除所有未被容器引用的匿名卷和具名卷使用前务必确认。镜像清理则用docker image prune -a它会删除所有没有被容器引用的镜像。本地调试时建议不要动不动就prune -a因为你可能刚拉完一个镜像准备换版本测试结果一清理下回又要重新下载。更安全的做法是先docker ps -a确认没有你需要保留的容器再针对性地docker volume rm 卷名。6. 最后分享我日常在用的“最小可靠流程”如果上面这些内容你觉得信息量有点大不用全背下来只需要记住一套最简单的操作节奏。我第一次跑本地 MySQL 容器后把一个 compose 文件固定在了项目仓库里以后每个新同事加入只需要git pull后执行docker compose up -d数据库环境就齐了。项目结束要清理就执行docker compose down -v但要注意-v会把命名卷也删掉如果里面是要留档的数据先备份。我有一个坚持了很久的习惯所有 MySQL 容器统一用--restart unless-stopped。这个策略保证电脑重启、Docker Desktop 重启后数据库能自动拉起来而不是每次都要手动docker start。另外我强烈建议在新环境第一次 connect 时不要急着导入大量表数据先跑一个最简单的SELECT VERSION();确认字符集、权限、网络都通再继续后面的工作。说实话Docker 跑 MySQL 的难点从来不是那条 run 命令而是理解容器生命周期和数据之间到底是怎么回事。容器是临时进程数据卷才是长期资产。你只要牢牢记住数据放挂载卷、配置写 compose 文件、排查看 docker logs本地数据库环境这件事就能变得非常省心。以上这些都是我反复踩坑后沉淀下来的操作习惯希望对你有帮助。