上周帮朋友在本地搭一套开发环境,他问我MySQL用什么方式装最省心。我没怎么犹豫就回了句:直接上Docker。他半信半疑——在他的印象里,Docker是部署阶段才用的东西,装个数据库何必绕这一圈。结果我在他电脑上敲完两条命令,十分钟后他已经能用Navicat连上容器里的MySQL了。这篇文章就是当时那两条命令的完整拆解,以及后续我踩过的那些坑。
如果你是第一次接触Docker下的MySQL,想搞清楚镜像怎么拉、容器怎么启、配置文件怎么挂、SQL怎么在容器里执行,那么这篇应该能帮你省不少时间。我尽量按一个完整的链路来讲:先交代思路,再给可复制的命令,最后把高频报错和排查逻辑单独拎出来说。所有内容都基于实际验证过的方式,版本以MySQL 8.0为主,部分地方会补充与5.7的差异。
1. 为什么我坚持用Docker跑MySQL:从裸机到容器化的思路转变
1.1 什么场景适合容器化,什么场景不适合
先说结论:Docker跑MySQL最舒服的场景,是本地开发、测试环境、多版本共存和CI/CD流水线。我自己日常至少有三个MySQL环境是同时跑的——一个MySQL 8.0给当前项目用,一个5.7用来复现老项目的兼容问题,还有一个临时实例用来验证数据库初始化脚本。如果是裸机安装,你得在多个版本之间反复卸载、修改配置、处理端口冲突,光想想就头疼。容器化之后,每个实例都是独立的名字、独立的端口映射、独立的存储卷,想开就开,想删就删,完全不干扰系统里其他软件。
但如果你是要跑一个对性能极致敏感的大型线上实例,或者数据安全合规要求极高,那我不建议直接用Docker裸上。不是说容器本身性能差多少,而是生产环境牵扯到网络方案、调度策略、文件系统选型,这些不是本文这种新手向内容能覆盖的。我的原则很简单:开发、测试、个人项目随便用容器,生产环境请交给专业的DBA和运维去评估。
1.2 Docker环境准备:先把虚拟化这关过了
经常有人卡在第一步:Docker Desktop装好了,点启动,报错。热词里那个很长的错误其实很典型,说的是“virtualization support not detected”,翻译过来就是系统没检测到虚拟化支持,Docker Desktop直接起不来。
这种报错九成出现在Windows机器上。排查顺序我建议是这样:
- 打开任务管理器,切到“性能”标签,看CPU部分是否显示“虚拟化:已启用”。如果显示“已禁用”,得进BIOS/UEFI把Intel VT-x或AMD-V打开。这一步在不同主板上的菜单位置不一样,但关键词基本都是Virtualization、SVM Mode之类的。
- 确认Windows的“适用于Linux的Windows子系统”功能(也就是WSL2)已经开启。可以管理员身份运行PowerShell,执行
wsl --status看状态。没装的话先用wsl --install装一个。 - 如果你用的是Windows 10旧版本,建议先把系统更新到较新版本,旧版Docker Desktop和WSL2的兼容性没那么好。
我这几年在Mac、Linux服务器上都跑过Docker,Mac的Docker Desktop体验最省心,Linux服务器上直接用curl -fsSL https://get.docker.com | bash一键装也行。Windows最大的问题就是虚拟化这一关,过了之后就顺利了。总之,装Docker之前先确认底层虚拟化是通的,可以省掉一晚上的折腾。
1.3 Docker跑MySQL的核心逻辑:镜像、容器、数据卷
在操作之前,先把Docker的三个概念和MySQL结合起来理解。
镜像可以理解为MySQL的“安装包”,它里面已经打包好了可执行文件、默认配置和运行环境。容器是你用这个“安装包”创建出的一个运行实例,就像一台独立的、只跑了MySQL的小机器。数据卷则是这台小机器里的外部硬盘,你把它挂载到容器内的数据目录,容器删了、重建了,数据都还在。
这三个概念对应到MySQL上,就是三条命令的事:docker pull拉镜像,docker run创建并启动容器,-v参数挂载数据卷。后面的操作全部围绕这三件事展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 镜像选择与首次启动:一条命令背后的完整逻辑
2.1 版本标签怎么选:别一上来就latest
我见过太多人上来就docker pull mysql:latest,这不是不能用,但不太推荐。原因很简单:latest标签随着官方发版会漂移,你今天拉下来的镜像和你同事下周拉下来的,未必是同一个版本。这就失去了容器化“环境一致”的意义。
我的建议是锁定一个大版本,最好连小版本也固定。比如当前项目基本兼容8.0,那就直接指定:
bash复制docker pull mysql:8.0
如果你想连小版本都锁死,可以在Docker Hub上查一下当前的8.0.x具体版本号,然后docker pull mysql:8.0.40这样精确指定。5.7的历史项目同理,指定mysql:5.7即可。官方镜像的tag列表是开源的,查起来很方便,养成锁版本的习惯,后面排查问题会好受很多。
2.2 从docker run说起:环境变量、端口、数据卷逐个拆解
拉完镜像,核心操作就是创建容器。我第一次用Docker跑MySQL时,命令是抄来的,完全没搞懂每个参数干什么,结果后面遇到问题只能干瞪眼。这里我把一条典型的启动命令拆开来讲。
假设我要创建一个名为mysql8的容器,使用8.0镜像,宿主机3306端口映射到容器3306端口,root密码设成MyRoot@123456,时区设为东八区,数据存到名为mysql-data的数据卷里:
bash复制docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD='MyRoot@123456' \
-e TZ=Asia/Shanghai \
-v mysql-data:/var/lib/mysql \
--restart=always \
mysql:8.0
逐个说:
-d:后台运行,不占着当前终端。--name mysql8:给容器取名。后续docker exec -it mysql8 bash直接按名字操作,比记一长串ID方便得多。-p 3306:3306:端口映射。左侧是宿主机端口,右侧是容器端口。如果宿主机3306已经被占用,改成-p 3307:3306,那么外部客户端就连3307端口。-e MYSQL_ROOT_PASSWORD:初始化root密码。官方镜像要求必须设置MYSQL_ROOT_PASSWORD、MYSQL_ALLOW_EMPTY_PASSWORD、MYSQL_RANDOM_ROOT_PASSWORD三者之一,否则容器会创建失败。-e TZ=Asia/Shanghai:设置容器时区。不设的话,容器内默认是UTC时间,你查NOW()会比北京时间慢8个小时,各种日志时间也会对不上。-v mysql-data:/var/lib/mysql:数据卷挂载。mysql-data是Docker管理的卷,/var/lib/mysql是容器内MySQL默认的数据目录。这样容器删了重建,数据不会丢。--restart=always:Docker服务启动或容器异常退出时自动拉起容器。很实用的参数,本地开发时不用每次开机手动启动数据库。
如果你想临时试一下,不指定--name也没问题,Docker会自动生成一个随机名字,但后续操作就不方便了。我个人习惯名字、端口、时区、数据卷每次都要显式指定,哪怕只是测试环境,也按生产标准来。
2.3 initdb脚本:首次启动自动建库建表的黑魔法
还有一个很多人不知道的细节:官方镜像支持把.sql或.sh脚本放到容器内的/docker-entrypoint-initdb.d/目录,首次初始化数据目录时会自动执行。
什么意思呢?比如你希望创建一个新用户、建好数据库和表结构,不用等容器起来后再手动敲SQL。你可以把初始化脚本先放在宿主机上,挂在容器里:
bash复制docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD='MyRoot@123456' \
-v mysql-data:/var/lib/mysql \
-v /path/to/init:/docker-entrypoint-initdb.d \
mysql:8.0
容器第一次启动时,会先初始化数据目录,再执行这个目录下的.sql文件或.sh脚本。注意有两个前提:第一,数据卷必须是空的;第二,脚本只在第一次启动时执行。数据卷里已经有数据的话不会重复执行。这对自动化初始化环境非常有用,我从第二年开始就一直在用这个机制,能省下大量重复建库的时间。
3. 自定义配置与数据持久化:让容器真正成为能长期用的环境
3.1 配置文件挂载:把my.cnf放进容器
MySQL跑起来之后,第一件事往往是调整配置。字符集、时区、连接数、慢查询日志,这些都属于“不改不难受”的项。而这些配置在容器里,默认值往往不适合国内开发环境。
官方镜像读取配置文件的路径是/etc/mysql/,其中/etc/mysql/conf.d/是专门给用户放自定义配置的目录。如果你的自定义配置是my.cnf,可以整个文件挂载到这个目录:
bash复制-v /path/to/my.cnf:/etc/mysql/conf.d/my.cnf
或者把整个配置目录挂进去:
bash复制-v /path/to/myconf:/etc/mysql/conf.d
我个人更推荐第一种,挂载单个文件,因为这样目录里不会出现一堆残留文件,配置变更时也只改一个文件。
挂载配置后需要重启容器才能让MySQL重新读取配置文件:
bash复制docker restart mysql8
3.2 我常用的配置清单:时区、字符集、连接数、慢查询
下面这份配置是我在本地开发环境下最常用的一套,直接写入my.cnf然后挂载就行:
ini复制[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
default-time-zone='+08:00'
max_connections=500
wait_timeout=600
interactive_timeout=600
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow.log
long_query_time=1
逐个解释。
字符集方面,utf8mb4是必选项。MySQL 8.0的默认字符集虽然已经是utf8mb4,但5.7及更早版本默认还是utf8mb3,实际只能存基本多文种平面字符,遇到emoji这种四字节字符会报错或乱码。collation-server我习惯用utf8mb4_unicode_ci,排序规则更符合直觉。
时区方面,容器内默认是UTC,这在你用NOW()函数或看日志时是个大坑。如果你已经通过-e TZ=Asia/Shanghai设置了环境变量,那系统时区是对的;但MySQL内部读取的是default-time-zone配置,所以两者最好都设。配置里写'+08:00'比写'Asia/Shanghai'更稳,因为后者依赖系统时区表,在最小化镜像里偶尔会有问题。
连接数方面,max_connections=500是开发环境比较舒服的数值,避免并发一高就报Too many connections。在线上的话这个值要结合内存评估,不要盲调。wait_timeout=600表示非交互连接10分钟没活动就断开,能减少服务器端资源的无效占用。
慢查询日志方面,slow_query_log=1打开慢日志,long_query_time=1表示超过1秒的查询都被记录。开发阶段这个阈值可以放宽到0.5秒甚至更小,方便抓出隐藏的性能问题。日志文件路径要放在容器内的/var/log/mysql/下,如果容器里没有这个目录,先启动容器后进容器里创建一下,或者直接把日志输出到标准错误(docker logs能直接看到),各有利弊。我习惯挂一个单独的日志目录,方便在宿主机上直接看。
3.3 改完配置如何生效:重启容器 vs 动态变量
配置文件改完,最直接的方式是docker restart mysql8。但有些参数支持在线修改,不用重启就能生效。
比如连接数和超时时间,可以在客户端执行:
sql复制SET GLOBAL max_connections = 500;
SET GLOBAL wait_timeout = 600;
这属于动态变量,改完立即生效,但服务重启后会回到配置文件里的值。所以如果你想长期保留,还是要把它们写进my.cnf。
有些参数则只能写在配置文件里,改完必须重启,比如character-set-server、default-time-zone这类。我记得有一次图省事,用SET GLOBAL character_set_server = utf8mb4,结果客户端显示Variable 'character_set_server' is a read only variable,白忙一场。所以判断标准很简单:读online开头的变量可以动态改,restart开头的必须重启。不确定的话,用SHOW VARIABLES查状态,或者直接看官方文档的变量表。
3.4 验证配置是否生效
配置改完不验证等于白改。进入容器连上MySQL,执行:
sql复制SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'default_time_zone';
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'slow_query_log';
看到的是你配置的值,就说明挂载生效了。这一步很重要——我有一次把配置文件路径挂错了,容器正常启动,数据也正常读写,但所有自定义配置都没生效,查了半天才发现挂载路径写成了/etc/mysql/mysql.conf.d/,而官方镜像读的是conf.d目录。所以验证这一步别省。
4. 进入容器执行SQL的三种姿势:从查询到批量导入
4.1 最直接的方式:docker exec 进入容器
标题里说的“进入容器执行(查询)sql”,最朴素的方式就是先进容器的交互式shell,再执行MySQL客户端命令。
bash复制docker exec -it mysql8 bash
进入容器后,你会看到类似root@容器ID:/#的提示符。这时直接敲:
bash复制mysql -uroot -p
输入密码就进入了MySQL命令行。在这个环境里,你能做的操作和裸机安装的MySQL没有任何区别。比如查看所有库:
sql复制SHOW DATABASES;
进入指定库:
sql复制USE mydb;
查看一张表的数据:
sql复制SELECT * FROM users LIMIT 10;
这里有个细节:在容器内用mysql -uroot -p,走的是UNIX socket连接,不是TCP。对应的socket文件在/var/run/mysqld/mysqld.sock。所以如果你在容器里执行mysql -h127.0.0.1,反而可能会因为认证方式或host限制出现问题。容器内就直接用socket连接,别加-h参数。
4.2 不进入容器直接执行SQL
如果你只想跑一条查询,不想进入容器和MySQL交互式命令行的两层嵌套,可以用下面的方式:
bash复制docker exec mysql8 mysql -uroot -p'MyRoot@123456' -e "SELECT NOW();"
这里的mysql是容器内的MySQL客户端,通过docker exec在当前宿主机上直接调用。加-e表示执行一段SQL后退出。这种方式非常适合在Shell脚本里用,比如定时检查数据库状态:
bash复制docker exec mysql8 mysqladmin -uroot -p'MyRoot@123456' status
需要提醒的是,命令里直接带密码会有泄露风险,尤其是多人共用的机器。替代方案是把密码写到MYSQL_PWD环境变量里,或者在宿主机上用--defaults-extra-file指定配置文件。但无论哪种方式,都要注意不给脚本仓库的明文密码。我个人在本地开发机图省事会直接带,但凡是需要提交到代码库的脚本,一律用环境变量或配置管理工具。
4.3 宿主机或远程客户端连接容器中的MySQL
很多时候你要用Navicat、DataGrip或者DBeaver连接容器里的MySQL。这时走的是TCP协议,需要保证三件事:容器端口映射正确、客户端能访问到宿主机端口、MySQL账号允许对应来源的连接。
连接串大概是:
bash复制mysql -h 127.0.0.1 -P 3306 -uroot -p
这里最容易踩的坑是账号授权问题。裸机安装的MySQL通常root只允许localhost登录,外部连接会报Access denied。官方Docker镜像则不同,默认创建的root账号host是%,允许外部通过TCP连接。但如果你用的是自定义初始化脚本,或者后来自己创建了账号,就得检查mysql.user表:
sql复制SELECT user, host, plugin FROM mysql.user;
如果某个账号的host是localhost,你在宿主机上连就会失败。解决办法是创建host为%的账号,或者授权时显式指定:
sql复制CREATE USER 'app'@'%' IDENTIFIED BY 'AppPass@123';
GRANT ALL PRIVILEGES ON appdb.* TO 'app'@'%';
FLUSH PRIVILEGES;
另一个常见坑是MySQL 8.0默认的认证插件是caching_sha2_password,一些老版本的客户端工具不支持这个插件,连接时直接报Authentication plugin 'caching_sha2_password' cannot be loaded。解决办法有二选一:升级你的客户端工具到新版本,或者把这个账号改回mysql_native_password兼容模式:
sql复制ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'MyRoot@123456';
两条路各有利弊。我自己的习惯是能升级客户端就升级,因为mysql_native_password在新版本里越来越边缘化,终归要走新认证方式。
4.4 导入SQL文件的两个常用方法
初始化数据、恢复备份,经常要把本地SQL文件导入容器里的MySQL。两个方法都很实用。
第一个方法是通过管道重定向,不需要把文件复制进容器:
bash复制docker exec -i mysql8 mysql -uroot -p'MyRoot@123456' < /path/to/backup.sql
注意这里的-i参数不能少。它表示保持标准输入打开,把宿主机文件的内容输送给容器内命令。如果漏了-i,命令不会报错,但执行结果会是空的,文件内容根本没传进去。
第二个方法是先复制文件再在容器内执行:
bash复制docker cp /path/to/backup.sql mysql8:/tmp/backup.sql
docker exec -it mysql8 bash
mysql -uroot -p
source /tmp/backup.sql
这个方法适合文件很大、网络不稳定、或者你想边导入边观察输出的场景。source在MySQL命令行里等同于执行文件中的SQL语句。
有一点要提醒:导入前确认文件里的SQL是否包含CREATE DATABASE IF NOT EXISTS或者USE语句。如果没有,你要先手动建好目标库,再执行导入,否则可能出现“No database selected”之类的报错。
5. 高频报错排查与性能优化:连接失败、乱码与慢SQL
5.1 连接类报错逐个拆解
先说一个热词里很常见的报错:
code复制ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'
这个报错在Docker场景下出现得比裸机还频繁,因为它背后至少有三种完全不同的原因。
第一种是容器里的MySQL没起来。你到了容器内直接敲mysql,结果连不上socket,这很可能是mysqld进程根本没在跑。先docker ps看容器状态,再用docker logs mysql8看启动日志,基本能定位。
第二种是socket文件路径对不上。官方镜像的socket默认在/var/run/mysqld/mysqld.sock,如果你的my.cnf里自定义了socket路径,而客户端仍按默认路径找,就会报这个错。前面我说过,容器内连接MySQL不要加-h,走默认socket就能对上;如果你改了socket路径,客户端也要用mysql -S /path/to/mysqld.sock指定路径。
第三种是混淆了“容器内”和“宿主机上”两个环境。在宿主机上执行mysql -uroot -p,如果宿主机本身没装MySQL客户端,系统里自然没有socket,也会报同样的错。这时要么在宿主机上装一个mysql-client,要么通过docker exec在容器内执行。
另一个很常见的报错是:
code复制ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (61)
这通常是端口映射没生效。重点检查docker ps里容器是否把3306映射到宿主机,以及宿主机的端口是不是被别的进程占用了。我自己遇到过最乌龙的一次是宿主机上另一个MySQL竟然还占着3306,Docker的端口映射根本没有生效,导致怎么连都失败。
5.2 乱码与字符集问题
很多人建表时明确写了utf8mb4,可查出来的中文还是乱码,数据插入后变成问号。这种问题大多出在“连接层”的字符集,不在表结构本身。
MySQL字符集链路很长:服务器字符集、数据库字符集、表字符集、连接字符集、客户端字符集、结果集字符集。表结构设置了utf8mb4,不等于客户端发过来的SQL在传输过程中用的也是utf8mb4。
遇到乱码,先按顺序查三个位置:
sql复制SHOW VARIABLES LIKE 'character_set_client';
SHOW VARIABLES LIKE 'character_set_connection';
SHOW VARIABLES LIKE 'character_set_results';
如果这三个变量里有非utf8mb4的值,连接层的字符集就没对齐。可以在客户端连接后执行:
sql复制SET NAMES utf8mb4;
这个命令一并设置以上三个变量。治本的办法是在my.cnf的[mysql]或[client]段里加:
ini复制[client]
default-character-set=utf8mb4
另外,MySQL 8.0默认的character_set_server已经是utf8mb4,5.7不是,升级或迁移时要注意。检查乱码问题,最后再看一眼数据文件里到底存了什么:
sql复制SELECT HEX(name_column) FROM users WHERE id = 1;
如果返回的十六进制是e4b8ade69687这样的三段式,说明数据本身是正确的utf8mb4,乱码只发生在显示或传输环节。这个技巧能帮你快速区分到底是存储问题还是展示问题。
5.3 SQL性能问题定位:从慢查询日志到执行计划
MySQL跑一段时间后,最让人头疼的是SQL越来越慢。Docker环境里排查方式和裸机没什么区别,但要把慢查询日志先打开。前面配置里已经开了slow_query_log和long_query_time=1,现在可以直接看日志。
bash复制docker exec -it mysql8 bash
cat /var/log/mysql/slow.log
慢日志会记录超过阈值的SQL、执行时间、锁等待时间、返回行数等。看到一条慢SQL,首先用EXPLAIN看执行计划:
sql复制EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1;
重点看几个字段:type,如果是ALL说明是全表扫描,大概率需要索引;key,实际用到的索引;rows,预估扫描的行数;Extra,有没有Using filesort或Using temporary,有的话基本是排序或分组设计得不理想。
我遇到过最典型的场景是:开发环境数据库数据量才几万行,查询怎么跑都快,等数据涨到几百万行,同样的SQL突然秒级响应变成十几秒。问题通常不是配置,而是索引缺失或索引没走对。解决办法可以是给WHERE条件里最常用于过滤的列建索引:
sql复制ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
这里有个细节:查询条件的列如果用了函数包裹,比如WHERE DATE(created_at) = '2026-01-01',那索引就失效了。改成范围查询WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02',才能充分利用索引。这种问题在本地开发环境几乎不会暴露,线上数据量一大就是灾难。
另一个值得提的点是:Docker容器的资源限制也会影响SQL性能。默认情况下Docker容器并没有限制CPU和内存,但如果你的机器整体资源紧张,容器的innodb_buffer_pool_size还是默认值128MB,大数据量查询就会频繁走磁盘IO,再好的索引也救不了。这个参数可以根据宿主机内存适当调大,修改方法也是写入my.cnf后重启容器。我之前在一台4G内存的开发机上把innodb_buffer_pool_size从128M调到512M,同样一批SQL的执行时间肉眼可见地下降了一截。
5.4 关于“mysql设置默认值为0”这类参数问题
热词里有“mysql设置默认值为0”,这个通常和sql_mode有关。MySQL 8.0默认的sql_mode包含NO_ZERO_DATE和NO_ZERO_IN_DATE两个选项,意味着不允许日期字段存在'0000-00-00'这样的值,也不允许月份或日期为0。
但有的老项目数据里偏偏就有这种脏数据,导入时直接报错。这时候就需要修改sql_mode,去掉这两个限制:
sql复制SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
同样地,这个配置要写进my.cnf才能重启后保持。我处理过不止一次这种问题,印象很深的是有个迁移项目,Oracle那边迁移过来的数据日期字段全是0000-00-00,MySQL默认模式直接拒绝写入,最后全表数据导入失败。当时的解决办法就是调低sql_mode的严格程度,导完数据后再考虑清洗。
6. 日常运维清单:别把容器当一次性玩具
6.1 数据备份的三种方式
容器的一个特点是生命周期短。今天还在跑的容器,明天可能就因为升级、调试、资源不足被删掉。所以备份这件事必须形成习惯。
第一种是直接用docker cp把整个数据目录复制出来:
bash复制docker cp mysql8:/var/lib/mysql /path/to/backup-mysql
这种方式最简单,相当于把整个数据文件打包备份。但它只适合在MySQL停止写入或允许短暂停库的场景,直接复制运行中的数据文件可能导致备份不一致。我在本地通常只用来“存档”,不当作真正可靠的备份。
第二种是用mysqldump做逻辑备份:
bash复制docker exec mysql8 mysqldump -uroot -p'MyRoot@123456' --all-databases > all_databases.sql
逻辑备份导出的是SQL文本,跨平台、跨版本兼容性好,推荐场景是数据量不大(几个GB以内)的开发库和测试库。恢复方式就是前面讲的导入方法。
第三种是定期备份加恢复演练。光导出一个文件不算备份成功,至少得验证生成的SQL文件能不能重新导入一个全新的容器里。我给自己定的规则是:每次备份完,在本地用临时容器导入一次,确认数据完整再放心。别嫌这一步麻烦,真到需要恢复数据的那天,你会感谢昨天的自己。
6.2 容器日志查看与资源占用
MySQL不带日志就会像一个黑盒。docker logs是查看容器内MySQL日志最直接的方式:
bash复制docker logs mysql8
加上-f参数可以实时滚动输出:
bash复制docker logs -f mysql8
MySQL自身的错误日志通常输出到容器的stdout,docker logs直接能看到,包括启动过程、初始化信息、异常关闭原因等。排查Table doesn't exist、Can't connect这类问题,第一手信息基本都在这里。
资源占用方面,docker stats可以实时看容器的CPU、内存、网络IO:
bash复制docker stats mysql8
这个命令能直接暴露出内存是不是快打满了、CPU是不是长期100%。MySQL是把内存当缓存用的,docker stats能看到RSS占用很高,但要看是不是合理,还得结合innodb_buffer_pool_size。
还有一个容易忽视的点:MySQL容器日志如果长期不处理,会越来越大。docker logs本身不产生日志文件,但如果你把容器内的输出重定向到了宿主机日志文件,注意加日志轮转。Docker daemon的配置里可以设置max-size、max-file,这一步对长期运行的实例很有必要。
6.3 容器升级与迁移
换版本这种事,容器化比裸机舒服得多。
最稳妥的方式是逻辑迁移。先在旧容器上mysqldump导出数据,再拉取新版本镜像,创建新容器并导入数据。注意,跨大版本迁移(比如5.7到8.0)不能直接复用旧的数据卷,因为8.0的ibdata1、mysql.ibd等系统表文件格式变了,直接挂旧数据卷很可能会启动失败。遇到这种需求,老老实实走逻辑备份恢复,比强行挂载数据卷省心得多。
升级时除了数据,还要关注两个差异点。第一是认证插件,前面已经提过。第二是sql_mode和不可见索引等行为差异,8.0的默认sql_mode比5.7更严格,老项目可能会在升级后突然报各种“写入失败”的错误。
如果你用的是docker compose管理多个容器,可以顺手把MySQL的配置模板化:
yaml复制services:
mysql8:
image: mysql:8.0
container_name: mysql8
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=MyRoot@123456
- TZ=Asia/Shanghai
volumes:
- mysql-data:/var/lib/mysql
- ./my.cnf:/etc/mysql/conf.d/my.cnf
restart: always
volumes:
mysql-data:
这样以后起一台MySQL就一句话的事:docker compose up -d。升级时改一改image标签,重新创建即可。我后来把自己手上的MySQL环境全部迁到了compose管理,维护成本明显下降。
最后再分享一个我个人的习惯:不管在什么环境,我都会把容器的名字、端口、数据卷名、配置文件路径写进项目的README或者一个备忘文件里。时间一长你会发现,最难的不是起容器,而是总忘记上一次是怎么起这个容器的。写下来的成本很低,但能节省下次排查的时间,这是我现在最想让你从这篇文章里带走的一个小建议。
