拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Docker部署MySQL 8.0:数据持久化与容器挂载实战解析

Docker部署MySQL 8.0:数据持久化与容器挂载实战解析

最近帮朋友梳理开发机上的数据库环境,光清理旧 MySQL 就折腾了大半天。一台机器上 5.7 和 8.0 的残留共存,rpm 装的、源码编译的、apt 装的混在一起,配置互相打架,启动报错查到最后居然是两个版本的初始化脚本冲突。后来我干脆把机器上所有 MySQL 清干净,用 Docker 重新装了一份 MySQL 8.0,并且按照“数据必须持久化”的原则做了目录挂载。这套环境稳定跑了两个月,中间经历过容器重建、服务器重启、镜像升级,数据一次都没丢。

这篇就把完整过程写成记录,从 Docker 环境准备、数据持久化原理,到实际部署、验证、排错和日常维护,一次讲清楚。文章适合刚接触 Docker、想用容器跑 MySQL 的开发者,也适合已经在用 Docker 但担心“容器一删数据就没了”的运维朋友。我会把每一步为什么要这么做、坑在哪里都交代明白,你跟着操作基本能一次跑通。

1. 为什么要把MySQL装进Docker:先想清楚再动手

1.1 传统安装方式到底痛在哪

早年装 MySQL,基本逃不开 rpm、apt 或者源码编译三条路。rpm 装起来看似简单,依赖关系却常常让人头大,装个 mysql-community-server 要带上 mysql-community-client、mysql-community-common、mysql-community-libs 一串,版本对不上就报冲突。用 apt 稍好一点,但 Ubuntu 和 CentOS 的包管理策略不同,软件源里的 MySQL 版本往往比较旧,想装 8.0 还要额外配官方 apt 源或 rpm 源。

真正麻烦的是卸载和升级。老项目里经常出现多个 MySQL 版本共存:配置文件散落在 /etc/my.cnf、/etc/mysql/、/usr/my.cnf 多个位置,数据目录也有 /var/lib/mysql、/opt/mysql、自定义路径之分。最惨的一次,我帮同事排查一台 CentOS 机器,发现 mysqld_safe 起不来,原因是 5.7 的初始化脚本被 8.0 的 RPM 包覆盖了一部分,两个版本的 systemd 服务文件同时在打架,前后修了三个小时。

相比之下,Docker 镜像把整个运行环境打包好了。拉一个官方 mysql 镜像,里面自带 MySQL 二进制、依赖库、默认配置和初始化逻辑。宿主机上只需要 Docker 运行时,不需要安装任何 MySQL 相关组件。升级版本时直接换镜像 tag,回滚时停掉新容器、用旧 tag 重新跑一个即可,整个系统的依赖冲突问题基本消失。

1.2 容器化带来的核心收益

用 Docker 跑 MySQL,最直接的收益是环境一致性。开发、测试、生产只要用同一个镜像版本,运行行为基本一致,“我本地是好的”这类争议会少很多。另一个收益是运维动作标准化:启动、停止、删除、重建都是 docker 命令,不依赖系统服务管理器的差异,在 Ubuntu 上和在 CentOS 上操作完全一致。

容器的资源隔离也让多实例部署变得很优雅。同一台机器上想同时跑 MySQL 5.7 和 8.0 做对比,传统方式要处理端口、目录、配置文件冲突,Docker 只需要映射两个不同的宿主机端口,数据目录分开挂载,几分钟就能搞定。开发环境需要临时开一个 MySQL 实例测数据迁移脚本,用完直接删掉,完全不影响主环境。

但这里有个关键前提:Docker 容器默认是“无状态”的。容器一旦被删除,容器内写入的数据全部消失。所以如果要用 Docker 跑 MySQL,数据持久化不是可选项,而是必选项。这也是这篇文章后面重点展开的内容。理解了这个前提,你才能真正安全地使用容器化数据库。

1.3 不是所有场景都适合容器化

