news 2026/10/15 19:46:10

Docker部署MySQL实战:从拉镜像到容器运行与数据持久化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker部署MySQL实战:从拉镜像到容器运行与数据持久化

1. 为什么我用Docker装MySQL,而不是直接在机器上装

先交代一下背景。之前我一直是"本机安装派",MySQL、Nginx这些数据库中间件全部老老实实装到系统里。直到某天要在三台配置不同的机器上搭同一套环境,传统安装方式的问题一次性全暴露了:一台是Ubuntu,包管理器不一样;一台是刚装的系统,缺各种依赖库;还有一台跑着旧版本MySQL,端口和服务冲突处理得我头大。最终折腾了两天,才把所有机器的环境对齐。

从那以后,我在本地开发环境里基本不再直接装数据库,而是统一走Docker。这个转变的核心原因是:Docker把MySQL的运行环境整个打包了——不只是可执行文件,还包括它依赖的系统库、配置文件、数据目录结构,全部封装在镜像里。

对刚接触Docker的读者,我需要先把概念缩小到一个最小可用的范围。你不需要一开始就理解镜像分层、联合文件系统这些底层机制。你只需要知道:镜像是模板,容器是模板运行出来的实例。就像你手机里的App安装包是模板,点安装之后出现的应用图标就是实例。Docker跑MySQL,本质就是"下载一个官方做好的MySQL模板,然后在Docker里启动一个MySQL实例"。

适合读这篇内容的人,主要是这几类:

  • 第一次接触Docker,想在Docker里把MySQL跑起来的新手;
  • 本机装了MySQL但总出问题,想切换到Docker环境的开发者;
  • 需要在多台机器上快速部署相同数据库环境,不想重复踩坑的人。

这篇文章覆盖的路径是完整的:从零开始,拉镜像、启动容器、配置文件挂载、字符集设置、端口映射,再到最终进入容器执行SQL查询。每一步我都会解释为什么这么做,以及常见的坑在哪里。

我在实际帮助同事排查问题时发现一个现象:很多人"能跑起来",但不清楚每条命令的参数到底做了什么。这导致一旦环境出问题,完全没有排查头绪。所以我这篇文章的重点不只是"怎么操作",更在于帮你理解"为什么这么操作"。

2. 动手前的三分钟:镜像、容器和数据卷到底是什么关系

2.1 从安装包思维跳转到镜像思维

要理解Docker下的MySQL,建议先忘掉"安装"这个词,改用"拉取镜像"和"运行容器"这两个词。

传统安装MySQL,做的是把二进制文件、系统服务脚本、配置文件放到指定目录,然后注册成系统服务。整个过程侵入系统,不同版本之间容易留有残留。而Docker的思路完全不同:镜像就是一份不可更改的MySQL运行环境快照,你拉取的是什么版本的MySQL,镜像里就是什么样的MySQL环境,不会因为你宿主机系统的差异而出现行为不一致。

举个我经常用来打比方的例子:传统安装像租房,家具是房东放的,你搬进去之后还得自己补各种东西,退租的时候还得恢复原样;Docker像住酒店,硬件设施齐全,你住进去只动自己需要动的,退房关门,房间恢复原样。这个比喻不完全准确(数据卷可以持久化),但第一次接触这个概念时,先用这个思路理解没错。

2.2 容器生命周期:启动、停止、删除,都不动你的数据

容器不是虚拟机,但它有类似虚拟机的隔离特性。你在容器里改配置文件、装软件、建立数据库,这些操作默认只影响这个容器,不影响宿主机。

关键点在这里:容器本身是临时的。如果你跑了一个容器,往里面建了表插了数据,然后把这个容器删了,那么这些数据就没了——除非你把数据存储在数据卷(Volume)里。

我见过很多初学者在这里踩坑:起了一个MySQL容器,创建了业务库,然后对着容器执行了重建操作,数据全丢。这不是Docker的缺陷,而是没有理解容器的生命周期。容器可以被随意销毁和重建,但数据必须放在数据卷中。

2.3 数据卷和端口映射的直觉理解

数据卷的作用,可以理解为宿主机上的一个"外部硬盘",你这个容器用完了删掉了,外部硬盘上的数据仍然在。下次再起新容器时,把这个外部硬盘挂到新容器上,数据就回来了。

命令行里对应的是-v参数。比如:

docker run -v /home/user/mysql-data:/var/lib/mysql

