ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker搭建本地MySQL实战:从环境准备到数据持久化

Docker搭建本地MySQL实战:从环境准备到数据持久化 1. 为什么我建议你用 Docker 搭本地 MySQL先说结论如果你只是临时想跑一个 MySQL 实例做测试、学 SQL或者给本地项目提供一个数据库服务Docker 是最省心的一条路。这不是标题党我踩过太多次直接装 MySQL 的坑后来彻底切到 Docker 方案现在本地开发环境里跑着 MySQL、Redis、MongoDB 全套换版本跟切菜一样简单。直接装 MySQL 的痛点想必不少人都经历过。Windows 上双击安装包一路 Next 之后系统服务里多了一个 MySQL但它跟系统绑定得死死的——想换个大版本卸载不干净注册表残留服务删不掉。Linux 上稍微好点但也逃不过依赖冲突、配置文件路径分散、多版本共存困难这些问题。更麻烦的是MySQL 8.0 之前的认证插件和 8.0 之后的不兼容本地装了一版项目里连另一版连密码都报错非常头疼。Docker 的思路完全不一样。它把 MySQL 跑在一个独立的容器里容器外面的人只看到几个对外暴露的端口看不到内部系统被改动。数据库文件用数据卷挂在宿主机上容器删了再建数据还在。想换版本改一行镜像标签然后重建容器几分钟搞定。这才是本地开发该有的体验。这篇文章适合这么几类人刚接触 Docker 想拿 MySQL 练手的新手做本地开发需要一套干净数据库环境的后端工程师以及被多版本 MySQL 折磨到想放弃的运维和测试同学。我会从 Docker 环境准备讲起把拉镜像、启动容器、配置参数、数据持久化、docker-compose 编排到最后的问题排查全部过一遍每一行命令都给出理由保证你照着敲就能跑起来。2. Docker 环境准备Windows 和 Linux 的差别与坑2.1 Windows 上装 Docker Desktop 前必做的两件事Windows 上跑 Docker 的原理跟 Linux 不太一样Docker 引擎本身是 Linux 内核的东西Windows 不能直接跑所以需要借助 WSL2 或者 Hyper-V 虚拟出一个完整的 Linux 环境。我个人强烈建议用 WSL2 方案它的资源占用、启动速度、兼容性都比 Hyper-V 好。装 Docker Desktop 之前你需要先确认两件事。第一Windows 10 必须是 2004 版本或更高Windows 11 没这个问题。查看方法很简单WinR 打开运行框输入 winver弹出的窗口会明确显示版本号。如果你的版本太老要么先更新系统要么老老实实用 Docker Toolbox——但我强烈不建议后者那玩意维护得很差各种坑。第二CPU 虚拟化必须在 BIOS 里开启。开机时按 Del 或 F2 进 BIOS找 Intel Virtualization Technology 或 AMD SVM Mode设为 Enabled。任务管理器里切换到“性能”标签页右下角如果显示“虚拟化: 已启用”说明没问题。然后打开“启用或关闭 Windows 功能”勾选“适用于 Linux 的 Windows 子系统”。装完后重启在管理员 PowerShell 里执行 wsl --update 和 wsl --set-default-version 2确保 WSL 内核是最新的。注意Docker Desktop 安装包下载慢的话可以试试用国内镜像源下载或者直接在官网下载页面复制你对应版本的下载链接用下载工具拉速度会快不少。Docker Desktop 装好后建议在设置里做两件事。第一Settings - Resources - WSL Integration把你常用的 WSL 发行版比如 Ubuntu勾上这样才能在 WSL 终端里直接用 docker 命令。第二Settings - Docker Engine 里配置镜像加速器这个对国内用户很重要直接把 Docker Hub 的拉取速度提升一大截。推荐配置的核心代码登录 Docker 账号后可以开启 Docker Hub 的个人限速提升但更实用的做法是配置 registry-mirrors填入你的云厂商镜像加速地址。如果没有云厂商账号用一些公共镜像站也行但稳定性参差不齐建议自己多试试。2.2 Linux 上安装 Docker Engine 的最短路径Linux 上装 Docker 反而简单很多因为 Docker 本来就是 Linux 原生的东西。以 Ubuntu 22.04 为例官方推荐的做法是添加 Docker 官方仓库然后用 apt 安装。我不太建议用发行版自带的 docker.io 包版本太老很多新特性用不上而且跟 Docker 官方源冲突时容易出诡异问题。先更新索引并安装依赖sudo apt update sudo apt install ca-certificates curl gnupg lsb-release然后添加 Docker 官方 GPG 密钥和仓库sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null接着正式安装sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin安装完成后启动服务并设置开机自启sudo systemctl enable docker --now sudo systemctl status docker这一步走完Docker 基本就能跑了。但有个细节要注意普通用户直接执行 docker 命令会报 permission denied因为 docker 的 socket 文件只对 root 开放。两条路一是每次用 sudo docker 前缀二是我建议的做法——把当前用户加入 docker 组sudo usermod -aG docker $USER newgrp docker这样以后敲 docker 命令就不用再带 sudo 了。但这里有个安全提示能执行 docker 命令的用户其实等同于拥有 root 权限所以在多人共用的服务器上加组要谨慎如果你只是个人开发机那就无所谓。2.3 验证 Docker 环境是否正常环境装好之后先别急着拉 MySQL做一次完整的验证。执行docker --version docker compose version docker run hello-world第一条命令看 Docker 主版本第二条看 compose 插件是否就位第三条会拉取一个最小的测试镜像并运行容器如果控制台出现 Hello from Docker! 相关的输出说明整个引擎链路是通的。这一步千万别跳过我见过有人折腾了半天 MySQL最后发现是 Docker 进程根本没启动。如果第三条报错先确认 Docker Desktop 是不是已经启动——Windows 上托盘区应该有个鲸鱼图标Linux 上检查 systemctl status docker。如果是虚拟化没开的报错比如 Virtualization support not detected回到 BIOS 里把虚拟化打开再重启。这些问题在前面准备阶段都处理过这里通常就能顺利通过。3. 用 docker run 快速拉起一个 MySQL 实例3.1 镜像选择5.7 还是 8.0环境就绪后第一个决策是选 MySQL 镜像版本。这直接决定你后面连接数据库时用什么认证方式、什么默认字符集。Docker Hub 上的官方镜像库mysql/mysql-server和 Docker 官方镜像mysql是两个不同的东西。我平时主要用mysql它是 Docker 官方维护的更新勤、文档全、坑相对少。镜像标签里mysql:5.7是最经典的选择很多老项目、基于 PHP 5/7 的老系统都在用这个版本它的默认认证插件是 mysql_native_password兼容老的客户端连接器。mysql:8.0则默认用 caching_sha2_password安全性更高但如果你用的客户端工具版本较老会出现 Authentication plugin caching_sha2_password cannot be loaded 的报错。我个人给新项目做测试时默认选 8.0因为这是官方当前的主推版本特性和性能都更好。但如果你手头有老项目、或者教程是基于 5.7 写的直接选 5.7 也没问题。关键是你要清楚两者的差异不要一个项目里想着兼容两个版本的字符集和认证方式。这里还要提一个搜索热词里反复出现的版本mysql:5.7.44。这是 5.7 分支的最后一个维护版本很多生产环境的存量实例停在这个版本如果你需要复现生产环境的问题拉这个精确版本最靠谱。Docker 的镜像标签本来就支持精确版本指定这比在系统里装一个指定版本再想方设法锁定它省心多了。3.2 标准启动命令逐项拆解拉镜像本身很简单docker pull mysql:8.0这里我不建议直接 pull 不带标签的mysql:latest因为 latest 标签指向的版本不受控今天拉是 8.0过一阵可能就是 8.2 甚至 9.x。在开发环境里保持版本明确能省去很多排查昨天还能跑今天连不上的时间。镜像拉下来后启动容器的命令是所有后续操作的地基我拆开讲docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEtest_db \ -e MYSQL_USERtest_user \ -e MYSQL_PASSWORDtest_pass \ -v mysql-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0逐个参数解释一下因为理解每个参数意味着什么你才能按需调整而不是照抄。-d表示后台运行容器不占用当前终端。没有这个参数容器会在前台运行CtrlC 就把它停了。--name mysql-dev给容器起一个固定的名字这样后面 docker stop mysql-dev、docker logs mysql-dev 都不用去查容器 ID。如果不指定Docker 会随机起一个像tender_shannon这种名字查起来麻烦。-p 3306:3306做端口映射左边是宿主机端口右边是容器内端口。MySQL 默认监听 3306所以这个映射把宿主机 3306 端口收到的连接转发给容器里的 MySQL。如果你本机已经有一个 MySQL 占着 3306就把左边改成 3307-p 3307:3306这样你访问 localhost:3307 就能连到容器里的 MySQL。-e是环境变量参数。MYSQL_ROOT_PASSWORD设置 root 账号的密码如果你不设置容器首次启动时会自动生成一个随机密码并打印在日志里查起来很不方便所以我会明确指定。MYSQL_DATABASE会在初始化时创建一个库方便你上来就测。MYSQL_USER和MYSQL_PASSWORD则创建一个普通用户并授权给它访问 MYSQL_DATABASE 创建的库这样一个普通用户就够日常开发用了不需要每次都拿 root 去连。-v mysql-data:/var/lib/mysql是数据卷挂载把容器里 MySQL 存数据的目录映射到宿主机上的一个由 Docker 管理的数据卷里。这样你删掉容器、重建容器数据还在。这条参数是把 MySQL 数据和容器生命周期解耦的关键没有它你每次 docker rm 都会连数据一起删光哭都来不及。-v /etc/localtime:/etc/localtime:ro把宿主机的时区文件挂载进容器让容器内系统时间跟宿主机一致。MySQL 的 NOW()、CURRENT_TIMESTAMP 这些函数会直接受影响如果你不挂载这个容器默认 UTC 时区你存一个2025-01-01 12:00:00查出来发现是2025-01-01 04:00:00之类的偏差。这个坑我刚开始用 Docker 时没注意后来被数据的时间差狠狠坑过一次。如果你需要设置时区更彻底的做法可以再加上-e TZAsia/Shanghai这样容器的系统级时区也是中国标准时间了。命令执行完用docker ps看看容器状态如果 STATUS 列显示 Up说明启动成功。如果显示 Exited用docker logs mysql-dev查看启动日志90% 的情况是端口被占用或者数据卷权限问题。3.3 首次连接验证确认容器起来了还要确认 MySQL 真的能连上。先后台看日志docker logs mysql-dev日志末尾出现ready for connections或者类似port: 3306 MySQL Community Server - GPL的输出就说明服务已经就绪。注意首次启动需要初始化数据目录可能要等十几秒甚至更久尤其是机械硬盘环境下别看到日志没动静就以为失败了。然后进入容器内部用 MySQL 自带的客户端验证docker exec -it mysql-dev mysql -uroot -p输入密码后如果出现mysql提示符说明一切正常。进入后SELECT VERSION();查看版本号SHOW DATABASES;查看初始库确认test_db是否创建成功。这一步还有个常见误区docker exec和你本机的mysql命令是两回事。如果你本机没装 MySQL 客户端不要慌在容器里执行就好了。如果你确实希望从宿主机直接连那需要在宿主机装 mysql-client然后执行mysql -h127.0.0.1 -P3306 -uroot -p注意-h不能用 localhost因为 localhost 在某些环境下会走 socket 而不是 TCP导致连接失败。4. 数据持久化再也不怕容器删了数据没了4.1 为什么 MySQL 容器必须挂数据卷这是整个 Docker 化 MySQL 里最核心的一个话题。很多新手第一次用 Docker 跑 MySQL 时没有挂载数据卷跑起来也正常数据也能写就以为没事了。直到某天不小心docker rm了容器或者升级 Docker Desktop 后容器全部重建才发现数据库里的表和数据全军覆没那一刻的心情我不想再体验第二次。原理其实不复杂。容器本身是一个临时性的运行环境docker rm删除容器时默认会把容器可写层的数据一起清掉。MySQL 的数据写在容器里的/var/lib/mysql这个路径在容器可写层里所以容器一删数据就没了。而数据卷volume是 Docker 在宿主机上独立管理的存储区域不随容器生命周期而删除。挂载之后MySQL 往/var/lib/mysql里写的文件实际落在宿主机上由 Docker 管理的目录里容器删了数据卷和里面的文件都还在。更关键的是如果你不挂数据卷每次创建新容器MySQL 都要重新做一次初始化生成系统库、生成初始用户、刷新授权表。这些步骤耗时不说万一中间断了很容易留下一个损坏的数据目录。提示用-v mysql-data:/var/lib/mysql这种命名卷的方式而不是用-v /绝对路径:/var/lib/mysql这种直接绑定挂载的写法。命名卷的好处是 Docker 会帮你管理文件权限和所有者绑定挂载经常遇到权限问题容器内 MySQL 以 mysql 用户运行而宿主机的文件目录权限不对导致无法写入。4.2 命名卷 vs 绑定挂载的选型挂载方式有两种选择取决于你的使用场景。命名卷方式是-v my-mysql-data:/var/lib/mysql。数据存储在 Docker 管理的路径下Linux 上通常在/var/lib/docker/volumes/my-mysql-data/_data你不用关心具体位置Docker 全权管理。这个方式的优势是权限问题少、跨平台行为一致、不需要手动创建目录适合大多数开发场景。备份时用docker run --rm -v my-mysql-data:/data alpine tar -czf /backup.tar.gz -C /data .就能把整个数据卷打包出来。绑定挂载方式是-v /home/user/mysql-data:/var/lib/mysql。数据直接存在你指定的宿主机目录里你能在文件管理器里直接看到数据库文件备份只需复制整个目录。这个方式的优势是数据位置透明适合你想用 MySQL Workbench 直接操作物理文件、或者需要把数据和实体文件一起交给别人、或者数据必须放在指定盘符的 Windows 场景。两种方式可以并存也可以随时互相切换。我的习惯是临时测试用命名卷正式本地开发项目用绑定挂载因为绑定挂载让我一眼能看到数据文件在哪出了问题也能直接查文件心里踏实。4.3 备份、恢复与迁移备份这种事最怕的就是以为没事结果出事的时候找不到备份。我建议把备份操作刻在肌肉记忆里每次做重要变更前都备份一次。用 mysqldump 做逻辑备份是最通用的方案不依赖数据卷类型只依赖 MySQL 本身的工具docker exec mysql-dev mysqldump -uroot -p --databases test_db backup_$(date %Y%m%d).sql这个命令把test_db库的结构和数据全量导出到一个 SQL 文件。恢复时cat backup_20250101.sql | docker exec -i mysql-dev mysql -uroot -p注意这里用了-i而不是-it-t通常指分配一个伪终端那个在输入重定向时会导致不可预期的问题比如回车被转换、管道被干扰等只用-i表示保持标准输入打开就行。Windows 环境下执行这些命令文件路径就要带上传入位置比如把备份文件放到D:\backups然后执行docker exec mysql-dev mysqldump -uroot -p --databases test_db D:/backups/backup_20250101.sql还有一种冷备份方式直接把数据卷复制一份docker run --rm -v mysql-data:/data -v /tmp:/backup alpine tar czf /backup/mysql-data.tar.gz -C /data .这种方式备份的是整个数据目录恢复时也要停掉目标容器把数据卷整体替换。优点是包含所有配置、binlog、undo log适合整个数据库完全迁移缺点是不够灵活跨大版本恢复时容易不兼容。从实际经验看逻辑备份比冷备份更常用。MySQL 的逻辑备份文件只要版本跨度不是太大比如 5.7 的备份恢复到 8.0 一般是支持的都能通过 MySQL 自带工具导入。冷备份则强依赖版本一致性5.7 的数据目录直接搬到 8.0 大概率是起不来的。所以我平时双保险每周一把冷备份存到独立磁盘每天做一次 mysqldump 逻辑备份两手抓。5. 进阶用 docker-compose 把 MySQL 管起来5.1 为什么需要 docker-composedocker run 适合跑一个孤零零的 MySQL 实例但真实项目里数据库几乎不会单独存在。你跑一个后端应用可能要同时启动 MySQL、Redis、消息队列。用三条 docker run 命令管理这三个服务虽然也能跑但命令会越来越长参数越来越混乱环境变量堆在一起毫无可读性更别说那些必须按顺序启动的前后依赖关系。docker-compose 解决的就是多容器应用编排的问题。它把多个容器的配置写进一个 YAML 文件用docker compose up -d一条命令拉起全部服务用docker compose down一次性停止清理。配置全在一个文件里跟着项目走换台电脑 clone 下来直接就能把环境跑起来不需要重新回忆当初用了哪些参数。MySQL 这种有状态服务放 compose 里管理尤其合适因为它的配置相对固定数据卷、端口、环境变量、健康检查这些配置声明在文件里比你每次敲 docker run 时拼写命令可靠得多。5.2 docker-compose.yml 实战配置创建一个项目目录比如~/dev-env/mysql在里面新建docker-compose.ymlservices: mysql: image: mysql:8.0 container_name: mysql-dev restart: unless-stopped ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: test_db MYSQL_USER: test_user MYSQL_PASSWORD: test_pass TZ: Asia/Shanghai volumes: - mysql-data:/var/lib/mysql - ./init-scripts:/docker-entrypoint-initdb.d:ro - ./my.cnf:/etc/mysql/conf.d/my.cnf:ro networks: - dev-network healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -p123456] interval: 10s timeout: 5s retries: 5 start_period: 30s command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: mysql-data: networks: dev-network: driver: bridge这里面有几个配置值得展开讲。restart: unless-stopped表示容器因为异常退出时自动重启但如果你手动 docker stop 了它就不会自动拉起来。这个策略适合本地开发时不想半夜爬起来拉容器的情况。environment里的变量跟 docker run 的-e完全对应但 YAML 格式更清晰。注意密码如果有特殊字符最好加引号避免 YAML 解析问题。volumes下挂载了三个路径。第一个是数据卷跟前面一样。第二个是./init-scripts目录Docker 的 MySQL 镜像有一个隐藏功能容器首次启动时会按字母顺序执行/docker-entrypoint-initdb.d/目录下的.sql、.sh、.sql.gz文件。这意味着你可以把手写的建表语句、初始化数据、创建用户的 SQL 脚本放在这个目录下第一次启动时自动执行库表结构一次性就位。第三个是自定义的my.cnf配置文件挂载到 MySQL 的配置目录里灵活调整 MySQL 参数。healthcheck定义健康检查每 10 秒执行一次 mysqladmin ping连续失败 5 次就标记为不健康。这在编排依赖场景里非常有用比如应用容器可以通过 depends_on 等 MySQL 健康后再启动。command后面跟的是 MySQL 启动参数这里设置了默认字符集为 utf8mb4。这是 2024 年几乎必做的配置——MySQL 5.7 默认字符集是 latin1会导致中文乱码8.0 默认是 utf8mb4但显式写出来更稳。让项目从一开始就使用 utf8mb4你会在未来数据迁移、内容管理、日志分析里少掉无数乱码的坑。需要注意如果你挂载了自定义的my.cnf配置文件里也可以写同样的内容[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci两种方式等效选择你喜欢的一种即可。启动docker compose up -d停止并保留数据卷docker compose down停止并删除数据卷慎用docker compose down -vdown -v会把数据卷一起删掉跑完别想找数据我提醒过你了。平时维护用docker compose stop和docker compose start就够了别动不动 down。5.3 从 docker run 切换到 compose 的迁移注意事项已经有 docker run 跑着的容器想换成 compose 管理需要注意一点容器名、数据卷、端口不能冲突。做法是docker stop mysql-dev docker rm mysql-dev先停掉并删除旧容器然后确保 compose 里的数据卷名字不一致——docker run 时如果用的是命名卷mysql-datacompose 里也定义了同一个名字的mysql-data那么 Docker 会识别出这是同一个数据卷不会重建数据。这是数据卷设计的好处命名卷是全局命名空间下的容器挂了但它还在compose 照样可以挂载它。但也容易踩坑如果你的 docker run 用的是绑定挂载路径比如-v /home/me/mysql-data:/var/lib/mysql而 compose 里用的是命名卷那二者的数据就完全隔离了你会在新环境里看到一个空的数据库。迁移时要么在 compose 里显式用- /home/me/mysql-data:/var/lib/mysql这种类型要么用docker cp或者 mysqldump 把数据迁移过来。6. 常见问题与实战排查技巧6.1 端口被占用症状docker run 启动后立刻退出docker logs mysql-dev看到bind: address already in use。原因宿主机 3306 端口被其他程序占用比如你之前直接装的 MySQL、MariaDB或者另一个 Docker 容器。排查netstat -ano | findstr 3306 # Windows ss -lntp | grep 3306 # Linux看到 LISTEN 状态的进程确认是什么占了端口。解决方法是把你新容器映射到别的端口比如-p 3307:3306或者在 compose 里改 ports 映射。我个人建议开发机上统一规划主机 MySQL 占 3306Docker 里跑的项目库占 3307、3308互相不打架。6.2 连接报认证插件错误症状本机用 MySQL 客户端连接容器里的 MySQL 8.0报Authentication plugin caching_sha2_password cannot be loaded。原因MySQL 8.0 默认认证插件是 caching_sha2_password而你本机的客户端特别是 5.x 版本的 Navicat、老版本 JDBC 驱动、某些语言库不认识这个插件。解决方案有几个升级客户端到支持 caching_sha2_password 的版本这是最推荐的做法在创建用户时显式指定旧认证方式CREATE USER x% IDENTIFIED WITH mysql_native_password BY password;修改全局参数让 MySQL 使用旧插件但不建议影响面太大连接同一个 MySQL 服务却因为客户端版本不同而出现认证失败这是 8.0 迁移路上最常见的坑。如果项目代码里报这个错优先检查 JDBC 驱动或 ORM 的版本升级通常能解决70%的问题。6.3 数据卷权限导致的启动失败症状容器启动后立即退出日志里出现[ERROR] InnoDB: Operating system error number 13 in a file operation或者Permission denied。原因绑定挂载了宿主机的目录但目录的所有者是 root容器内 MySQL 进程以 mysql 用户运行没有写权限。解决方案把宿主目录改成容器内 mysql 用户的 UID。MySQL 镜像里 mysql 用户的 UID 通常是 999执行sudo chown -R 999:999 /path/to/mysql-data这个数字在不同镜像版本里可能略有不同保险起见在容器里执行id mysql看一下。这个坑在使用命名卷时几乎不会出现因为 Docker 会自动处理权限但在绑定挂载场景下非常常见——所以新手我建议全部用命名卷等熟练了再玩绑定挂载。6.4 容器启动慢或内存不足症状Docker Desktop 里 MySQL 容器一直没起来或者起来后过一会儿 OOM 挂掉。日志里有InnoDB: mmap(137428992 bytes) failed。原因Docker Desktop 默认分配给虚拟机的内存不够MySQL 8.0 的 InnoDB 缓冲池默认占用不小再加上其实 WSL2 也会占内存。解决Docker Desktop 的 Settings - Resources 里把 Memory 调到 4GB 以上电脑内存 16GB 的话 6GB 不亏再把 Swap 提升到 1GB。如果是长期开发机建议给 Docker Desktop 固定分配别让它动态争抢。有一个更精细的调法启动容器时限制 InnoDB 缓冲池大小减少内存占用docker run -d ... mysql:8.0 --innodb-buffer-pool-size256M这个小参数能让你的开发机内存占用降一个量级适合配置只够跑 IDE 的机器。开发环境完全没必要给 MySQL 2GB 缓冲池256MB 跑测试没问题。6.5 时区不一致症状程序写入数据库的时间比本地时间少了 8 小时。原因就一句话MySQL 容器系统时区是 UTC你写的是北京时间。排查确认时区docker exec mysql-dev mysql -uroot -p -e SELECT NOW();如果显示的比本地时间少 8 小时就是时区问题。解决方式在启动时挂载/etc/localtime并设置TZ环境变量前文已经说了。但要注意已经跑起来的容器改环境变量只能删掉重建没法热改。而已经存在的 datetime/timestamp 数据不会自动转换需要手动刷新一下补偿。6.6 每次 docker run 都要临时拉一堆包症状docker run hello-world秒过但拉 MySQL 镜像时卡住进度条纹丝不动或者超时。原因Docker Hub 在国内的访问经常不稳定尤其是拉大镜像时。MySQL 镜像大概 500MB裸连很容易断。解决在 Docker Desktop 设置里或者 Linux 的/etc/docker/daemon.json里配置 registry-mirrors。让我把 Linux 的配置方式写出来{ registry-mirrors: [https://your-mirror.mirror.aliyuncs.com] }然后重启 Dockersudo systemctl restart docker配置生效后拉取镜像时 Docker 会自动从镜像站拉取速度通常能到几MB/s 甚至更快。这一步做完后面所有 Docker 操作都会舒服很多。实测下来配不配镜像加速完全就是拉一个镜像五分钟 vs 三十秒的区别。6.7 M1/M2 Mac 的兼容问题如果你是 Apple Silicon 的 Mac跑 MySQL 镜像要注意架构问题。Docker Desktop 会自动拉取 arm64 版本的镜像大多数情况下没问题。但如果你用docker run --platform linux/amd64强制跑 x86 版本性能会差一些而且某些老版本 MySQL比如 5.6的 arm64 支持不完整。好在这种情况在主流镜像上已经不常见了。如果你确实遇到exec format error之类的问题检查docker inspect mysql-dev | grep Architecture确认镜像架构是否匹配。不确定的话就让 Docker Desktop 自己选默认架构它能拿 arm64 就拿 arm64没有才去模拟 x86。6.8 常见问题速查表问题表现首选方案端口被占bind: address already in use改宿主机端口映射认证插件报错caching_sha2_password cannot be loaded升级客户端 / 用户指定旧认证容器起不来日志有 Permission denied绑定挂载目录 chown 给 999数据全丢docker rm 后回不去先确认数据卷是否挂上再删容器时间不对入库时间差 8 小时挂载 localtime 设置 TZ拉镜像崩溃进度卡住不动配置 registry-mirrors连接不上容器 Up 但 MySQL 未就绪查看 docker logs 确认 ready for connections中文乱码含中文列显示 ????启动参数改 utf8mb47. 几个进一步提高效率和稳定性的配置7.1 自定义字符集与排序规则日常开发中如果没做任何配置MySQL 8.0 默认字符集是 utf8mb4排序规则是 utf8mb4_0900_ai_ci。这个配置在多数场景下没问题但有些老工具对utf8mb4_0900_ai_ci支持不够好或者你需要兼容比较老的数据迁移。我更推荐的组合是utf8mb4_general_ci它对中文排序、老客户端兼容性更好跟 5.7 时代迁过来的数据无缝衔接。配置方式我已经写在了上面的 compose 文件里就是--character-set-serverutf8mb4 --collation-serverutf8mb4_general_ci这两行参数。理解字符集决策背后的逻辑很重要utf8mb4 是 UTF-8 的完整版本支持 BMP 之外的字符emoji、生僻字而 MySQL 里的 utf8mb3 才是大家常说的 utf8 老实现——只支持三个字节。如果你用 utf8mb3插入 emoji 就会报Incorrect string value的错。就这一条足够让你死心塌地用 utf8mb4 了。7.2 初始化脚本自动建表镜像自带的/docker-entrypoint-initdb.d目录是一个很实用的功能。首次启动时MySQL 会创建一个空数据目录然后依次执行这个目录下的脚本。这意味着你可以把项目需要的库表结构、测试数据、存储过程都放进去新环境一条命令拉起来表结构已经在了。目录内容示例init-scripts/ 01-schema.sql 02-data.sql脚本执行的顺序按文件名字母排序所以用数字前缀来控制顺序非常方便。01-schema.sql建表02-data.sql插入基础数据。注意这个目录只在数据目录为空时生效——如果你已经有一个带数据的数据卷再往 init-scripts 里加脚本不会执行这是设计如此不要困惑。7.3 把 MySQL 端口对局域网开放默认-p 3306:3306会暴露在宿主机所有网络接口上。如果你用的是公司网络同一网段的其他人也能扫到你的 3306 端口。开发环境这台机器一般不设防容易被队友或者扫描工具进来。要限制只本机访问用-p 127.0.0.1:3306:3306docker run -d --name mysql-dev -p 127.0.0.1:3306:3306 mysql:8.0这样只有你本机能连局域网别人访问不到。等你有明确需求比如让同一局域网的同事连一下你的库排查问题再放开也不迟。7.4 容器网络模式简述Docker 的默认网络模式是 bridge容器各自拥有独立的 IP通过端口映射对外提供服务。如果你在本机跑一个应用容器和一个 MySQL 容器想让它们互相通信最简单的办法是让应用连接127.0.0.1:宿主机映射端口而不是去查 MySQL 容器的 IP。但更正规、也是 compose 默认的做法是把多个容器放在同一个自定义网络里。自定义网络里容器可以通过容器名互相解析比如 MySQL 容器叫mysql-dev应用容器里配置数据库 host 直接写mysql-dev就行不需要关心 IP 和端口映射。这就是我上面的 compose 配置里定义dev-network的原因。这个特性在真实项目中非常常见掌握它你就能理解为什么项目里配置文件的数据库地址是服务名而不是 localhost。8. 一些真实的使用建议和扩展方向用 Docker 跑 MySQL 这条路我已经走了好几年从最初各种踩坑到现在一条 compose 文件就能一键拉起整套环境。有几个真实体会值得分享。时刻记住容器是临时的、数据是永久的这个核心原则。容器随便删、随便重建只要数据卷挂对了数据就不会丢。反过来如果不挂数据卷不要轻易删容器。这句话值得记住能帮你规避掉绝大部分灾难性数据丢失场景。版本明确化。我在所有项目里都显式写好 MySQL 镜像的完整版本号比如mysql:8.0而不是mysql:latest。latest在本地偶尔还能跑但一旦镜像仓库更新了标签指向你哪天清理缓存重建容器就会发现本地库版本突变了积压的 binlog 格式不兼容直接报错。固定版本问题就少一半。初始化脚本写好了换电脑、换同事、clone 项目不到五分钟就能拉到一套完全一致的数据库环境。这种基础设施即代码的体验真的比手动在一台新机器上装 MySQL、跑 SQL 脚本、配权限要省太多的时间了。这个方案后续还能继续扩展。比如在 compose 里加一个 phpMyAdmin 或 Adminer 容器来可视化操作数据库加一个 Redis 容器统一开发缓存用 mysqldump 加 cron 定时任务做自动备份甚至在 docker-compose 里把后端应用容器一起编排实现本地一键全部启动。数据库环境的使用频率极高把这块打磨顺了整个开发体验都能舒服不少。最后想说的是Docker 化 MySQL 的真正价值并不在于省去安装步骤而在于把数据库环境变成了项目的一部分——是可复制、可迁移、可版本控制的。任何环境问题都不再是我本机装了什么我不知道的东西这种玄学而是清清楚楚写在配置文件里的几个参数。这个心智模型转变之后你不管是本地开发还是在别的机器上复现问题都会淡定很多。
返回列表