尽管 Docker 跑 MySQL 在很多场景下很香,但我不建议无脑容器化所有数据库。如果你的业务是写密集型、对 IOPS 和延迟极其敏感的大型生产库,容器化会引入额外的存储驱动开销和网络代理开销,调优路径也比物理机复杂。如果公司已经有成熟的物理机数据库运维体系,监控、备份、高可用都绑定在系统服务上,强行迁移到容器反而增加了运维成本。

我的建议是:本地开发、测试环境、内部中小型业务系统,用 Docker 跑 MySQL 完全够用,配合持久化挂载和定时备份,可靠性完全能接受。生产环境要评估实际情况,至少要做好性能压测、备份恢复演练、监控接入这三件事再上线。不要因为“容器化很流行”就盲目迁移,数据库的稳比炫技重要得多。

2. 数据持久化:理解容器为什么一删就没

2.1 容器文件系统的读写机制

要理解持久化,先得搞明白容器的文件系统模型。Docker 镜像是一层一层只读的,比如 mysql:8.0.36 镜像,底层有操作系统基础层、MySQL 程序层、配置层等。容器运行时,Docker 在最上层叠加一个“可写层”,所有在容器内产生的文件改动都写在这个可写层里。

这个可写层和镜像层是松耦合的。容器正常运行时一切正常,但一旦执行 docker rm 删除容器,可写层连同里面所有数据会一起销毁。你可以把这种机制理解成在咖啡馆的草稿纸上写东西:纸是咖啡馆提供的,你离开后这张纸会被收走,内容也随之消失;想长久保存就得把内容誊写到自己的笔记本上。容器的“笔记本”就是数据卷和挂载目录。

docker commit 可以把当前容器的可写层固化成新镜像,有人用它来“保存”容器状态,但这对数据库来说是个坏习惯。MySQL 数据文件状态复杂,commit 出来的镜像层既不能保证数据一致性,又会在镜像仓库里堆积大量垃圾层,更不适合做备份。真正可靠的方案只有一个:把数据写到容器外部的持久化存储上。

2.2 数据卷和绑定挂载怎么选

Docker 提供两种持久化方式:数据卷(volume)和绑定挂载(bind mount)。数据卷由 Docker 管理,数据存放在 /var/lib/docker/volumes/ 目录(Linux),Windows 和 Mac 则由 Docker Desktop 管理。绑定挂载直接把宿主机某个目录或文件挂进容器,路径由你自己指定,数据也存在指定的宿主机位置。

对比项数据卷(volume)绑定挂载(bind mount)
数据位置Docker 管理目录宿主机自定义路径
备份/迁移需要用 docker run --volumes-from 或拷贝卷目录直接操作宿主机文件夹
宿主机直接查看数据库文件需要进 Docker 目录,跨平台路径不一致直接 ls 就能看到
权限控制Docker 统一处理,跨平台较友好需要手动处理 用户/权限
适合场景compose 编排、多平台复用想直接放配置文件、导出备份到宿主机

我自己的使用习惯是:配置目录和日志目录用绑定挂载,因为 my.cnf 文件放在宿主机上方便直接编辑和版本管理,日志文件也想用 logrotate 统一处理;数据目录在单机部署时也用绑定挂载,路径透明直观,出问题定位方便。在 docker-compose 里用命名卷也很常见,但如果你在 Windows/Mac 和 Linux 之间切换环境,named volume 的跨平台性更好。两者没有绝对优劣,选一个顺手的方式,关键在于明白数据存在哪里、怎么备份。

2.3 MySQL容器里哪些目录值得持久化

跑 MySQL 容器时,最核心的持久化目录是 /var/lib/mysql,这是 MySQL 的数据目录,包括系统数据库、用户业务库表文件、InnoDB 的 redo log、undo log,以及 binlog 日志文件都在这里。如果只挂载一个目录,那一定是它。

第二个建议持久化的是自定义配置目录。MySQL 官方镜像默认在 /etc/mysql/conf.d/ 下加载额外的 .cnf 配置,你可以把宿主机上的 my.cnf 文件挂载到这个目录下,或者直接挂载单个文件,比如-v /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro。这样容器重启、重建后配置依然生效,不用每次手动 docker exec 进去改配置。

