ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker Compose 部署 MySQL 5.7 生产级指南:从镜像选型到备份恢复

Docker Compose 部署 MySQL 5.7 生产级指南:从镜像选型到备份恢复 接手过 MySQL 5.7 存量项目的朋友都有同感升级 8.0 的声音喊了好几年可真到生产环境5.7 依然是很多业务系统的底线版本。这个月我刚好用 Docker Compose 帮团队把一个老项目从裸机迁移到容器化部署整个过程踩了不少坑也沉淀了一套可以直接拿走的实践方案。这篇文章想把完整的生产级部署细节写清楚——镜像怎么选、配置怎么写、初始化怎么做、备份怎么兜底以及上线后最容易翻车的几个问题怎么排查。内容不只适合第一次用 Docker Compose 部署 MySQL 的新手也适合那些正在做存量 MySQL 5.7 容器化迁移、需要一套可靠方案的运维同学参考。1. 为什么我坚持把旧业务继续放在 MySQL 5.7 上1.1 存量业务的兼容性与升级阻力先说结论MySQL 5.7 在 2023 年 10 月结束了官方生命周期支持但官方停止维护和业务准备完毕完全是两回事。我经手的几个老项目代码里还残留着一堆旧版 SQL 写法比如隐式类型转换、旧的排序规则依赖、非严格模式的隐式行为。这些在 5.7 里跑得稳稳当当的业务一旦升到 8.0 就可能因为默认字符集变化、认证插件切换、SQL 模式收紧而直接报错。最典型的例子是mysql_native_password认证插件。5.7 默认用它8.0 默认换成了caching_sha2_password。老项目的 JDBC 驱动如果停留在 5.x连接 8.0 会直接抛认证相关异常。虽然可以手动把 8.0 的账号改回mysql_native_password但既然要改底层的认证那还不如先把容器化做扎实等业务代码的依赖升级完整后再动数据库大版本。所以我的建议很明确如果业务代码没有为 8.0 做兼容性整改的预算和时间生产环境继续用 5.7 是合理选择。我们要解决的不是换版本而是让 5.7 在新基础设施上跑得更稳、更容易运维。1.2 5.7、8.0 与 MariaDB 的选型权衡有人会问既然 5.7 停止维护为什么不直接换 MariaDBMariaDB 确实是兼容 MySQL 协议的替代品很多场景下迁移成本低但低不代表零。存储引擎细节、GTID 行为、备份恢复工具链都有差异尤其是老项目如果有深度依赖 InnoDB 信息 schema 查询、特定 binlog 格式解析的定制监控系统迁移到 MariaDB 需要单独排期测试。8.0 的优势很明显窗口函数、CTE、更好的 optimizer、数据字典独立、undo 表空间拆分、redo log 容量自适应。这些都是长期红利。但对一个稳定运行了两三年的业务系统来说这些新特性并不能立刻转化为业务价值反而每次大版本升级都伴随回归测试、慢查询波动分析和中间件适配验证。我的选型判断标准很朴素团队当前最痛的是部署不能复现、配置散落在各台机器、备份机制靠人肉这属于基础设施问题靠 Docker Compose 就能解决跟换数据库大版本没关系。先把部署形态标准化再谈版本升级风险更可控。1.3 Docker Compose 在这个场景里的不可替代性裸机部署 MySQL 5.7 我做过太多次要手动下载安装包、处理 systemd 服务文件、分散管理配置文件、环境变量靠 export。最头疼的是每台机器都有细微差异——A 机器字符集配了 utf8mb4B 机器忘了配导数据时乱码才被发现。Kubernetes 当然可以做编排但对一个只有几个 MySQL 实例的中小团队来说引入 K8s 集群本身就是巨大运维负担。Docker Compose 正好卡在中间用 YAML 把镜像、端口、卷、环境变量、健康检查、重启策略一次性声明清楚一份文件可以同时在开发机、测试机、生产服务器上复现完全一致的运行环境。而且 docker compose 的管理成本比 K8s 低一个量级配合docker compose pull和docker compose up -d两个命令就能完成大部分日常操作。2. 部署前的四件小事先定镜像、目录、时区、字符集2.1 镜像版本怎么选顺手解决 ARM64 的坑镜像选择上我强烈建议把 tag 锁定到具体的小版本不要用mysql:5.7这种浮动 tag。mysql:5.7会跟随官方更新你永远不知道哪一天docker compose pull之后底层镜像变了行为也跟着变。生产环境最怕不可预期的变化所以要用mysql:5.7.44这样的精确版本。5.7.44 也是 5.7 系列的最终版本以后官方不会再发新补丁行为完全冻结适合长期稳定运行。这里有个容易踩的坑ARM64 环境拉取 MySQL 5.7 官方镜像。Docker Hub 上的mysql:5.7对 arm64 架构支持一直不理想在 Apple Silicon Mac 或 ARM 服务器上直接 run 可能报no matching manifest或运行异常。我建议如果必须在 ARM64 上运行 MySQL 5.7优先找可信的第三方多架构镜像或者用官方镜像时确认你的 registry mirror 是否支持架构转发。还有一种可行做法是在 ARM64 宿主机上用 MySQL 官方 apt 源直接装 5.7然后用 docker compose 管理周边的监控和备份工具但这样 MySQL 本身不在容器里备份脚本、端口管理还是要单独处理反而更繁琐。我实测下来如果团队没有强制的 ARM 成本诉求MySQL 5.7 这类核心数据库尽量跑在 x86_64 上稳定性和生态兼容性都更好。2.2 数据目录与日志目录的宿主机规划生产级部署最核心的原则是数据必须落在宿主机或独立卷上绝不能放在容器可写层里。我见过很多开发者图省事直接 run 一个 MySQL 容器不挂卷容器一删除数据全没了这种教训一次就够痛。我的标准规划是建一个专门的目录结构/opt/mysql57/ ├── conf/ # my.cnf 和初始化脚本 │ ├── my.cnf │ └── init/ ├── data/ # MySQL 数据文件 ├── logs/ # error log、slow log └── backup/ # 宿主机侧备份目录data目录用 bind mount 直接挂到/var/lib/mysqllogs挂到/var/log/mysqlconf目录挂载配置文件。这样日志直接落在宿主机排查问题时可以用tail、grep直接看不需要进容器绕一圈。bind mount 的权限要注意MySQL 容器内 mysql 用户的 UID 通常是 999所以宿主机上的 data 目录要chown -R 999:999否则容器启动时会因为无法写入数据目录而退出。2.3 时区与字符集必须在初始化前定死时区问题非常隐蔽。MySQL 5.7 里NOW()、CURRENT_TIMESTAMP这类函数的行为取决于系统时区设置。很多国内团队要的是东八区时间如果容器默认 UTC写进数据库的时间会比实际时间慢 8 小时业务侧查出来又用本地时区解析就会出现账上时间和对不上的诡异问题。我的做法是两个维度同时设容器环境变量TZAsia/Shanghai以及 MySQL 参数default-time-zone08:00。注意default-time-zone需要重启 MySQL 才生效所以在 compose 文件的 command 里加上最省事。字符集同理。MySQL 5.7 默认字符集是latin1不改成 utf8mb4 的话存 emoji、中文生僻字会出现乱码或报 Incorrect string value 错误。初始化之前就要在 my.cnf 或 command 里指定character-set-server utf8mb4 collation-server utf8mb4_general_ciutf8mb4_general_ci是 5.7 时代的主流排序规则性能比utf8mb4_unicode_ci略好最重要的是和老业务保持兼容。如果老库用的是utf8mb4_unicode_ci那就继续用不要混否则 join 的时候排序规则冲突。2.4 端口映射方案暴露还是内部访问MySQL 的 3306 端口在容器化部署里有不同的暴露策略。开发环境图省事直接ports: 3306:3306映射到宿主机所有网卡但生产环境我建议做两层控制。第一层只绑定内网网卡。10.0.0.10:3306:3306这种方式让 MySQL 只在指定 IP 上监听外网完全不可达。应用服务器通过内网访问运维通过堡垒机跳板访问。第二层如果 MySQL 只给 Docker 网络内的其他容器用可以完全不映射端口利用 compose 里的服务名mysql直接内网访问。我通常采用折中方案映射到内网 IP方便宿主机上的备份脚本、监控 Agent 连接同时避免端口裸奔到公网。3. 手写一份生产级 docker-compose.yml逐行拆解3.1 我用到的完整编排文件直接给出我这套实践中使用的最终版 docker-compose.yml后面再逐项拆解为什么这么配services: mysql: image: mysql:5.7.44 container_name: mysql57 restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: appdb MYSQL_USER: appuser MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD} TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_general_ci - --max_connections500 - --default-time-zone08:00 ports: - 10.0.0.10:3306:3306 volumes: - /opt/mysql57/data:/var/lib/mysql - /opt/mysql57/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro - /opt/mysql57/logs:/var/log/mysql - /opt/mysql57/conf/init:/docker-entrypoint-initdb.d:ro healthcheck: test: [CMD-SHELL, mysqladmin ping -h127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent] interval: 10s timeout: 5s retries: 12 start_period: 30s deploy: resources: limits: memory: 3G reservations: memory: 1G networks: - backend networks: backend: driver: bridge3.2 容易理解错的环境变量与 command 参数MYSQL_ROOT_PASSWORD、MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD这四个环境变量是官方镜像在首次初始化时才会生效的入口。注意首次这两个字——如果data目录里已经存在初始化过的数据这些环境变量就完全不会再生效改密码必须走ALTER USER或mysqladmin。所以我强烈建议用.env文件管理这些变量而不是直接写死在 compose 文件里# .env MYSQL_ROOT_PASSWORD这里改成强密码 MYSQL_USER_PASSWORD这里改成业务密码command里追加的参数其实是 mysqld 启动参数优先级高于 my.cnf。这里有一个非常容易踩的坑如果 my.cnf 里也写了max_connections500command 里也写了command 会覆盖文件里的值。为了避免这个参数到底生效没有的困惑我建议把字符集、时区这类必须全局一致的参数放在 command把 InnoDB、连接数、慢查询这类需要经常调优的参数放在 my.cnf。两边不要重复配置相同参数给自己留一个清晰的判断依据。3.3 健康检查为何比 depends_on 更可靠很多新手写依赖容器时喜欢用depends_on: - mysql以为这样应用容器就会等 MySQL 就绪。实际上depends_on只控制容器启动顺序不保证 MySQL 已经完成初始化进入可接受连接的状态。MySQL 启动到能接受连接通常有十几秒特别是首次初始化数据目录时要建系统表时间更长。这时候应用容器抢先连接就会报连接拒绝然后应用崩溃退出。我的做法是两层组合healthcheck 定义 MySQL 的就绪探针应用侧再用depends_on加上condition: service_healthy。上面配置里我用的探针命令是mysqladmin ping还需要加--silent参数否则 ping 成功时会输出mysqld is alive探针判断的逻辑就会混乱。注意转义$在 compose 的 test 里写${MYSQL_ROOT_PASSWORD}会被 compose 先解析所以要写成$$MYSQL_ROOT_PASSWORD让探针在容器内部再取环境变量的值。start_period: 30s也很关键。容器刚启动那 30 秒内健康检查失败不计入 retries避免首次启动初始化阶段被连续探测误伤直接标记 unhealthy。3.4 资源限制与重启策略生产环境的容器不能无限吃资源。MySQL 是内存大户一个失控的查询或连接风暴能把宿主机内存打满。compose 的deploy.resources在 Docker Compose v2 里是支持的底层映射到 cgroup 限制我设了 memory limit 3Greservation 1G。这样宿主机即使有其他容器MySQL 也不会把整机内存吞噬。restart: always配合 Docker daemon 自启动策略让 MySQL 在容器崩溃、宿主机重启后都能自动恢复。但要注意restart: always解决的是进程退出不解决数据损坏。真正让数据安全的还是卷挂载和备份这一点后面展开说。4. 初始化账号与权限生产环境别把 root 裸奔4.1 通过环境变量初始化的正确姿势官方镜像的/docker-entrypoint-initdb.d机制非常实用目录下的.sh、.sql、.sql.gz文件会在数据目录首次初始化时按文件名顺序执行。我一般在这里放三个脚本/opt/mysql57/conf/init/ ├── 01_create_tables.sql ├── 02_seed_data.sql └── 03_create_readonly_user.sql注意文件名的数字前缀就是执行顺序这个顺序设计比一张大而全的初始化脚本更好维护。01 建表、02 导基础数据、03 建只读账号每一段职责单一。还有一个细节初始化脚本只在数据目录为空的首次启动时执行。如果中途改脚本必须删除/opt/mysql57/data目录内容重新初始化或者手动在运行中的实例里执行变更。生产环境中数据目录已经有业务数据后千万不要图方便去删目录重来要么走正常迁移流程要么手动执行 SQL。4.2 业务账号最小权限设计用MYSQL_USER创建的默认账号拥有的是MYSQL_DATABASE库的全部权限这个权限范围在生产环境偏大。我更建议通过初始化 SQL 手动创建精确权限的账号例如CREATE USER appuser10.0.0.% IDENTIFIED BY 密码; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO appuser10.0.0.%; GRANT SELECT ON mysql.proc TO appuser10.0.0.%; FLUSH PRIVILEGES;只读账号用于监控和数据分析CREATE USER readonly10.0.0.% IDENTIFIED BY 另一套密码; GRANT SELECT ON appdb.* TO readonly10.0.0.%; FLUSH PRIVILEGES;appuser10.0.0.%表示只允许来自 10.0.0.0/24 网段的连接。很多安全事件都始于账号权限过大、来源 IP 不限这里宁可配严一点后面业务确实需要再扩。root 账号我建议只在初始化时使用日常运维用另一个有全部权限的管理员账号避免 root 密码散落在各个脚本和监控配置里。如果一定要用 root 做备份也要把 root 密码放在只有运维能读的.env或密钥管理系统中别写在 docker-compose.yml 里传到代码仓库。4.3 初始化脚本的原子性docker-entrypoint-initdb.d 的执行是一次性且顺序执行的但要注意如果中间的某个 SQL 脚本执行失败整个数据库初始化会中断容器启动失败。这其实是好事相当于给初始化过程加了一个失败即停止的约束避免一半数据进去了、另一半没进去然后业务在一个残缺的库上跑起来。我遇到过一次比较尴尬的情况初始化脚本里有中文注释文件编码是 GBK结果执行时报语法错误。这属于典型的编码问题提醒大家初始化脚本一律保存为 UTF-8文件头不要带 BOM注释行里不要出现额外符号。另外init目录挂在只读模式下很必要防止容器内部进程篡改初始化脚本- /opt/mysql57/conf/init:/docker-entrypoint-initdb.d:ro5. 数据安全兜底备份、恢复、日志轮转一个都不能少5.1 基于 docker exec 的全量备份脚本容器化部署最大的便利之一是备份命令可以用docker exec直接执行不需要关心 mysqldump 装在哪台宿主机上。我用的是这个备份脚本#!/bin/bash BACKUP_BASE/opt/mysql57/backup DATE$(date %Y%m%d_%H%M%S) KEEP_DAYS7 mkdir -p $BACKUP_BASE docker exec mysql57 sh -c exec mysqldump \ --single-transaction \ --quick \ --routines \ --triggers \ -uroot -p$MYSQL_ROOT_PASSWORD \ --all-databases $BACKUP_BASE/full_$DATE.sql if [ $? -eq 0 ]; then gzip $BACKUP_BASE/full_$DATE.sql find $BACKUP_BASE -type f -name *.sql.gz -mtime $KEEP_DAYS -delete echo [OK] backup: $DATE else echo [FAIL] backup at $DATE exit 1 fi这个脚本有几个关键点--single-transaction参数在 InnoDB 下利用事务一致性快照做非锁表备份生产环境可以安全地在线备份而不阻塞业务写入--routines和--triggers必须带上否则备份文件缺少存储过程、函数、触发器恢复后的库功能不完整备份数据直接打到宿主机目录再配合定时任务每天执行0 2 * * * /opt/mysql57/scripts/backup.sh /var/log/mysql_backup.log 21选择凌晨 2 点是因为业务低峰期减少对主库的压力。如果备份文件很大建议备份盘和数据盘分开存放避免同一块磁盘故障把数据和备份一起带走。5.2 恢复演练备份不回放等于没有备份备份只是完成了第一步真正决定容灾能力的是恢复演练。我坚持每个季度在测试环境做一次完整恢复流程如下# 创建临时新容器挂一个全新的数据目录 docker run --rm --name mysql_restore \ -e MYSQL_ROOT_PASSWORD临时密码 \ -v /tmp/mysql_restore_data:/var/lib/mysql \ -v /opt/mysql57/backup:/backup:ro \ mysql:5.7.44 # 在容器内执行恢复 gzip -dc /backup/full_最新时间.sql.gz | docker exec -i mysql_restore mysql -uroot -p临时密码恢复完成后检查三个东西表数量是否一致、关键业务表的行数是否匹配、CHECK TABLE是否全部通过。只要有一个季度没演练真到故障恢复时大概率手忙脚乱所以我把它当成例行工作而不是可选动作。另一个容易被忽略的点如果表结构里包含外键约束恢复到空库时要注意SET FOREIGN_KEY_CHECKS0。mysqldump 默认会在导出文件里带上这个控制语句但如果你的备份脚本写得太简单没有包含相关语句恢复大量表时外键顺序很容易报错。5.3 容器日志与 binlog 的清理策略MySQL 容器跑久了有两个地方会不知不觉占满磁盘。第一是 Docker 的 json-file 日志驱动默认会无限累积容器标准输出。MySQL 的 error log 走的是文件不是标准输出但mysqladmin ping这类健康检查和 mysqld 偶尔往 stdout 打的日志仍然会积累。我建议在 compose 文件日志驱动的地方限制logging: driver: json-file options: max-size: 100m max-file: 3第二是 MySQL 的 binlog。如果开了 binlog 用于主从复制或时间点恢复binlog 会持续增长。在 my.cnf 里设置expire_logs_days 75.7 里这个参数还叫这个名字8.0 才改成binlog_expire_logs_seconds自动清理 7 天前的二进制日志。binlog 和备份是配套的全量备份加最近 7 天 binlog理论上可以恢复到任意时间点。6. my.cnf 调优清单5.7 值得单独设置的参数6.1 InnoDB 缓冲池与日志文件5.7 默认配置是为小内存机器准备的生产环境必须显式调整。最核心的是innodb_buffer_pool_size它决定 InnoDB 缓存索引和数据页的内存大小。经验公式是物理内存的 50% 到 70%如果你的 MySQL 容器内存上限是 3G缓冲池设 1.5G 到 2G别贪心留出操作系统和连接线程的空间[mysqld] innodb_buffer_pool_size 2G innodb_log_file_size 512M innodb_flush_log_at_trx_commit 1 innodb_file_per_table 1innodb_log_file_size默认只有 48M对于写入频繁的业务来说太小会导致 redo log 频繁切换影响写入性能。5.7 改这个参数不能在线操作必须重启 MySQL所以规划时就要定好。innodb_flush_log_at_trx_commit 1是 ACID 合规的配置每个事务提交都要刷盘性能有一定损耗但换来的是崩溃时数据不丢失。如果业务可以接受最多丢失 1 秒事务比如部分日志类业务可以折中设置成 2性能提升明显。6.2 连接数、超时与线程缓存连接管理是另一个生产环境重点。默认max_connections 151对于容器部署加上应用连接池的场景明显不够。我一般设 500但这个值不是越大越好——每个连接都要占用线程栈内存连接数失控会把 MySQL 拖垮。配合max_connect_errors设置为 1000避免网络抖动导致连接异常累积后Host is blocked的问题。超时参数容易被忽略。wait_timeout和interactive_timeout默认 8 小时如果应用连接池配置不当MySQL 侧的 sleep 连接会堆积占用连接数和内存。我一般维持 288008 小时但在应用层同步配置连接池的空闲回收策略比如 Druid 或 HikariCP 的空闲超时控制在 60 秒让连接池自己先淘汰无用连接。还有一个我实测有效的参数是innodb_buffer_pool_instances。缓冲池大于 1G 时多实例可以降低并发访问的锁竞争。5.7 中默认会自动根据大小调节一般不强制指定也行但如果你的业务并发写压力大可以显式设为 4 或 8。6.3 慢查询与监控参数不监控慢查询的生产库等于盲人开车。我习惯在 my.cnf 里开三件套slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 2long_query_time 2表示超过 2 秒的 SQL 会被记录下来。注意 5.7 里long_query_time的单位是秒最小精度 0.001不要写成毫秒数值。生产环境我还会加一行log_queries_not_using_indexes 1把没有走索引的查询也记下来这类查询往往是慢查询的潜伏者。监控工具方面如果不想上商业产品可以用mysqld_exporter配合 Prometheus 做指标采集再在 Grafana 里看趋势。容器化部署下建议把 exporter 单独跑一个容器和 MySQL 容器共享同一个 Docker 网络即可不用在 MySQL 容器里多装进程保持数据库容器的纯净性。7. 上线后最容易踩的四个坑排查链路记录7.1 error 2002 (HY000)socket 连不上本地 MySQL热词里出现频率很高的ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock我在容器部署中也遇过。大部分人的第一反应是MySQL 没启动但真实原因往往更隐蔽容器内部的 mysqld 默认把 socket 文件放在/var/run/mysqld/而宿主机上的 mysql 客户端尝试连接的是/tmp/mysql.sock。排查链路是这样先docker ps确认容器在跑再docker logs mysql57看有没有启动错误接着docker exec -it mysql57 mysql -uroot -p进容器内部连接如果容器内部能连、宿主机不能连基本确定就是 socket 路径不一致。解决办法是用 TCP 协议连接mysql -h127.0.0.1 -P3306 -uroot -p宿主机上没有装 mysql 客户端时也可以直接用容器内的客户端docker exec -it mysql57 mysql -uroot -p这个问题的根因在于混淆了宿主机视角和容器视角。MySQL 容器的 socket 文件对宿主机是不可见的除非你专门挂载 socket 目录所以宿主机上的各种客户端工具应该优先走 TCP而不是走 socket。7.2 Navicat 连接报 SSL 错误的处理热词里另一个高发问题是 Navicat 连接 MySQL 5.7 报 SSL 错误。5.7 默认开启了 SSL 支持且require_secure_transport默认关闭按理说普通连接不会强制走 SSL。但我在一个项目里遇到过本地 MySQL 服务端证书过期或客户端和服务端 SSL 版本协商不一致Navicat 报SSL connection error: protocol version mismatch。处理思路有两种。第一种在连接配置里把使用 SSL改为不加密或如果可用则使用。Navicat 的连接高级设置中有一项 Use SSL选 No 或者 Auto。第二种在启动参数里显式关闭 SSLcommand: - --skip-ssl不过我不推荐直接禁用 SSL。正确做法是生成新的自签名证书并挂载到容器里让 SSL 正常工作。生产环境数据库连接要走内网加密这是安全基线不该为了省事把 SSL 关掉。7.3 写入时间差 8 小时时区与连接参数即使 compose 里配了TZAsia/Shanghai仍然可能出现程序写入的时间在数据库里显示成 UTC 的情况。原因是 JVM 等应用运行时的时区、JDBC 连接串里的serverTimezone参数、MySQL 的time_zone变量是三个相互影响的变量。我排查过一次典型的差 8 小时问题应用的 JDBC 连接串写的是serverTimezoneAsia/Shanghai但 MySQL 的default-time-zone没设SYSTEM变量跟着容器系统时区走结果程序读取的时候又多转了一次时区时间就错了。根治方法是两边统一MySQL 侧default-time-zone08:00保证 SQL 函数返回值固定东八区。JDBC 侧连接串显式加serverTimezoneAsia/ShanghaiuseLegacyDatetimeCodefalse不要让驱动猜测。还有一种伪时区问题是表的列用了DateTime但应用使用本地时间格式化这种就要靠应用层统一规范跟数据库配置无关。7.4 容器被重建后数据消失的极端场景有一个场景非常容易误判为数据丢失执行docker compose down之后又执行docker compose up -d发现业务表全都没了只剩初始化的空库。原因是 compose 文件里的卷如果写的是匿名卷或者卷名没定义好down 的时候容器删除匿名卷会被连带清理数据就真的没了。正确做法是使用具名卷或 bind mount。我用的是 bind mount路径/opt/mysql57/data:/var/lib/mysql数据直接写在宿主机目录容器删了重建只要挂载路径不变数据就不会丢。另外docker compose down默认不会删除具名卷但如果你手多加了-v参数具名卷也会被删。记住生产环境任何删除命令都要先确认卷和备份的状态养成先备份再 down的操作习惯。回到我的实践感受MySQL 5.7 容器化部署这件事难的不是写一个能跑的 compose 文件而是把选型逻辑、初始化策略、备份恢复、参数调优和安全边界这些侧面全部考虑完整。把这套方案跑通之后数据库环境不一致部署不可复现备份靠运气这些问题基本就消失了。如果你正在做老项目的容器化迁移建议先用这台搭建好的实例跑一段时间观察慢查询和连接曲线再逐步把周边工具链exporter、备份脚本、告警规则补齐。最后一个小技巧把.env文件纳入密钥管理把 docker-compose.yml 和 my.cnf 纳入版本库这样任何一台新服务器拉下来代码、填好密钥就能在十分钟内复现一套一致的生产数据库环境。
返回列表