先交代一个背景我最近在帮客户落地一套内部管理系统环境是 Ubuntu 22.04 Docker Compose数据库沿用 MySQL 5.7。一开始我也纠结要不要直接上 8.0但项目里有一套老业务系统部分 SQL 写法、存储过程和 8.0 的默认认证插件兼容性不理想切换成本摆在那最终还是定了 5.7。这次把整个部署过程、配置细节、踩过的坑完整梳理出来专门给像你一样需要在 Docker 环境里跑生产 MySQL 的同学参考。这篇内容面向的是已经有基本 Docker 概念、想直接抄一份可靠 compose 配置的读者。我会把镜像架构、数据持久化、参数调优、备份恢复、以及常见启动报错一次讲清楚每一步都解释为什么这么配而不是只丢给你一段能跑的 yaml。如果你正在规划 MySQL 容器化或者已经在生产环境里遇到过容器重启丢数据、时区错乱、Arm 机器拉错镜像这类问题这篇应该能帮你省不少试错时间。1. Docker Compose 跑 MySQL 的方案选型与设计思路1.1 为什么不用裸机安装也不用一个个 docker run先说个反直觉的结论在中小规模业务里MySQL 跑在 Docker 里完全没问题前提是你把该挂载的目录挂好、该限制的资源限好、该做的备份做好。裸机安装 MySQL 的老路子的最大问题是环境一致性。你在开发机上是 Ubuntu 20.04生产环境可能是 CentOS 7数据库编译参数、字符集默认值、glibc 版本带来的行为差异很容易在“开发环境好好的上了生产就出怪问题”这类状况里让人抓狂。而容器镜像把 MySQL 的完整运行环境打包好官方镜像从 MySQL 官方 Docker Hub 仓库发布经过大量用户验证比你自己编译或手工安装的版本可靠得多。那为什么不直接用docker run一条条跑因为生产环境从来不是只跑一个 MySQL。旁边可能还有 Redis、Nacos、应用服务。用docker run启动的容器每次重启都要手动记住端口、网络、挂载卷、环境变量一旦机器重建就要重新敲一大串命令。而 Compose 把这些全部声明在一个docker-compose.yml文件里所有服务一键拉起既能单独管理 MySQL也能将来在同一个文件里扩展其他中间件。1.2 生产环境下 MySQL 容器化的核心争议长期被吐槽的其实是这几个问题数据安全、性能损耗、以及容器生命周期管理带来的操作门槛。数据安全方面最怕的是容器一删数据全没。这个问题完全可以通过挂载数据卷解决后面我会给出具体的卷配置。性能损耗方面MySQL 跑在容器里的额外开销主要来自网络层NAT 端口映射和存储驱动overlay2 文件系统。内网跨容器访问走自定义 Docker 网络端口映射的开销可以忽略存储方面MySQL 数据文件一旦写入数据卷volume实际 I/O 就发生在宿主机磁盘上overlay2 的额外开销也会被绕过大半。实测下来容器化 MySQL 和裸机 MySQL 在生产负载下的 TPS/QPS 差距在 5% 以内这对绝大多数业务来说完全可接受。真正要警惕的反而不是性能而是运维习惯改变。以前是systemctl start mysql现在变成docker compose up -d以前看错误日志去/var/log/mysql/error.log现在要docker compose logs mysql。这种心智模型的切换需要时间但只要配置写得规范团队协作效率反而更高——因为整个数据库的初始化方式被写进了代码仓库任何新机器都能一键复现。1.3 这套方案最终解决的问题清单我这次部署方案主要解决四件事数据持久化容器重建、升级、迁移都不丢数据配置持久化my.cnf关键参数通过挂载文件管理不进容器改配置无需重新构建镜像环境一致性开发、测试、生产用同一份 compose 文件减少环境差异引入的故障快速灾备恢复配合定时mysqldump备份或通过数据卷备份实现整库恢复接下来我从镜像准备开始逐步展开整个落地过程。2. 版本选择、镜像架构与离线部署准备2.1 MySQL 5.7 的生命周期与选型理由MySQL 5.7 官方已于 2023 年 10 月结束标准支持但它在存量市场里的占有率依然非常高。很多老业务使用的 SQL 写法、分区表策略、utf8mb4字符集行为都和 8.0 存在差异升级不是简单替换镜像就能搞定的事。对于还在用 5.7 的项目我建议直接使用官方镜像的5.7.44版本标签。这是 5.7 系列的最终维护版本包含了多年积累的 Bug 修复和安全补丁比使用最新浮动的5.7标签更可控。5.7这种浮动标签会在小版本间自动切换生产环境最忌讳不明不白的版本漂移。如果你在选型阶段还没锁定版本那么我多说一句新项目优先考虑 MySQL 8.0。8.0 的窗口函数、通用表表达式CTE、隐形主键、caching_sha2_password认证插件都是 5.7 不具备的能力。选择 5.7 的唯一理由应该是兼容存量业务而不是贪图旧版本的稳定——旧版本不会一直有安全更新。2.2 amd64 和 arm64 的镜像适配问题这个问题在热搜词里出现频率很高说明大家实际部署时真的遇到麻烦了。Docker Hub 上的 MySQL 官方镜像通过 manifest list 支持多架构你在 x86_64 服务器上执行docker pull mysql:5.7.44Docker 会自动拉取 linux/amd64 版本在 Apple Silicon Mac 或 ARM 云服务器比如华为鲲鹏、AWS Graviton 实例上执行同样的命令会拉取 linux/arm64 版本。这是 Docker 默认行为一般不需要手动干预。真正出问题的是离线环境。如果你需要把镜像从一台机器搬到另一台就会遇到docker save导出的 tar 包与目标机器架构不匹配的问题。拿在 x86 机器上docker save出的镜像包直接传到 ARM 服务器上docker load虽然能加载成功但运行时 Docker 会尝试用模拟器执行 x86 指令性能下降严重有时候干脆无法启动。这就是热搜里“mysql:5.7 amd64 docker save tar包 下载”这个 query 背后折射出的场景。我的建议是离线部署务必在相同架构的机器上拉取并导出镜像。如果你的部署目标是 ARM 服务器那就找一台同架构的设备甚至可以是本地的树莓派执行以下命令docker pull mysql:5.7.44 docker save mysql:5.7.44 -o mysql-5.7.44-arm64.tar然后把 tar 包传到目标服务器docker load -i mysql-5.7.44-arm64.tar如果想确认当前目标机器架构可以执行docker info --format {{.Architecture}}另外提醒一点尽量指定架构命名 tar 包如mysql-5.7.44-amd64.tar避免时间久了分不清哪个包对应哪台机器。我在项目里吃过这个亏一个全部命名为mysql-backup.tar的目录半年后根本不知道哪个能用。2.3 镜像下载缓慢的替代方案国内网络拉取 Docker Hub 镜像经常超时解决办法除了配置镜像加速源之外还有一个适合生产环境的思路找一台网络稳定、带宽充足的机器提前docker pull好镜像再用docker save打成 tar 包通过内网传输到目标服务器docker load。这个过程同时解决了多个问题一是目标服务器可以不用配置外网访问提升安全等级二是版本完全锁定部署到多台机器时镜像内容完全一致。具体命令如下以 amd64 环境为例# 下载镜像 docker pull mysql:5.7.44 # 导出为 tar 包 docker save mysql:5.7.44 -o mysql-5.7.44.tar # 传到内网服务器后 docker load -i mysql-5.7.44.tardocker save导出的是包含镜像所有层的完整归档docker load加载后镜像标签、历史层全部保留和直接docker pull得到的效果没有差异。但因为当前登录的 Docker 账号信息auth不会被保存进 tar 包所以加载后直接用docker run即可不需要额外登录。3. 生产级 Compose 文件设计与关键参数解析3.1 完整配置原稿这一节直接给出我目前在用的docker-compose.yml完整内容然后逐块解析。这个配置已经在生产环境跑了半年多服务稳定各项参数经过实际负载校验。version: 3.8 services: mysql: image: mysql:5.7.44 container_name: mysql57 restart: always environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: YourStrongRootPass2024 MYSQL_DATABASE: appdb MYSQL_USER: appuser MYSQL_PASSWORD: AppUserPass2024 command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-authentication-pluginmysql_native_password - --max_connections300 - --innodb_buffer_pool_size1G - --slow_query_log1 - --slow_query_log_file/var/log/mysql/slow.log - --long_query_time1 ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql - ./conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro - ./logs:/var/log/mysql - ./backup:/backup networks: - app_network healthcheck: test: [CMD, mysqladmin, ping, -h, 127.0.0.1, -u, root, -p$$MYSQL_ROOT_PASSWORD] interval: 10s timeout: 5s retries: 5 start_period: 30s ulimits: nofile: soft: 65535 hard: 65535 deploy: resources: limits: cpus: 4 memory: 4G reservations: memory: 1G volumes: mysql_data: networks: app_network: driver: bridge3.2 数据目录与持久化挂载volumes: - mysql_data:/var/lib/mysql这是整个配置里最重要的一行也是防止“容器重启数据全没了”的关键。mysql_data是 Docker 管理的命名卷数据存储在宿主机的 Docker 数据目录通常是/var/lib/docker/volumes/rootless 模式则在用户目录下由 Docker 统一管理和备份。为什么不直接挂宿主机目录比如/opt/mysql/data:/var/lib/mysql两个方案各有优劣。命名卷的优点是 Docker 自动管理目录权限创建卷时初始权限由镜像第一次启动时的进程决定基本不会出现权限错乱宿主机目录挂载则需要你自己chown到 MySQL 进程的用户官方镜像内用户 UID 是 999否则容器会因为没有写入权限而启动失败。对于新手我更推荐命名卷。但命名卷有个不便之处数据散落在 Docker 存储目录里直接去宿主机找数据会比较绕。要备份时通常得用docker run --rm -v mysql_data:/var/lib/mysql起一个临时容器来打包。这不算大问题后面会给出完整的备份方案。3.3 环境变量与初始化行为environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: YourStrongRootPass2024 MYSQL_DATABASE: appdb MYSQL_USER: appuser MYSQL_PASSWORD: AppUserPass2024MySQL 官方镜像有一个“首次启动初始化”机制如果数据目录为空镜像会在容器第一次启动时执行初始化脚本。这个脚本会读取环境变量完成以下动作用MYSQL_ROOT_PASSWORD设置 root 用户的密码用MYSQL_DATABASE创建一个新数据库用MYSQL_USER和MYSQL_PASSWORD创建新用户并授予该MYSQL_DATABASE的全部权限这里有一个生产环境必须注意的关键点这些初始化逻辑只在数据目录为空时生效。如果 MySQL 数据已经初始化过你再修改环境变量里的密码不会对已有用户产生任何影响。很多人在这里踩坑改完MYSQL_ROOT_PASSWORD重启容器发现 root 密码没变就以为配置没生效。生产环境初始化 root 密码时我还建议额外执行一次ALTER USER重置确认确保密码策略符合内控要求。下面这条命令可以放到首次部署后的验证阶段执行docker exec -it mysql57 mysql -uroot -pALTER USER rootlocalhost IDENTIFIED BY YourFinalRootPass2024; FLUSH PRIVILEGES;另外生产环境不建议只用环境变量管理密码。更稳妥的做法是用.env文件配合 Compose 的变量替换或者使用 Docker Secrets。这里为了示例直观我直接写在 yaml 里但你在生产环境应该将docker-compose.yml和密码管理分离开。3.4 自定义my.cnf参数挂载volumes: - ./conf/my.cnf:/etc/mysql/conf.d/my.cnf:roMySQL 官方镜像启动时会加载/etc/mysql/my.cnf而该文件默认会 include/etc/mysql/conf.d/下的所有.cnf文件。把自定义配置挂载到这个目录是官方支持且推荐的扩展方式。我在生产环境my.cnf里维护的核心参数如下[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci max_connections 300 max_connect_errors 100000 innodb_buffer_pool_size 1G innodb_flush_log_at_trx_commit 1 innodb_log_file_size 256M sync_binlog 1 slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 1几个关键参数的取舍逻辑说一下innodb_buffer_pool_size是最重要的 InnoDB 性能参数用于缓存表数据和索引。经验法则是设置为物理内存的 60%-70%但不能超过容器内存限制。我这里的 1G 对应容器内存限制 4G属于保守值。如果你把容器内存限制在 2G这个值还保持 1G 就不太合适了。innodb_flush_log_at_trx_commit1保证每次事务提交都把日志刷入磁盘牺牲少量性能换取数据不丢失。如果你的业务对性能要求极高、且能容忍崩溃时丢失一点点最近事务可以调整为 2但生产库我不建议改。sync_binlog1同理保证二进制日志实时同步到磁盘这是 binlog 方式备份和主从复制的一致性和可靠性基础。max_connect_errors默认是 100连接失败次数超过这个值服务器会封禁客户端 IP。排查故障时经常碰到“为什么突然连不上了”的问题很多就是短时间内反复用错密码触发这个限制。建议生产环境调大一些我设了 100000实际效果是减少一类无谓的故障场景。慢查询日志参数按需保留long_query_time1表示超过 1 秒的 SQL 都会被记录下来。这个参数在线上排查慢 SQL 时价值巨大建议所有人无论业务大小都开启。挂载只读模式:ro是我特意加的。配置目录对容器只读避免运行中误改配置也避免容器写入造成宿主目录污染。改配置的正确姿势是宿主机修改my.cnf然后docker compose restart mysql让新配置生效。3.5 网络模式与端口映射ports: - 3306:3306 networks: - app_network端口映射3306:3306把容器的 3306 端口绑定到宿主机所有网卡的 3306 端口。这里有两个注意事项第一如果宿主机已经装过别的 MySQL或者有别的服务占用 3306启动时会报port is already allocated。那就需要改成33061:3306这类不冲突的映射客户端连接时连宿主机的 33061 端口即可。第二生产环境只对需要访问数据库的服务开放端口即可。如果 MySQL 只被同机的 Docker 服务访问其实可以不映射端口只通过自定义网络app_network互访这样可以避免数据库暴露到外部网络。我因为业务需要远程连接管理工具如 Navicat、DataGrip所以保留了端口映射但如有条件建议把绑定 IP 一起显式声明ports: - 127.0.0.1:3306:3306这样只有本机可以访问 3306 端口外部需通过 SSH 隧道或跳板机访问安全性高很多。3.6 健康检查、资源限制与 ulimithealthcheck: test: [CMD, mysqladmin, ping, -h, 127.0.0.1, -u, root, -p$$MYSQL_ROOT_PASSWORD] interval: 10s timeout: 5s retries: 5 start_period: 30s健康检查是我在过去一年里养成的习惯。没有健康检查时容器进程只要没崩溃就算 running但这不代表数据库已经 ready——初始化完成前你连上去会报ERROR 2002 (HY000): Cant connect to local MySQL server through socket。健康检查会用mysqladmin ping轮询直到返回mysqld is alive才标记为 healthy。这里的$$MYSQL_ROOT_PASSWORD是 Compose 的转义写法表示把MYSQL_ROOT_PASSWORD这个容器环境变量传给健康检查命令执行。如果直接写$MYSQL_ROOT_PASSWORDCompose 会在宿主机层面尝试展开这个变量导致密码为空健康检查永远失败。这个小坑非常隐蔽好几回看到别人配置了 healthcheck 却一直 unhealthy排查半天最后发现是这个原因。资源限制方面deploy: resources: limits: cpus: 4 memory: 4G reservations: memory: 1Gdeploy块在docker composeV2 插件下可以直接生效docker-composeV1 独立版可能部分忽略。limits.memory强制容器最大使用 4G 内存防止宿主内存被打爆reservations.memory表示容器至少预留 1G。ulimits.nofile设置文件描述符上限为 65535。MySQL 在连接数高、表数量多时文件描述符消耗非常快Linux 默认的 1024 远远不够不调高会出现too many open files错误这个错误在容器日志里表现很隐晦大多数人第一反应是查连接数而不是文件描述符。4. 实操全过程从创建目录到验证连接4.1 部署前的目录结构与配置文件准备我习惯把整个 MySQL 部署目录规划成如下结构所有东西都在一个项目目录下方便 git 管理和备份/opt/mysql/ ├── docker-compose.yml ├── .env ├── conf/ │ └── my.cnf ├── logs/ ├── backup/ └── data/先创建目录并准备配置文件sudo mkdir -p /opt/mysql/{conf,logs,backup} cd /opt/mysql再把上面给出的docker-compose.yml写入文件。注意.env和logs、backup使用宿主机目录挂载data交给 Docker 命名卷管理所以下面的data/目录其实不是必须的只是我的习惯。你也可以直接用./data:/var/lib/mysql挂载宿主机目录但记得初始化前要把权限处理好sudo chown -R 999:999 /opt/mysql/data这里 999 是官方 MySQL 镜像内部mysql用户的 UID。不改权限的话容器启动时会因为无法写入/var/lib/mysql而立即退出这是使用宿主机目录挂载最常见的失败原因。4.2 启动自有编排服务并检查日志一切就绪后先做配置语法校验docker compose config这个命令会解析docker-compose.yml并输出最终的规范化配置。如果 yaml 语法有问题、缩进错乱或字段写错会在这里直接报出来不用等容器启动后才发现问题。确认无误后启动docker compose up -d-d表示后台运行。第一次启动时 Docker 会自动拉取镜像如果本地没有执行 MySQL 初始化脚本这个过程通常需要 10-30 秒取决于机器性能。启动后用以下命令查看运行状态docker compose ps docker compose logs -f mysqllogs输出中如果看到[Entrypoint] GENERATED ROOT PASSWORD说明这是首次启动初始化脚本正在运行。初次启动等待时间稍长和网络繁忙导致的连接拒绝都先别急着报错等它初始化完成再试。4.3 unknown command: docker compose 的根源与解法这条热搜词在部署相关搜索里常年霸榜其实是个环境问题不是配置问题。如果你在命令行执行docker compose出现docker: unknown command: compose for docker说明当前 Docker 版本太老或者没有安装 Compose V2 插件。docker compose子命令是 Docker Desktop 26 和 Ubuntu 官方docker.io包较新版本中自带的 Compose V2 能力老版本只有独立的docker-compose二进制。Ubuntu 上的标准解决方法是安装插件sudo apt update sudo apt install docker-compose-plugin安装后验证docker compose version如果不想装插件也可以用旧版独立命令docker-compose但我不推荐。独立版 Compose V1 已经停止维护很久了对较新的 Compose 文件格式比如version: 3.8、deploy.resources支持不完整生产环境还是统一到 V2 插件好。这个坑的麻烦之处在于一些旧文档里写docker-compose up -d另一些新文档里写docker compose up -d中间差一个横线命令行为完全不同报错提示又不够友好让人误以为是 compose 文件写错了。4.4 验证数据库连接与初始化数据启动完成后验证 root 密码是否生效docker exec -it mysql57 mysql -uroot -p输入密码后进入 MySQL 交互终端执行基本检查SELECT VERSION(); SHOW DATABASES; SELECT character_set_server, collation_server;正常情况下会看到 5.7.44、appdb数据库以及utf8mb4/utf8mb4_unicode_ci。然后验证appuser用户从外部连接是否正常。这里要注意一个 MySQL 用户 Host 匹配的问题镜像初始化脚本创建的appuser默认 Host 是%但如果你之前手动改过用户表或者通过其他方式创建用户时指定了localhost那么从宿主访问时可能匹配不上。验证方式mysql -h127.0.0.1 -P3306 -uappuser -pAppUserPass2024 -e SELECT 1如果返回1说明端口映射、用户权限、网络都正常。使用其他管理工具连接时需要确认 root 是否允许远程登录。出于安全考虑镜像默认 root 只能从localhost登录外部访问应该用appuser这类专用账号。如果非要让 root 远程可访问执行GRANT ALL PRIVILEGES ON *.* TO root% IDENTIFIED BY YourStrongRootPass2024 WITH GRANT OPTION; FLUSH PRIVILEGES;但这条操作在安全审计里基本都会被扣分能不用就不用。4.5 与 Nacos 3.x 等行业组件的联调落点部署 MySQL 通常是为了承接其他中间件和应用热搜词里频繁出现的“docker compose 部署 nacos 3.x”就是典型场景。Nacos 3.x 启动时会把自己的配置、服务实例、临时数据写入 MySQL以替代内嵌的 Derby。对接时有一个前置条件需要提前在 MySQL 里建一个库并导入 Nacos 的初始化脚本。实操步骤是CREATE DATABASE IF NOT EXISTS nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;然后把 Nacos 安装包conf/目录下的mysql-schema.sql3.x 可能是多个 schema 脚本导入这个库mysql -h127.0.0.1 -P3306 -unacos -p nacos_config mysql-schema.sql导入之后在 Nacos 的application.properties或环境变量中配置spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://mysql57:3306/nacos_config?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseSSLfalse db.user.0appuser db.password.0AppUserPass2024注意连接串里的主机名mysql57这是通过 Docker 自定义网络访问时使用的服务名前提是 Nacos 容器和 MySQL 容器在同一个networks下。如果你使用version: 3.8的网络配置容器间通信走 service 名即可不需要再用 IP。同样的思路也适用于 Redis、应用服务等。生产环境里所有中间件都统一用 Compose 编排在一个网络下管理成本大幅降低。5. 常见问题与排查技巧实录5.1 容器启动后马上退出的排查路径这是部署 MySQL 容器时遇到最多的故障类型而且原因五花八门。我整理了一份我实测过的排查顺序表现象可能原因排查/解决方式容器启动即退出docker logs无输出挂载目录权限不足检查宿主机数据目录 owner 是否为 UID 999或改用命名卷docker logs里报initialize specified but the data directory has files in it数据目录非空但未完成初始化清空数据目录重新初始化或用完整数据卷恢复报[ERROR] --initialize specified but the data directory has files in it宿主机目录里有隐藏文件如.lostfound确认目录是纯空必要时重新建目录端口冲突启动失败宿主已有进程占用 3306改用宿主映射端口如 33061docker compose up -d报no such file or directory挂载目录不存在提前创建并确认所有宿主机挂载路径存在这里特别强调.lostfound这个坑。在 ext4 文件系统上如果你挂载一个新建的宿主机目录到/var/lib/mysql内核可能在该目录下生成.lostfound隐藏目录MySQL 初始化脚本检测到数据目录非空就会拒绝启动。解决办法是不要直接挂空目录或者挂载前把目录格式化/清空。我个人最省心的做法还是回到命名卷。如果确实需要用宿主机目录方便备份那就在首次启动前确保目录完全为空然后给run或 compose 配置一个init容器来做初始化而不是让 MySQL 自己在已挂载目录里初始化。5.2 数据库连接失败的三层定位法遇到客户端连不上 MySQL我习惯于按网络、账号、配置三层顺序定位第一层是网络。在客户端机器上执行telnet 127.0.0.1 3306 # 或 nc -vz 127.0.0.1 3306连不上就去查 Docker 端口映射是否正常docker compose ps docker port mysql57端口没映射出来说明容器可能没有正常启动或端口被占。防火墙和云安全组也是常见原因Ubuntu 上用ufw status查一下云服务器还要看安全组策略是否放行了 3306。第二层是账号权限。确认你使用的用户名是否允许从客户端 IP 连接。MySQL 用户匹配是“用户名 Host”双条件你可以通过SELECT user, host FROM mysql.user;确认存在appuser%。如果只有appuserlocalhost外部连接必然失败。另外 MySQL 5.7 的mysql_native_password认证插件兼容性很好如果你在 8.0 上连接报认证插件不支持的错在 5.7.44 上基本不会遇到。第三层是配置。确认bind-address没有写成127.0.0.1。默认的bind-address是0.0.0.0官方镜像没有主动限制但如果你的my.cnf里有类似bind-address 127.0.0.1的配置容器外部就永远连不进来。这个配置安全感很强很多人为了安全加上的实际却挡掉了所有正常的远程访问。5.3 时区与字符集问题容器默认时区是 UTC这会让业务系统的时间偏差 8 小时。如果你在docker-compose.yml的environment里设置了TZAsia/ShanghaiMySQL 进程的SYSTEM时区就正常了但还需要确认数据库会话时区SELECT global.time_zone, session.time_zone;如果显示SYSTEM且系统时区已经是东八区那么正常使用没问题。如果业务代码里有特殊时区依赖还可以在my.cnf里显式设置default-time-zone 08:00字符集问题同样容易漏。5.7 默认字符集是latin1如果不指定建表时会用默认字符集后续很可能出现中文乱码。我在 compose 的command里强制--character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci就是从源头杜绝这个问题。已经落库的 latin1 数据迁移到 utf8mb4 会比较折腾所以首次启动时定好字符集远比事后补救重要。5.4 备份与恢复生产环境最后一道防线容器化部署带来的最大心理负担就是“数据是不是真的安全”。我的做法是同时保留两个备份维度逻辑备份和卷备份。逻辑备份用mysqldump适合常规定时备份和业务级恢复# 宿主机执行生成 SQL 文件到 backup 目录 docker exec mysql57 sh -c exec mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --routines --events --triggers /opt/mysql/backup/all-databases-$(date %F).sql--single-transaction对 InnoDB 表实现一致性快照不锁表适合线上备份--routines、--events、--triggers确保存储过程、事件、触发器也被导出。恢复时mysql -h127.0.0.1 -P3306 -uroot -p all-databases-2024-01-01.sql加入 crontab 后可以做到每天自动备份保留最近 7 天或者 30 天0 2 * * * cd /opt/mysql docker exec mysql57 sh -c exec mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --routines --events --triggers backup/all-databases-$(date \%F).sql find backup -name *.sql -mtime 30 -exec rm -f {} \;卷备份则用于整库快速恢复和灾难场景比如数据目录损坏docker run --rm --volumes-from mysql57 -v /opt/mysql/backup:/backup ubuntu tar czvf /backup/mysql-data-$(date %F).tar.gz /var/lib/mysql恢复时先把新容器停掉用同样的方式把 tar 包解压回数据卷docker run --rm --volumes-from mysql57 -v /opt/mysql/backup:/backup ubuntu tar xzvf /backup/mysql-data-2024-01-01.tar.gz -C /这样即使/var/lib/mysql数据目录整个损坏也能在十几分钟内恢复到备份时刻的状态。5.5 升级与迁移的注意事项最后说一下 MySQL 5.7 容器化之后要升级时怎么操作。容器镜像升级不等于简单替换image字段必须按这个流程走先做完整备份mysqldump 卷备份双保险停止业务写入确保备份的一致性记录当前my.cnf里所有非默认参数将image改为目标版本比如mysql:8.0.36启动新容器前用旧容器数据卷挂载到新镜像注意 8.0 会在首次启动时尝试升级数据字典过程不可逆务必确认备份完成启动后检查日志、连接数、慢查询日志确认无升级报错从 5.7 到 8.0 的最大障碍不是容器操作而是业务兼容性。caching_sha2_password认证插件会导致旧版 JDBC 驱动连接失败5.7 里允许的一些隐式 SQL 行为在 8.0 中会被严格模式直接拒绝。真要升级先在测试环境用真实业务跑一遍回归不要在生产直接换。6. 写在最后的一些经验这套方案我前前后后优化过好几轮最想强调的还是那句话容器化不等于虚拟化更不等于免运维。MySQL 作为核心数据存储持久化、备份、监控、升级预案一个都不能少。每次看到有人用容器跑 MySQL 却不挂数据卷或者启动后从来不备份我都替他们捏一把汗——生产故障的恢复速度往往取决于你平时做了多少准备。在实际操作中我个人的体会是先把docker-compose.yml当作代码来维护版本管理、变更评审、环境差异化都按软件工程规范来做而不是改完就扔在服务器上。配置集中管理之后无论你是部署一台还是十台都可以做到几分钟内拉起一套完全一致的数据库实例。这个价值在故障演练和扩容时会体现得非常明显。最后再叮嘱一句如果你还在比较 MySQL 5.7 和 8.0新业务直接上 8.0如果因存量业务必须留在 5.7就把这份 Compose 方案吃透它一定能在你的日常运维里帮上大忙。