第三个是日志目录。MySQL 的慢查询日志、错误日志默认写到 stderr 或数据目录,如果你显式配置了 slow_query_log_file 等日志路径,建议把这些日志挂载到宿主机指定目录,避免容器重建后日志丢失,也方便统一采集。需要注意的是,权限设置要匹配 MySQL 容器内 mysql 用户的 UID(通常是 999),否则启动时可能报权限错误。

3. 实操:从拉镜像到验证数据持久化

3.1 环境准备:先确认Docker真的能跑

动手之前先确认 Docker 环境。Linux 服务器上执行docker --version和docker compose version,如果命令不存在,需要先安装 Docker Engine,安装后执行systemctl enable --now docker设置开机自启。

Windows 和 Mac 一般用 Docker Desktop。Windows 上经常遇到一个经典问题:启动 Docker Desktop 时提示 “Virtualization support not detected docker desktop failed to start because …”。这个报错的核心是虚拟化没开或者没对 Windows 功能做完整配置。解决思路是:进入 BIOS 开启 Intel VT-x 或 AMD-V;在 Windows 功能里启用“虚拟机平台”和“适用于 Linux 的 Windows 子系统”(WSL2);必要时执行bcdedit /set hypervisorlaunchtype auto重置 Hypervisor 启动类型,然后重启。

环境装好后,可以用docker run hello-world做一次冒烟测试。如果镜像拉取失败,大概率是网络问题,需要先配置 Docker 镜像加速或代理。确认 Docker 能正常拉镜像、跑容器,再进入下一步,否则后面所有操作都会卡在第一步。

3.2 镜像版本选择:别稀里糊涂拉latest

Docker Hub 上 MySQL 官方镜像主要有 5.7 和 8.0 两个大版本。8.0 是当前主流,支持窗口函数、CTE(公共表表达式)、默认认证插件是 caching_sha2_password;5.7 是老项目兼容性更好的选择,但官方社区版维护已经逐渐收尾,不建议新项目再用。

对比项mysql:5.7mysql:8.0
默认认证插件mysql_native_passwordcaching_sha2_password
新特性支持基本停滞窗口函数、CTE、Hash Join
老客户端兼容性好旧版客户端可能连不上
推荐场景存量老项目新项目、测试开发

还有一点容易被忽略:不要用 latest 标签。latest 会跟随官方发布漂移,你可能今天拉的是 8.0.36,过几个月再拉就变成 8.4 或者更高版本,小版本升级带来行为变化。建议锁定具体版本号,比如mysql:8.0.36,这样镜像内容可预期,回滚也方便。确认版本后,最好先docker pull mysql:8.0.36把镜像下载到本地,避免 run 的时候因为网络问题卡住。

3.3 创建挂载目录和配置文件

我习惯把 MySQL 相关文件统一放在 /data/mysql 下,子目录分为 data、conf、logs 三个。先创建目录,然后处理数据目录的权限。

mkdir -p /data/mysql/{data,conf,logs} chown -R 999:999 /data/mysql/data chmod 750 /data/mysql/data

这里解释一下权限:MySQL 官方镜像内的运行用户是 mysql,UID 固定为 999。如果宿主机的数据目录是 root 所有,容器内的 mysql 进程就没有写权限,初始化时会直接报错退出。chown 到 999 是官方镜像采用的约定方式,这样宿主机目录和容器内用户权限就能对齐。如果系统里有其他进程恰好用了 999 这个 UID,要提前检查冲突,避免误伤其他应用。

然后在 conf 目录下准备一个 my.cnf,内容按需配置。我常用的基础配置如下:

