尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Mac M1/M2芯片上使用Docker部署MySQL的完整指南与踩坑总结

Mac M1/M2芯片上使用Docker部署MySQL的完整指南与踩坑总结 这几天在给一台 M1 芯片的 Mac mini 折腾数据库环境按照网上很多教程装 MySQL容器一重启数据就没了配置文件改了也不生效折腾到半夜才把思路理顺。搜了一圈发现很多人都在问类似问题决定把自己踩过的坑和最终跑通的方案整理出来希望能帮你少走弯路。这套方案的核心思路其实不复杂MySQL 的容器本身是一次性的数据必须放到宿主机上。在 Mac M 系列芯片上因为架构从 x86 切到了 ARM镜像选择、端口映射、文件挂载这几个环节都跟以前的习惯不太一样如果你还在照搬 Intel 时代的操作方式出问题很正常。本文适合下面几类情况刚入手 M1/M2 芯片 Mac想在本地用 Docker 跑 MySQL 做开发测试已经被容器一删数据就没了坑过想搞清楚持久化到底怎么配需要修改 MySQL 配置比如字符集、最大连接数但改了没生效想弄明白配置文件的正确挂载方式准备把 Docker 里的 MySQL 用于真实项目想在 M 芯片上获得稳定的性能和可靠的数据备份方案在正式开始之前先说一句** Docker Desktop 在 Mac M 芯片上默认跑的是 ARM64 架构的容器**这直接决定了你拉取镜像、设置参数时的选择。带着这个认知往下看很多困惑会迎刃而解。1. 为什么在 M 系列芯片上装 MySQL 要单独讨论1.1 M 芯片改变了什么M1/M2 芯片采用的是 ARM 架构而大部分老教程、老镜像都是基于 x86_64Intel架构构建的。Docker Desktop 虽然内置了 Rosetta 2 转译层理论上可以运行 x86 的镜像但会带来两个实际影响性能损耗明显。数据库这种计算密集型应用转译运行浪费 CPU 资源跑起来还发烫部分 x86 镜像在转译模式下会有诡异的 bug比如 MySQL 偶发崩溃、初始化超时所以正确思路是优先使用 ARM64 原生镜像。在 Docker Hub 上MySQL 官方镜像已经提供了 multi-arch 支持拉取时会根据你的系统架构自动选择对应版本不需要手动指定 platform。1.2 镜像选择是第一步我用的是mysql:8.0这个版本 tag 对应的镜像同时支持 amd64 和 arm64Docker 会自动匹配。如果你在 Docker Desktop 的设置里确认过Use Rosetta for x86_64/amd64 emulation on Apple Silicon这个选项那么即使误拉了一个 x86 的镜像也还能跑但不推荐在生产环境里这么用。docker pull mysql:8.0拉取完成后可以检查一下镜像的架构信息docker inspect mysql:8.0 | grep Architecture我这里输出的是arm64说明本地运行的是原生 ARM 镜像。如果你看到amd64大概率是 Docker Desktop 自动转译了。1.3 环境版本参考我当前的运行环境供参考项目版本设备Mac mini (M1, 2020)系统macOS Sonoma 14.xDocker Desktop4.25MySQL 镜像mysql:8.0.x容器管理方式docker-compose不同的 Docker Desktop 版本在界面和默认行为上略有差异但底层原理一致。下面方案在这些组合上都验证过。2. 目录结构设计与持久化原理2.1 容器文件系统和宿主机的隔离很多人第一次接触 Docker 时容易把容器当作一台小虚拟机来用。但容器的可写层是临时的——当容器被删除docker rm或者重新创建后原来写在容器内部的数据就没了。MySQL 的数据默认写在容器内的/var/lib/mysql目录如果不做任何处理删容器就等于删数据库。持久化的本质就一句话把容器内目录挂载到宿主机目录数据落在宿主机上容器只是运行时的壳。2.2 推荐的目录规划我习惯把 MySQL 相关文件统一放在一个目录下方便管理~/docker/mysql/ ├── data/ # 数据库数据文件 ├── conf/ # 自定义配置 │ └── my.cnf ├── logs/ # 错误日志和慢查询日志 └── docker-compose.yml创建目录mkdir -p ~/docker/mysql/{data,conf,logs}这样做的理由很简单data目录保存所有数据库文件备份时只需打包这个目录conf目录存放自定义配置文件随时修改并重启容器生效不需要重新镜像logs目录独立分开排错时直接看宿主机上的日志文件不用进容器2.3 为什么用 docker-compose 而不是 docker run单条docker run命令当然能跑起来但可读性差、不易维护。等你需要调整端口、加容器、改环境变量时命令会越来越长。docker-compose.yml把容器的所有配置写在一个文件里后续维护只需要改这一个文件。而且 compose 有一个隐藏好处它会自动创建自定义网络容器之间可以通过服务名互相访问。后续你如果还要跑 phpMyAdmin、Redis 之类的容器它们和 MySQL 直接通过服务名通信即可不用暴露端口到宿主机。3. 编写 docker-compose.yml每一步的关键决策3.1 完整配置示例下面是我最终跑通的docker-compose.yml文件先贴出来再逐行解释version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql8 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: app_db MYSQL_USER: app_user MYSQL_PASSWORD: app_pass_2024 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - ./data:/var/lib/mysql - ./conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./logs:/var/log/mysql command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci security_opt: - seccomp:unconfined3.2 环境变量初始化时机只有一次很多教程会告诉你MYSQL_ROOT_PASSWORD是设置 root 密码的但有个关键细节经常被忽略这些环境变量只在数据目录首次初始化时生效。如果你启动容器之后发现密码不对去修改MYSQL_ROOT_PASSWORD再重启容器是没用的因为 data 目录已经初始化过了。这时候需要进入容器手动改密码或者直接把 data 目录清空重新初始化。所以第一遍启动的时候就想好密码别指望后面改环境变量能覆盖。3.3 端口映射注意本地冲突3306:3306表示把宿主机的 3306 端口映射到容器的 3306 端口。如果你的 Mac 上已经装了原生 MySQL 或者别的服务占用了 3306 端口启动会报错。这时候可以改成3307:3306宿主机用 3307 端口访问其他不变。检查端口占用lsof -i :3306如果有进程在监听先处理冲突再用 compose 启动。3.4 三个挂载卷各自的作用数据目录挂载./data:/var/lib/mysql这是持久化的核心。MySQL 的所有数据库文件、binlog、undo log 都写入/var/lib/mysql挂载到宿主机./data之后即使容器被删除数据仍然在。重启容器、重新 compose up数据都还在。一个常见问题如果你在 Linux 上跑过 MySQLdata目录可能已经有了文件换到 Mac 上直接挂载会导致启动失败。解决方案是清空 data 目录让 MySQL 重新初始化。配置文件挂载./conf/my.cnf:/etc/mysql/conf.d/my.cnfMySQL 官方镜像的配置加载机制是分层的。基础配置文件在/etc/mysql/下/etc/mysql/conf.d/目录下的.cnf文件会被自动加载。所以把自定义配置挂载到conf.d目录不需要修改镜像里的基础配置这是侵入性最小的方式。日志目录挂载./logs:/var/log/mysql挂载日志目录可能遇到权限问题。MySQL 容器内的 mysql 用户 uid 通常是 999如果你宿主机上的 logs 目录属于 root容器内写日志会报 permission denied。最简单的办法是给目录加写权限chmod -R 777 ~/docker/mysql/logs3.5 关于command参数和配置文件的取舍在docker-compose.yml里我用了command直接指定字符集参数同时也支持在my.cnf里配置。两条路都走得通区别在于用command传递可视化程度高一眼看出启动参数适合临时验证写在my.cnf文件里统一管理方便长期维护适合和团队共享我实际使用时两种方式都保留了。my.cnf里写的是长期生效的配置command里是核心默认参数。你完全可以把command里的参数并到my.cnf里只保留一种方式避免重复。3.6 关于security_opt: seccomp:unconfined这个参数在 M 芯片上尤其值得注意。MySQL 官方镜像在某些 ARM 环境下如果 seccomp 默认 profile 限制过严初始化时可能报错。加上seccomp:unconfined可以在一定程度上规避这类问题。但这也意味着容器逃逸后的系统调用不受 seccomp 保护仅建议在开发环境使用。4. 配置 my.cnf这些参数在 M 芯片上值得设置4.1 一个可以直接用的配置模板我在~/docker/mysql/conf/my.cnf里写的配置如下[mysqld] # 字符集 character-set-server utf8mb4 collation-server utf8mb4_unicode_ci # 连接数 max_connections 200 # 默认存储引擎 default-storage-engine InnoDB # InnoDB 缓冲池大小根据 Mac 内存调整 innodb_buffer_pool_size 512M # 日志 slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 2 log_error /var/log/mysql/error.log # 时区 default-time-zone 08:00 [client] default-character-set utf8mb44.2innodb_buffer_pool_size设置多少合适这是 InnoDB 最重要的内存参数。如果你的 Mac 是 16GB 内存建议设 512M 或 1G如果是 8GB 内存设 256M 就够了。Docker 容器可用的内存上限受到 Docker Desktop 设置的限制你可以在 Docker Desktop 的 Settings 里给容器分配更多内存否则即使配置写大了也不会生效。4.3 修改配置后如何让配置真正生效很多人改完 my.cnf 重启容器发现没生效原因是没搞清楚 MySQL 加载配置文件的顺序。MySQL 按照以下顺序读取配置/etc/my.cnf/etc/mysql/my.cnf/etc/mysql/conf.d/*.cnf/etc/mysql/mysql.conf.d/*.cnf后面的配置会覆盖前面的同名参数。你挂载到/etc/mysql/conf.d/my.cnf的是第三个位置如果/etc/my.cnf里有了相同参数可能优先读到前者的值。排查方法进入容器查看实际生效的配置docker exec -it mysql8 mysql -uroot -p -e SHOW VARIABLES LIKE character_set_server;如果显示的值不是你配置的需要检查是否被其他配置文件覆盖了。一般来说官方镜像在conf.d目录下没有预设相同参数所以挂载在这里是可靠的。4.4 字符集踩坑实录我遇到过这么个情况在某台机器上启动后创建表时发现character_set_server是latin1导致中文写入变乱码。排查后发现是挂载的 my.cnf 文件编码格式不对——文件里有隐藏的 BOM 头MySQL 解析时出错根本没正确读取配置。所以在 Mac 上编辑配置文件务必确保文件编码是 UTF-8无 BOM。如果你用文本编辑App 保存过 .cnf 文件建议用 VS Code 或 Sublime 重新确认编码格式。5. 启动、验证与日常使用5.1 启动容器在~/docker/mysql目录下执行docker-compose up -d首次启动会先创建数据目录并初始化数据库需要几十秒时间。查看启动日志docker-compose logs -f mysql看到类似[System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections.的日志说明启动成功。5.2 本地连接验证mysql -h 127.0.0.1 -P 3306 -u root -p输入密码后如果出现mysql提示符说明连接正常。你也可以用 Sequel Ace、Navicat 等客户端测试连接连接参数和上面一致。5.3 进入容器执行命令有时候需要在容器内部执行 SQL 或命令docker exec -it mysql8 bash进入容器后执行mysql -uroot -p即可。如果你需要执行一段 SQL 文件可以直接通过管道传输cat init.sql | docker exec -i mysql8 mysql -uroot -p你的密码5.4 重启、停止和删除容器# 重启 docker-compose restart mysql # 停止并保留数据 docker-compose down # 停止并删除容器和网络数据卷还在 docker-compose down --volumes # 注意这会删除数据卷如果没挂载宿主机目录数据就没了我这里用的是 bind mount宿主机目录挂载所以down --volumes不会误删~/docker/mysql/data下面的数据。但如果你用的是 Docker volumemysql_data:/var/lib/mysql这种写法data 就保存在 Docker 管理的卷里down --volumes会彻底删除数据一定要慎用。5.5 查看容器资源占用M 芯片的 Mac 上容器的 CPU/内存占用可以在 Docker Desktop 的仪表盘直接看到也可以用命令行docker stats mysql8正常情况下 MySQL 8 的空闲内存占用在 300~700MB 之间取决于innodb_buffer_pool_size。如果你的 Mac 内存紧张调低这个值立竿见影。6. 数据备份、恢复与迁移6.1 备份方案一目录直接打包因为 data 目录已经挂载到宿主机最简单粗暴的备份方式就是打包 data 目录# 先停掉容器保证数据一致性 docker-compose stop mysql # 打包 data 目录 tar -czvf mysql-data-backup.tar.gz ./data # 恢复 docker-compose start mysql这种方式的优点是不需要 MySQL 客户端和额外工具缺点是停服期间不能写入数据。适合开发环境或个人使用。6.2 备份方案二mysqldump 逻辑备份不停止服务用mysqldump备份指定数据库docker exec mysql8 mysqldump -uroot -p你的密码 \ --single-transaction --routines --triggers \ app_db app_db_backup.sql恢复cat app_db_backup.sql | docker exec -i mysql8 mysql -uroot -p你的密码 app_db这种方式在数据量大的情况下备份时间较长但做迁移很方便导出的 SQL 文件可以在任何 MySQL 环境上恢复和架构无关。6.3 整体迁移到另一台 Mac换新 Mac 或者给同事同步环境时最简单的流程用mysqldump导出所有数据库在目标机器上按本文步骤配置好 MySQL 容器导入 SQL 文件如果你数据量特别大可以直接拷贝整个data目录。但要注意 MySQL 版本必须一致或兼容且拷贝时目标机器上不能有正在运行的 MySQL 实例否则数据目录被占用。6.4 定时备份的进阶思路如果你需要定期自动备份可以借助 macOS 自带的launchd或crontab执行脚本。一份简单的备份脚本思路如下使用mysqldump导出所有数据库按日期命名备份文件保留最近 N 天的备份清理旧的这里不建议用容器内的 crontab因为容器本身可能随时被重建。定时任务放在宿主机上更加稳定。6.5 验证备份是否有效备份最怕的是关键时刻发现备份文件是坏的。我每次备份完成后都会做一次快速验证# 新建一个临时容器挂载备份文件并导入确认无报错 docker run --rm -v $(pwd):/backup mysql:8.0 \ bash -c mysql -uroot -pxxx /backup/app_db_backup.sql echo OK这只是最基本的验证方式对于重要数据建议定期做一次完整的恢复演练。7. Mac M 芯片专属问题排查手册7.1 容器反复重启日志显示权限错误症状docker ps看到容器不停地 Restartingdocker logs显示chown: changing ownership of /var/lib/mysql: Permission denied之类的错误。原因宿主机 data 目录属主和容器内 mysql 用户uid 999不一致且目录没有写权限。解决办法sudo chown -R 999:999 ~/docker/mysql/data或者收紧到只影响目录属主sudo chown -R 999 ~/docker/mysql/data7.2 镜像拉取速度慢或超时M 芯片上需要的镜像和 x86 不是同一个层可能某些镜像源没有缓存 ARM64 的层导致拉取慢。解决办法是给 Docker Desktop 配置国内镜像源在 Settings - Docker Engine 里修改 registry-mirrors。7.3 端口映射后宿主机连不上先确认容器内 MySQL 是否正常运行docker exec -it mysql8 mysqladmin ping -uroot -p如果容器正常但宿主机连不上检查端口监听状态lsof -i :3306如果端口没在监听可能是 Docker Desktop 的网络问题。重启 Docker Desktop 基本能解决。还有一种情况如果你之前用docker run启动过一个没有正确暴露端口的容器需要先删除旧容器再启动新的。7.4 MySQL 容器启动后 30 秒左右自动退出日志里如果看到[ERROR] [MY-010131] [Server] Cant create test file /var/lib/mysql/.mysql_test_file大概率还是权限问题。用上面的 chown 方法修复 data 目录权限即可。7.5 连接报错Public Key Retrieval is not allowed用客户端连接 MySQL 8 时如果用了caching_sha2_password认证插件客户端需要开启允许公钥检索选项。在连接参数里加上allowPublicKeyRetrievaltrue。另外你也可以创建使用mysql_native_password插件的用户来避免这个问题但 MySQL 8.4 开始mysql_native_password默认禁用长远建议升级客户端而不是改认证插件。7.6 重启 Mac 后容器没有自动启动我设置了restart: unless-stopped正常来说 Docker Desktop 启动后容器会自动启动。如果没起来检查Docker Desktop 是否设置了开机自动启动是否手动 stop 过容器unless-stopped策略下手动停止的容器不会在 Docker 启动时自动拉起如果是第二种情况手动执行docker-compose start mysql即可。8. 性能细节与进阶优化8.1 M 芯片上的 IO 性能表现在 M1 Mac mini 上用 SSD 跑 MySQL 8 容器实测简单增删改查的响应速度和原生安装的 MySQL 几乎没有差别差异在 5% 以内。但如果你的 Docker Desktop 使用的虚拟磁盘位于 macOS 系统盘的加密卷上IO 会有一定损耗。设置 Docker Desktop 的 Disk image location 时可以把它放到非系统盘上。8.2 内存参数调整建议MySQL 8 默认配置偏保守但也别一上来就调大所有参数。对大部分开发场景innodb_buffer_pool_size设为物理内存的 20%~25% 比较合理max_connections开发环境 100~200 足够太多反而浪费内存performance_schema如果你不需要性能监控数据可以关闭以节省约 200MB 内存关闭 performance_schema 的方法是在 my.cnf 里加一条performance_schema OFF这个开关对内存敏感的场景很有效但如果你要用 MySQL Workbench 的 Performance Dashboard需要打开它。8.3 远程访问和网络安全默认情况下 MySQL 只监听容器内的 3306 端口但由于 Docker Desktop 做了端口映射宿主机局域网的其他机器可以直接通过你的 Mac IP 访问 MySQL。这在你开发测试时很方便但生产环境要小心。一个简单的加固方案不要映射 0.0.0.0而是只映射到回环地址ports: - 127.0.0.1:3306:3306这样外部设备就访问不到只能本机访问。需要其他机器连接时再改成0.0.0.0:3306:3306或者直接省去 IP 部分Docker 默认监听所有接口。8.4 和 Docker 内其他容器通信如果你在 compose 文件里还定义了其他服务比如 Nginx、Redis它们可以通过服务名mysql直接访问数据库例如services: app: image: your-app-image environment: DB_HOST: mysql DB_PORT: 3306这样 MySQL 的 3306 端口只暴露在 Docker 内部网络宿主机上不映射端口减少了暴露面。这是生产环境更推荐的部署方式。9. 从 8.0 升级到 8.1/8.2 的注意事项MySQL 的版本升级比想象中更频繁而且大版本之间有些特性差异。我在测试 8.1 版本时发现一个现象直接用新镜像替换旧镜像挂在同一个个 data 目录上有时候会提示版本不兼容。因为 MySQL 数据文件的格式可能随版本变化跨大版本升级前必须做一次逻辑备份并在新版本上验证导入。如果你只是想从 8.0.x 升级到同大版本的最新 patch比如 8.0.35 - 8.0.36通常直接换镜像 tag 重启容器即可数据目录兼容。但如果跨到 8.1/8.2这些是创新版本强烈建议先备份再升级或者干脆保留两个 data 目录做 A/B 切换。10. 写在最后的一点个人体会整个方案跑通之后我对自己有一个要求任何一次对容器的大操作删除、重建、换镜像之前都先备份一遍 data 目录。这条习惯帮我躲过至少三次灾难——有一次就是改配置改到一半发现容器起不来了幸好有备份才能迅速回滚。另外说一个大家容易忽略的点Docker Desktop 本身也会消耗 Mac 的内存资源如果你本来就开着浏览器、IDE、通信工具一堆东西再跑 MySQL 容器可能会明显感觉到卡顿。这时候优先考虑少开点不用的大程序而不是一上来就调低 MySQL 的参数——毕竟在正常开发负载下MySQL 容器只占了很少一部分 CPU真正的内存大头往往在其他地方。M 系列芯片上跑 Docker MySQL 并不是什么高深操作把镜像架构、数据挂载、配置加载、权限这几个关键点理顺整个过程其实很顺畅。希望这篇文章能让你少踩几个坑把精力花在真正需要关注的业务逻辑上。如果你在配置过程中遇到本文没有覆盖到的问题建议先按两个思路自查一是看容器日志二是看端口监听和权限状态。这两个排查入口能解决绝大多数启动失败的情况。
返回列表