斜杠左边的/home/user/mysql-data是宿主机目录,右边的/var/lib/mysql是容器内MySQL默认的数据存储目录。意思就是:容器内写的数据库文件,实际保存在宿主机这个目录里。

端口映射也是类似的思路。MySQL默认监听3306端口,但容器是隔离的,宿主机默认访问不到容器内的端口。你需要把宿主机的某个端口和容器内的3306建立映射,外部程序才能通过"宿主机IP:映射端口"访问到容器里的MySQL。命令参数是-p:

docker run -p 3306:3306

前面的3306是宿主机端口,后面的3306是容器内的MySQL端口。熟悉了这两个参数,Docker跑MySQL的基本骨架你就掌握了。

3. 从拉镜像到成功启动:一条命令背后的每个参数

3.1 版本选择:mysql:8.0 与 mysql:5.7 怎么选

我建议新项目直接选8.0系列,而且明确指定小版本号,而不是用mysql:latest。原因是:latest标签指向的版本会随着官方发布而变化,今天拉的是8.0.36,过段时间可能就变成8.4或者更高版本,这种不确定性在复现环境时非常难受。

拉取镜像的命令:

docker pull mysql:8.0

如果你需要5.7做旧项目兼容,就拉mysql:5.7。两个版本在容器运行上差别不大,但SQL行为有差异,类型默认值、字符集排序规则这些细节会不同。这里多提一句:5.7是旧时代的经典,8.0是当前主流,两者的认证插件机制不同,这点执行SQL时影响不大,但用客户端工具连接时会遇到,后面我会单独说。

3.2 启动命令逐参数拆解

我一般用这样一个命令启动MySQL容器(以8.0为例):

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e TZ=Asia/Shanghai \ -v /data/mysql8/conf:/etc/mysql/conf.d \ -v /data/mysql8/data:/var/lib/mysql \ --restart always \ mysql:8.0

这里逐一说一下每个参数的作用:

参数作用说明
-d后台运行容器在后台运行,不会占用你的终端
--name mysql8给容器命名后续所有docker命令都用这个名称操作,比记一长串容器ID方便得多
-p 3306:3306端口映射宿主机3306映射到容器3306,宿主机端口可以改成其他值,如-p 3307:3306
-e MYSQL_ROOT_PASSWORD设置root账号初始密码这是官方镜像提供的最直接环境变量方式
-e TZ=Asia/Shanghai设置时区不设的话容器内默认是UTC时间,比北京时间慢8小时,排查时间类问题时特别容易被坑
-v /data/mysql8/conf:/etc/mysql/conf.d挂载配置目录把宿主机上的配置目录挂进容器,后续调整MySQL配置不用进容器改文件
-v /data/mysql8/data:/var/lib/mysql挂载数据目录数据持久化的关键,删除容器数据也不丢
--restart always自动重启策略Docker守护进程启动时自动拉起这个容器,机器重启后你不需要手动再启动MySQL

正式环境我还会追加一个--privileged=false(默认就是false,显式写出来只是强调安全意识),并且限制容器资源如-m 1g之类,但入门阶段先不追求这些,把上面这张表里的内容吃透就够用了。

3.3 启动校验的三层检查

容器启动后,很多人直接就开始连数据库,连不上就一脸懵。正确的做法是分层校验:

第一步看容器状态:

docker ps -a

STATUS列显示Up表示容器正在运行,显示Exited说明容器退出了。如果刚启动就退出,基本是配置错误或端口冲突。用docker logs mysql8看日志,MySQL启动失败的常见报错里面都会写清楚原因。

第二步看MySQL日志:

docker logs mysql8 --tail 100

看到类似ready for connections的日志,说明MySQL服务已经在容器内监听端口了。

第三步实际连接:

docker exec -it mysql8 mysql -uroot -p

输入你设置的root密码,进入mysql>交互界面,才算真正确认"能用了"。这三步检查可以帮你快速把问题定位到"容器没起来"还是"数据库没起来"还是"密码不对"。

4. 首次配置:字符集、时区、权限与远程连接的连锁反应

4.1 字符集为什么必须用utf8mb4而不是utf8

很多新手都会踩这个坑:建库时用了utf8,然后插入表情符号(emoji)或者某些生僻字时报错。原因在于MySQL的utf8字符集实际上最多只支持3字节,而表情符号需要4字节存储,utf8mb4才是真正完整的UTF-8实现。