[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-time-zone='+08:00' max_connections=200 slow_query_log=1 slow_query_log_file=/var/log/mysql/slow.log long_query_time=1 [client] default-character-set=utf8mb4

这里有几个细节。default-time-zone 用+08:00而不是Asia/Shanghai,原因是 MySQL 的命名时区支持依赖操作系统时区表,容器里如果没有完整加载 tzdata,可能不生效;而+08:00这种偏移量写法在任何环境都能直接识别。慢查询日志路径我单独配置到了 /var/log/mysql 下,这个目录会挂载到宿主机 /data/mysql/logs,方便运维排查。

3.4 运行容器:一条命令拆开讲

目录和配置准备好之后,执行下面的命令启动 MySQL 容器:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -p 33060:33060 \ -e MYSQL_ROOT_PASSWORD='YourStrong@Passw0rd' \ -e MYSQL_ROOT_HOST='%' \ -e TZ=Asia/Shanghai \ -v /data/mysql/data:/var/lib/mysql \ -v /data/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro \ -v /data/mysql/logs:/var/log/mysql \ --restart always \ mysql:8.0.36

参数逐条说明。-d表示后台运行;--name mysql8给容器命名,后续管理都用这个名字。-p 3306:3306把宿主机 3306 映射到容器 3306;-p 33060:33060映射 MySQL X Protocol 端口,如果不用 X Protocol 可以不加。-e MYSQL_ROOT_PASSWORD设置 root 初始密码,首次初始化时生效;-e MYSQL_ROOT_HOST='%'允许 root 从任意主机远程连接,仅在内网环境建议这么用,生产环境建议改成具体网段或者用业务账号。

三个-v是持久化的核心:数据目录挂载、配置单文件挂载、日志目录挂载。配置单文件挂载比挂载整个目录更安全,因为整个目录挂载会覆盖镜像原有的 conf.d 内容,可能出现配置丢失或加载异常。--restart always让 Docker 在容器异常退出或宿主机重启时自动拉起容器,相当于给 MySQL 加了自动恢复能力。首次执行后,官方镜像的 entrypoint 脚本会发现数据目录为空,自动执行 MySQL 初始化流程,包括创建系统表、设置 root 密码等。可以用docker ps查看容器状态,再用docker logs mysql8看初始化日志。

3.5 持久化验证:重启、删除、重建三步走

容器起来后,不要急着相信“数据已经持久化”,一定要做一轮完整验证。我通常按三步走。

第一步是基础写入。进容器连接 MySQL,创建一个测试库和测试表,写入一条数据:

docker exec -it mysql8 mysql -uroot -p
CREATE DATABASE test_persist; USE test_persist; CREATE TABLE t_demo (id INT PRIMARY KEY, name VARCHAR(50)); INSERT INTO t_demo VALUES (1, 'hello docker mysql');

第二步验证重启。执行docker restart mysql8,容器重启完成后再次查询数据,确认数据还在。这个步骤验证的是容器异常退出、命令重启后数据不丢。

第三步是关键:模拟最极端的情况——删除容器再重建。执行docker rm -f mysql8强制删除容器,然后再执行和上一步完全相同的 docker run 命令重新启动容器。注意数据目录还是同一个 /data/mysql/data,所以容器初始化脚本检测到数据目录非空,就不会重新初始化,而是直接启动已有数据。进入容器查询刚才的 test_persist 表,数据应该原封不动。

这套验证做完,才是真正放心地把业务数据交给 Docker 容器。我踩过没做验证就直接上线的坑,后来清理无用容器时误删了数据目录,虽然最终从备份里恢复了,但那一次的经历让我养成了“恢复演练优先”的习惯。

3.6 备份与恢复:写在最前面的保命技能

持久化解决的是“容器删了数据还在”的问题,但解决不了“磁盘坏了、目录被误删”的灾难。数据库的备份与恢复必须提前准备。用 Docker 跑 MySQL 后,备份命令有一点小变化,但逻辑和传统方式一致。

全量备份用 mysqldump,执行宿主机上的重定向,把备份文件直接写到宿主机目录:

docker exec mysql8 sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction --routines --triggers' > /data/mysql/backup_$(date +%F).sql

--single-transaction对 InnoDB 表做一致性快照备份,避免备份过程中数据不一致;--routines和--triggers把存储过程和触发器一起备份,容易漏。恢复时用 mysql 客户端读入备份文件:

docker exec -i mysql8 sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < /data/mysql/backup_2024-01-01.sql

注意这里用了-i而不是-it,目的是保持标准输入重定向,不要分配伪终端。定期备份可以写到 crontab 里,保留最近 N 份。有条件的话,备份文件每天同步到另一台机器或对象存储,防止宿主机本身出问题。

4. 常见问题与排查技巧实录

4.1 容器起不来先看日志

遇到容器启动失败或初始化失败,第一件事永远是看日志,不要瞎猜。执行:

docker logs --tail 50 mysql8

日志里会直接给出 MySQL 的错误输出。我见过的几类高频问题:数据目录权限不对,日志会报[ERROR] Failed to open file '.../ibdata1'或mysqld: Can't create/write to file/isn't a directory;挂载目录被宿主机其他进程占用或格式不对,会报[ERROR] InnoDB Operating system error number 13;初始化阶段数据目录非空但不完整,会提示类似 “Temporary file for –create-options’ could not be created” 或者版本不匹配的错误。日志是定位问题的第一入口,养成先看日志、再动手改配置的习惯。

4.2 宿主机端口被占用

容器启动时报Bind for 0.0.0.0:3306 failed: port is already allocated,说明宿主机 3306 端口已被占用。先用ss -lntp | grep 3306或netstat -tlnp | grep 3306找出占用进程。如果是宿主机之前装过 MySQL,需要停掉旧服务;如果只是端口被其他应用占用,最简单的处理是换宿主机端口映射,比如-p 3307:3306,容器内 MySQL 端口保持默认 3306 不变,外部通过 3307 连接。

换端口的时候注意防火墙安全组。云服务器需要放行新端口,本地防火墙如果开启,也要同步调整。很多人在本机改了端口后连接不上,排查半天才发现是 firewall 没放行。

4.3 客户端连不上:认证插件、SSL错误和Socket问题

客户端连接报Authentication plugin 'caching_sha2_password' cannot be loaded,这是 MySQL 8.0 默认认证插件和旧客户端不兼容导致的。老版本的 Navicat、旧版 JDBC 驱动、某些老语言库不认识 caching_sha2_password。解决方法是给用户改成旧的 mysql_native_password:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;

新版客户端可以直接支持 caching_sha2_password,不需要改。连接池场景下要注意,多个连接同时建立时,如果驱动不支持新插件,会出现随机连接失败的诡异现象,不只是单个连接报错,这一点在排查“应用偶发断连”时值得优先考虑。

另一个高频问题:本地命令行连接报ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'。这个报错发生在宿主机上直接执行mysql -uroot -p时,因为宿主机没有 MySQL 客户端的 socket 文件,MySQL 默认走 unix socket 连接方式。在宿主机上连接容器内的 MySQL,应该用 TCP 方式并指定端口:

mysql -h 127.0.0.1 -P 3306 -uroot -p

至于 mysql ssl连接错误(ERROR 2026 (HY000): SSL connection error),可能原因包括客户端和服务端的 SSL 协议不匹配、证书时间问题、中间设备拦截。短时间内定位问题可以临时禁用 SSL 验证:

mysql --ssl-mode=DISABLED -h 127.0.0.1 -P 3306 -uroot -p

如果禁用后能正常连接,说明问题出在 SSL 通道本身,需要检查证书配置。生产环境不建议长期禁用 SSL,应该配置正确的 CA 证书并让客户端校验。

4.4 时区和字符集问题

新容器连接后发现数据少 8 小时,大概率是时区没有配置到位。排查命令:

SHOW VARIABLES LIKE '%time_zone%'; SELECT NOW();

如果显示 SYSTEM 或 UTC,说明 MySQL 会话时区不是东八区。解决方案有几个层面:容器启动时加-e TZ=Asia/Shanghai设置系统时区;配置文件里写default-time-zone='+08:00'设置 MySQL 默认时区;应用连接串里追加serverTimezone=Asia/Shanghai或connectionTimeZone=Asia/Shanghai。我建议前两个都做,三层覆盖最稳。

字符集乱码也是高频问题。检查:

SHOW VARIABLES LIKE 'character%';

如果 character_set_server 不是 utf8mb4,需要修改配置重启。已经创建的库和表不会自动跟随默认字符集改变,要手动转换:

ALTER DATABASE yourdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE yourtable CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CONVERT TO会重写表数据,大表操作前务必先备份、业务低峰期执行。字段的默认值问题也经常伴随字符集出现,比如设置默认值 0 时ALTER TABLE ... ALTER COLUMN col SET DEFAULT 0,但已有数据不受影响,只影响后续新插入的行,这是很多人的误区。

4.5 Docker网络不通怎么办

容器化之后网络问题比传统部署多一些。先说跨容器访问:一个 Web 容器要连 MySQL 容器,如果直接用localhost连,必然失败,因为两个容器各自有独立网络命名空间。正确做法是创建一个自定义 bridge 网络,把两个容器都加进去,然后用容器名作为主机名访问。

docker network create mynet docker run -d --network mynet --name mysql8 ... mysql:8.0.36 docker run -d --network mynet --name webapp ... your-webapp

Web 应用连接串里数据库地址写mysql8而不是 IP。

同一个宿主机上的外部应用要连 MySQL,用127.0.0.1和映射端口即可。其他机器连不上,先查宿主机的防火墙和安全组,再查 MySQL 的 root host 是否允许该 IP 来源。容器内访问外网失败,则查看宿主机 DNS 配置,镜像是精简版可能没有内置 dig、ping,可以用docker exec mysql8 cat /etc/resolv.conf确认 DNS 设置,必要时加--dns参数覆盖。

4.6 Windows Docker Desktop的坑

Windows 上跑 Docker Desktop 有几个独特问题。启动报错Failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine,一般出现在 Docker 引擎还没完全启动时,处理方式是重启 Docker Desktop,检查系统托盘图标是否变成稳定状态,或者切回 Linux Container 模式后再切回来。

WSL2 后端下,如果 MySQL 数据挂在 /mnt/c 这类 Windows 文件系统上,I/O 性能会明显下降,因为 WSL2 的跨文件系统读写性能本来就不太行。这时把数据目录放在 WSL2 的虚拟磁盘里,或者直接用 named volume,性能会好很多。另外 WSL2 默认占用内存较高,可以在用户目录下建 .wslconfig 限制:

[wsl2] memory=4GB swap=2GB

改完执行wsl --shutdown重启 WSL 生效。

4.7 常见报错速查表

报错现象常见原因处理方式
docker: Error response from daemon: driver failed programming external connectivity端口映射冲突查看端口占用,换宿主机端口
[ERROR] InnoDB: Operating system error number 13数据目录权限不足chown -R 999:999 数据目录
Authentication plugin 'caching_sha2_password' cannot be loaded客户端版本过旧升级客户端或改用户认证插件
ERROR 2002 Can't connect via socket宿主机上没用 TCP 连接用 -h 127.0.0.1 -P 3306
ERROR 1130 Host not allowed to connectroot 主机限制用 MYSQL_ROOT_HOST='%' 或授权用户
ERROR 2026 SSL connection errorSSL 协议或证书问题临时 ssl-mode=DISABLED 定位

5. 进阶:从“能用”到“好用”的几个习惯

5.1 用docker-compose替你把参数管起来

docker run 命令参数多,记不住也容易打错。我更推荐用 docker-compose 管理 MySQL 容器,所有配置收敛在一个 yaml 文件里,配合 Git 做版本管理,换机器迁移环境直接复制文件再docker compose up -d就能复现。

services: mysql8: image: mysql:8.0.36 container_name: mysql8 restart: always ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=YourStrong@Passw0rd - MYSQL_ROOT_HOST=% - TZ=Asia/Shanghai volumes: - ./data:/var/lib/mysql - ./my.cnf:/etc/mysql/conf.d/my.cnf:ro - ./logs:/var/log/mysql command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 30s timeout: 5s retries: 5

healthcheck是容器健康检查,MySQL 实例就绪后 mysqladmin ping 才会返回成功。Web 应用依赖数据库启动时,可以通过depends_on配合 healthcheck 条件等待数据库就绪,避免应用启动时数据库还没初始化完成。用docker compose logs -f mysql8查看日志,docker compose down停止容器,注意down不会删除挂载目录里的数据,这是数据持久化的保障。

5.2 给容器戴上“紧箍咒”:资源限制

MySQL 是吃内存大户,尤其是 InnoDB 的缓冲池会按配置申请内存。如果宿主机同时跑多个容器,一个没有限制的 MySQL 容器可能把机器内存吃光,拖垮其他应用。建议启动时加资源限制:

docker run -d ... --memory=2g --cpus=2 mysql:8.0.36

在 compose 里对应:

deploy: resources: limits: memory: 2g cpus: '2.0'

注意 InnoDB buffer pool 大小要配合容器内存限制设置。如果容器限制内存 2G,而 MySQL 默认 buffer pool 是 128M,配置 low 一点问题不大;但如果你调高了 buffer pool,同时又把容器内存限制得很小,MySQL 可能因为内存不足拒绝写入或直接崩溃。资源限制是对宿主机的保护,配置参数是对 MySQL 的保护,两者要联动设置。

5.3 日志和存储空间别等你主动去清

容器化之后,MySQL 的错误日志和慢查询日志如果没有挂载,会写到容器内,容器删除就没了;如果输出到 stdout,则被 Docker 收集到 json-file 日志里,长时间运行会让宿主机磁盘出现大量日志文件。排查日志最常用的是:

docker logs --tail 100 mysql8 docker logs --since 30m mysql8

更合理的做法是给容器日志设置滚动上限:

docker run -d \ --log-opt max-size=50m \ --log-opt max-file=3 \ ... mysql:8.0.36

compose 里写:

logging: driver: "json-file" options: max-size: "50m" max-file: "3"

这样每个日志文件最多 50MB,保留 3 个,磁盘不会被日志撑爆。

另一个空间黑洞是 binlog。MySQL 默认在数据目录下生成 binlog,如果开启了 binlog 且没有设置过期时间,日积月累会占用大量磁盘。建议在 my.cnf 中设置:

binlog_expire_logs_seconds = 604800

保留 7 天。注意 MySQL 8.0 里 expire_logs_days 已经废弃,用 binlog_expire_logs_seconds 替代。

5.4 初始化和安全方面的两个加分项

MySQL 官方镜像支持在首次初始化时执行 /docker-entrypoint-initdb.d 目录下的脚本,包括 .sql 和 .sh 文件。这个特性很适合在数据库第一次初始化时自动创建业务库和业务用户。比如准备一个 init.sql:

CREATE DATABASE IF NOT EXISTS appdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'appuser'@'%' IDENTIFIED BY 'AppUser@123'; GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'%'; FLUSH PRIVILEGES;

启动容器时挂载:

-v /data/mysql/init:/docker-entrypoint-initdb.d:ro

注意这个目录只会在数据目录为空、数据库首次初始化时执行,如果数据目录已经初始化过,脚本不会再执行。往已有数据的环境里追加初始化脚本是无效的。

安全方面,环境变量传递密码虽然方便,但容易在 shell history 里留下痕迹。更稳妥是用 env_file 指定环境变量文件,并在 Git 里忽略该文件。另外我习惯为业务单独创建账号,而不是让应用直接使用 root。root 只用于运维操作,业务账号按最小权限原则授权。容器端口也尽量不要对公网开放,通过 Docker 内部网络让应用容器直连 MySQL,不映射外部端口或只绑定内网 IP,这是最省事也最安全的方式。

最后再分享一个我自己的经验:用 Docker 跑 MySQL 确实舒服,但真正决定数据安全的从来不是“用不用 Docker”,而是你有没有想清楚数据放在哪里、有没有做备份和恢复演练。我试过在没有任何持久化挂载的情况下直接删容器,也见过同事因为挂载目录权限不对把数据库初始化两遍,这些坑都不难绕开,但前提是先把持久化验证当成部署流程的一部分。如果你第一次在 Docker 里跑 MySQL,建议在业务数据迁入之前,按下文 3.5 节的做法完整走一遍重启、删容器、重建的验证。另外一个小技巧:把docker ps -a和docker logs --tail 50记成常用命令,容器出了问题先看这两条输出的习惯,能帮你省下大量排查时间。

返回列表