配置字符集最稳妥的方式是在容器启动时就挂载一个自定义配置文件。我通常会在宿主机/data/mysql8/conf目录下新建一个名为my.cnf的文件:

[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci skip-character-set-client-handshake

注意点:这个目录是启动命令里通过-v挂载到/etc/mysql/conf.d的。MySQL启动时,主配置读取完后会继续读取/etc/mysql/conf.d/目录下的配置文件。所以你在宿主机上放的文件,容器启动时会自动生效。

skip-character-set-client-handshake这一行的含义是:忽略客户端连接时指定的字符集。这样不管客户端用什么字符集握手,服务端都强制使用utf8mb4,避免出现"同一个库在不同连接工具下看到的乱码不一样"的问题。

修改完配置后需要重启容器:

docker restart mysql8

然后进容器验证:

SHOW VARIABLES LIKE 'character_set%';

重点看character_set_server和character_set_database这两项,都应该是utf8mb4。

4.2 时区问题比想象中更容易出问题

镜像默认的时区是UTC,这个在开发环境测试时通常感知不到,等到了联调阶段,别人问你"为什么插入的时间差了8小时",你才开始查。提前在启动命令里加-e TZ=Asia/Shanghai是最省事的方法。如果容器已经启动了,也可以进容器内改配置,但不如启动时就设好。

验证时区:

SELECT NOW();

看返回的时间是不是当前本地时间。如果是UTC时间,就再检查一下你的启动命令和系统环境变量。

4.3 远程连接失败的两个隐藏原因

默认情况下,官方镜像的MySQL root用户被限定只能从容器内部连接。你在宿主机上执行mysql -h127.0.0.1 -P3306 -uroot -p都可能连不上(取决于具体版本和权限设置),用Navicat或DataGrip这类图形工具连接更是大概率失败。

第一个原因是权限。MySQL 8.0默认的root账号认证插件是caching_sha2_password,而很多客户端工具和旧版本驱动默认使用mysql_native_password,两者不兼容导致连接报错Authentication plugin 'caching_sha2_password' cannot be loaded。

解决办法有两种,我按推荐程度排列:

  • 升级客户端工具到最新版本,新版工具基本都支持8.0的认证方式;
  • 如果客户端无法升级,则给远程连接的用户指定旧的认证插件:
CREATE USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY 'app_password'; GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'%'; FLUSH PRIVILEGES;

第二个原因是防火墙。宿主机未放行你映射出去的端口(比如3306),外部机器自然无法连接。这个不属于Docker范畴,但排查顺序排在很靠前,别忽略。

4.4 开放远程权限到什么程度合适

还有一个常见操作误区:为了省事,把所有权限都给了'root'@'%'。这样做在开发环境或许可行,但一旦这个数据库可以被内网其他机器访问,风险就成倍增加。

我的建议是:root只保留容器内部使用,外部连接单独建专用账号,权限按需授予。比如某个应用的库只读账号:

CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'readonly_password'; GRANT SELECT ON business_db.* TO 'readonly_user'@'%'; FLUSH PRIVILEGES;

这样即使密码泄露,攻击者能做的也只是读取,无法删除和篡改。

5. 进入容器执行SQL的四种姿势和它们的适用场景

5.1 最常用:docker exec进入交互式命令行

这是我在日常开发中使用频率最高的方式,一条命令直接进入MySQL命令行:

docker exec -it mysql8 mysql -uroot -p

拆开来看:docker exec的含义是"在运行中的容器里执行命令",-it是保持终端交互模式,mysql8是容器名,后面的mysql -uroot -p是在容器内执行的MySQL客户端命令。执行后输入密码,就进入了熟悉的mysql>交互界面。

进入交互界面后,你就可以正常使用SQL了。查看所有数据库:

SHOW DATABASES;

切换数据库:

USE business_db;

查看当前库所有表:

SHOW TABLES;

查询某张表的数据:

SELECT * FROM users LIMIT 10;

5.2 不经交互界面的单行命令

如果只是想快速执行一条SQL并拿到结果,不需要进入交互界面,可以这样写:

docker exec -it mysql8 mysql -uroot -p123456 -e "SELECT VERSION(); SELECT NOW();"

-e参数后面直接跟SQL语句。这种方式非常适合在脚本里调用,比如定时检查DB状态。

需要注意:把密码直接写在命令行里,在开发环境图省事没问题,但生产机器上会被shell历史记录留下痕迹,也有被其他正在查看进程列表的人看到的风险。建议生产环境配合MYSQL_PWD环境变量或使用配置文件管理认证信息。

5.3 进入容器内部再操作的不常见场景

有些场合需要先在容器内做一些检查,比如查看MySQL日志文件、检查数据目录大小。这时先进入容器:

docker exec -it mysql8 bash

进入容器内部后,你会发现自己身处一个精简版的Linux环境。此时MySQL客户端也是可用的:

mysql -uroot -p

容器内直接执行ls /var/lib/mysql可以看到数据库的物理文件,这在排查数据目录占用大、文件权限异常等场景下非常有用。用完输入exit退出容器。

5.4 批处理方式:从宿主机导入SQL文件

你有没有遇到过这种情况:拿到一个几百MB的SQL备份文件,要通过source方式导入数据库。传统方式在宿主机上执行mysql < dump.sql引入,但在Docker下,由于mysql客户端在容器里,不能用宿主机的shell直接重定向进去。

正确的方式是借助docker exec -i的交互参数,让标准输入重定向进容器内的mysql命令:

docker exec -i mysql8 mysql -uroot -p123456 business_db < /path/to/backup.sql

注意这里用的是-i而不是-it,因为不需要终端交互,只需要标准输入管道。加上-t反而会在管道场景下输出乱码提示。

导入较大SQL文件时,建议先在目标库创建好库:

CREATE DATABASE IF NOT EXISTS business_db DEFAULT CHARSET utf8mb4;

再执行导入命令。

5.5 这四种方式的适用情况对比

方式命令示例适用场景
交互式命令行docker exec -it mysql8 mysql -uroot -p日常查询、调试、手动操作
单行命令docker exec mysql8 mysql -e "SQL"脚本化执行、快速验证
进入容器docker exec -it mysql8 bash排查容器内文件、日志、环境问题
输入重定向docker exec -i mysql8 mysql ... < 文件导入SQL文件、批量执行

6. 跑起来之后,我替你踩过的坑和排查套路

6.1 端口冲突:宿主机3306已被占用

这是最经典的问题。你的机器上可能已经装过MySQL,或者另一个容器占了3306端口,运行新容器时报错:

Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use

排查方法:

# 查看宿主机端口占用 lsof -i:3306 # 如果是已存在的mysql服务占用,可以停掉或改新容器的映射端口 docker run -d --name mysql8 -p 3307:3306 ...

把宿主机端口改成3307,外部连接时就用-P3307参数。这个处理方式不优雅,但能最快解决问题。长期方案是统一规划端口使用,别让容器端口随机分配。

6.2 数据卷权限:容器启动后立即退出

常见的报错日志:

[ERROR] [MY-010457] mysqld: Cannot change permissions of the file '/var/lib/mysql/.mysql.lock' [ERROR] [MY-012574] InnoDB: Unable to lock ./ibdata1 error: 11

原因通常是挂载目录的权限不对。容器内部的mysql用户UID是999,而宿主机上你创建的目录和文件属主可能是root或你自己的用户,导致容器没权限写入。

排查手段:

# 查看挂载目录属主和权限 ls -ld /data/mysql8/data

解决办法,一是把宿主目录属主改成999:

chown -R 999:999 /data/mysql8/data

二是把挂载目录先改成宽松权限再启动容器。不过更推荐第一种,能避免后续文件属主混乱的问题。

6.3 caching_sha2_password认证插件导致连接失败

前面提到过,这里展开讲。MySQL 8.0默认使用caching_sha2_password认证,但部分旧版客户端不支持。最典型的现象:用Navicat 11或旧版JDBC连接时,报错:

Public Key Retrieval is not allowed

本地开发时最简单的办法是调整连接字符串参数,以JDBC为例,加上allowPublicKeyRetrieval=true&useSSL=false。但如果受影响的不止一个客户端,我更建议给该应用单独建一个mysql_native_password的账号:

CREATE USER 'legacy_app'@'%' IDENTIFIED WITH mysql_native_password BY 'legacy_pass'; GRANT SELECT, INSERT, UPDATE, DELETE ON business_db.* TO 'legacy_app'@'%'; FLUSH PRIVILEGES;

这样既兼容了旧客户端,又避免了为所有账号开低安全级别的认证插件。

6.4 Linux下MySQL表名大小写敏感

同一套SQL,在Windows本机和Linux的Docker容器里跑,却报Table 'xxx' doesn't exist。原因是Linux下MySQL的lower_case_table_names默认值是0,即表名区分大小写;而Windows默认值是1,不区分。

我个人强烈建议在配置文件中显式声明:

[mysqld] lower_case_table_names=1

注意:这个参数在已经初始化过的MySQL数据目录上修改可能不生效或引发问题。正确的做法是在第一次启动容器、数据目录为空、还在初始化阶段时,就把这个配置挂载进去。如果已经跑起来了,就只能通过导出数据、清空数据目录、重新初始化来解决。我在这个坑上吃过亏,所以这里特意强调。

6.5 忘记root密码的容器级重置方案

传统安装方式忘记密码,要改配置跳过授权表、重启服务、再改回来,步骤繁琐。Docker容器下的重置思路类似,但更干净:

第一步,在当前容器配置目录里加一行跳过授权验证:

echo "skip-grant-tables" >> /data/mysql8/conf/my.cnf docker restart mysql8

第二步,此时无需密码即可进入:

docker exec -it mysql8 mysql -uroot

第三步,重设密码并退出,然后删掉配置项再重启:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';
sed -i '/skip-grant-tables/d' /data/mysql8/conf/my.cnf docker restart mysql8

这套方法屡试不爽,特别适合开发环境的恢复场景。

6.6 用Docker Compose固定整套运行参数

到了多台机器都要跑同一套MySQL环境时,一条长长的docker run命令复制起来容易抄错。我这时就会把参数固化到docker-compose.yml里:

services: mysql: image: mysql:8.0 container_name: mysql8 restart: always ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=123456 - TZ=Asia/Shanghai volumes: - /data/mysql8/conf:/etc/mysql/conf.d - /data/mysql8/data:/var/lib/mysql

启动命令:

docker compose up -d

这个文件提交到代码仓库后,换一台机器,拉代码后只需要执行这一条命令就能把MySQL环境带起来。配置变更、环境复现都变得可追踪。我从本地开发转到团队协作之后,这是Docker带给我最直接的生产力提升。

这套容器化的工作流,核心就是:镜像保证环境一致,数据卷保证数据不丢,配置文件挂载保证可修改,Compose文件保证可复现。想明白这四件事,MySQL在Docker下的安装、启动、配置、进入容器执行SQL,就再也不会是困扰你的问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 19:45:55

丝绸之路9.0资源包:版本号驱动的模块化配置与避坑指南

简介&#xff1a;这份资源是面向服装行业从业者与打版技术人员的「丝绸之路9.0」服装CAD系统安装包&#xff0c;集设计、打版、放码与排料功能于一体&#xff0c;可帮助中小服装企业提升出图效率与设计精度。软件需配合加密锁授权使用&#xff0c;安装后由加密锁验证许可信息方…

作者头像 李华
网站建设 2026/10/15 19:44:26

HoloCubic 固件中的 LVGL 编码规范:从命名约定到 astyle 自动格式化实战

嵌入式物联网智能硬件嵌入式GUI硬件开发 【免费下载链接】HoloCubic 带网络功能的伪全息透明显示桌面站 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ho/HoloCubic 点击查看 免费下载 本文以 HoloCubic 开源仓库内置的 LVGL 图形库规范文档 CODING_STYLE.md 为核心&…

作者头像 李华
网站建设 2026/10/15 19:42:50

Windows SDK 7.1 下载安装与避坑指南:老项目构建链的完整解决方案

简介&#xff1a;Microsoft Windows SDK 7.1 是一套面向 C 开发者的官方工具集&#xff0c;主要用来构建、调试和部署 Windows 7 及 Windows Server 2008 R2 环境下的原生应用程序。该压缩包体积只有 102KB&#xff0c;却包含 4 个文件&#xff0c;类型涵盖安装引导程序、MSI 安…

作者头像 李华
网站建设 2026/10/15 19:41:43

Ubuntu 18.04 深度学习环境搭建:CUDA 10.2 + cuDNN 8 + TensorRT 6.0 完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 19:41:08

C#高性能Modbus实时采集框架设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 19:40:23

嵌入式系统实现Printf函数

void dbg_printf(const char *fmt, ...) {char buf[128];va_list ap;int len;va_start(ap, fmt);len vsnprintf(buf, sizeof(buf), fmt, ap);va_end(ap);if (len > 0) {if (len > (int)sizeof(buf) - 1) {len sizeof(buf) - 1; // 截断}// while(CDC_Transmit_FS((uin…

作者头像 李华