当前位置: 首页 > news >正文

Docker Compose 部署 MySQL 8.4:从原理到实操(含 -v/-p 详解与改密码的坑)

数据库部署入门:从本地安装到 Docker

本文档整理自一次完整的实操学习过程,主线是:搞懂「部署一个数据库」到底是怎么回事,并亲手用 Docker + docker-compose 部署一个 MySQL,最终能够从「拿别人连接信息的人」变成「提供连接信息的人」。

阅读方式:概念部分建议顺读,实操部分可以照着敲。文中用了不少比喻,它们不是装饰,是帮你把抽象概念钉住的锚。


一、先想清楚:「部署一个数据库」到底是什么

新手容易把「部署」想成一件神秘的大事,其实它可以拆成很具体的5 件事。任何一份数据库连接配置,比如:

CONFIG = { 'server': '192.168.2.7', # 数据库跑在哪台机器上 'port': 1433, # 那台机器上,哪个端口在听 'database': 'substitute', # 服务里的哪个库 'user': 'sa', # 用哪个账号连 'password': 'xxxxxx', # 密码 }

它背后就是有人把下面这 5 件事一件件落实了:

配置项部署时要做的事
server有个 IP找一台机器
port有个端口在听把数据库服务跑起来,它会占一个端口
database有个库在服务里建一个库
user/password建一个账号并设密码
「别人能连」配置成允许从网络连入

核心心智模型(贯穿全文)

一个数据库服务,本质就是守在某个端口上、配好账号密码、等着别人连进来的一个程序。 不管用哪种方式部署(本地安装 / Docker / 云托管),你最终都是在凑齐上面那份{server, port, database, user, password}。方式换来换去,内核不变。


二、本地安装 vs Docker:到底差在哪

一个常见困惑:把数据库直接装进操作系统(本地/裸机安装),和用 Docker 起一个容器,有没有本质区别?

答案:没有致命性的本质差异。数据库还是同一个数据库,干的活一模一样。Docker 改变的不是「数据库本身」,而是「数据库外面那层壳(环境和依赖)怎么打包、怎么隔离」。

比喻:同一台咖啡机(数据库)。

  • 本地安装= 把咖啡机直接拧在自家厨房台面上,水电直接接进墙里。
  • Docker= 同一台咖啡机装进一个标准化的整装橱柜(集装箱),水电通过橱柜上的标准接口转接。

咖啡机没变,煮出来的咖啡一样。变的只是它怎么跟你的系统对接。

真正的区别全在隔离这一件事上:

维度本地安装Docker
数据库本身完全相同完全相同
和系统的关系混在一起,共享环境隔离,自带环境
装多个版本难,会互相打架轻松,各跑各的
删除麻烦,留残渣一条命令,干净
可复制性差,靠记忆/文档极强,一份配方(命令或 yml)
数据安全数据老实躺硬盘需主动配卷,否则删容器丢数据
性能无额外开销极小开销(基本无感)

结论:Docker 解决的是「规模化和一致性」的问题,不是「数据库好不好用」的问题。一台机器上同时稳定跑几十个服务(前端、后端、多种数据库)而互不干扰,靠的就是容器化。这也是公司普遍采用 Docker 的真正理由。

补充:生产环境的数据库,主流其实是云托管数据库(如 RDS)或K8s 有状态编排,而不是随手docker run。随手docker run是开发/测试/学习的主流。但三者内核一致,先把docker run吃透,升级到别的方式只是换个更省事的壳。


三、用 Docker 部署 MySQL(docker run 方式)

先看单条命令的方式(PowerShell,行尾反引号`是换行符):

docker run --name my-mysql ` -e MYSQL_ROOT_PASSWORD=MyPass123! ` -e MYSQL_DATABASE=substitute ` -p 3306:3306 ` -v D:\Docker\Docker_project\MySQL\data:/var/lib/mysql ` -d mysql:8.4

这一条命令,把「部署 5 件事」全包了:

这一段干的事对应「部署 5 件事」
docker run ... -d mysql:8.4下载并后台跑 MySQL 8.4①装软件 ②跑服务
--name my-mysql容器起名,方便管理——
-e MYSQL_ROOT_PASSWORD=...设 root 密码③账号密码
-e MYSQL_DATABASE=substitute自动建一个叫 substitute 的库④建库
-p 3306:3306端口映射⑤开端口
-v 路径:/var/lib/mysql数据存容器外,防丢数据持久化

对比一下解压版当年要手动做的「解压→写 my.ini→初始化 data→注册服务→启动→设密码→建库」七八步——现在全压进了这一行。这就是「部署被压成一行能存进笔记的命令」。


四、关键概念一:端口映射-p

容器是一个隔离的盒子,里面的 MySQL 默认外面碰不到。-p 主机端口:容器端口就是在盒子上开个洞,把里面的端口接到你电脑的端口上。

  • 左边 = 你电脑(宿主机)的端口
  • 右边 = 容器里的端口

以后访问localhost:3306,请求就会被转进容器里的 MySQL。

比喻:容器是封闭的集装箱,MySQL 在里面。-p就是在箱壁上钻个孔、接根管子出来,外面才能和里面通话。

一个真实生产的最佳实践:观察公司服务器上的容器端口,会发现——

  • 应用容器0.0.0.0:8010->8000/tcp(把端口暴露到宿主机,谁都能从外面访问,因为 API 本来就要对外)
  • 数据库容器:只写3306/tcp,没有->故意不对外开放,只让同一个 Docker 网络里的应用容器能连到它)

数据库这样藏起来是为了安全——它最怕被外面直接连、被拖库。

比喻(餐厅):应用容器 = 对客人开放的大堂;数据库容器 = 只有内部员工能进的后厨仓库,客人根本摸不到门。


五、关键概念二:数据卷-v(数据持久化)

这是 Docker 里对数据库最要命的一块。核心问题只有一个:

数据会不会跟容器一起死?

「数据在本地硬盘上」是废话(它当然在),真正的问题是它绑在容器身上,还是独立于容器。这完全由-v决定,分三种情况:

情况一:不写-v(最危险)

数据写在容器内部。容器内部文件系统跟着容器生死,一旦docker rm删掉容器,数据灰飞烟灭,不可恢复

比喻:数据写在便利贴上贴在集装箱内壁,箱子扔了便利贴跟着进垃圾场。

情况二:-v 卷名:/容器内路径(命名卷,推荐)

-v mysql-data:/var/lib/mysql
  • /var/lib/mysql= 容器里 MySQL 固定的数据目录(镜像作者定死的,不改)
  • mysql-data= 一个由 Docker 管理的、独立于容器的「卷」

MySQL 照常往/var/lib/mysql写,但数据实际落在容器外面的卷里。删容器,数据还在卷里,起个新容器接上同一个卷就能续用。

卷在硬盘的位置由 Docker 管理(Linux 上通常在/var/lib/docker/volumes/),用命令查看:

docker volume ls # 列出所有卷 docker volume inspect mysql-data # 看某个卷的详情和真实路径

情况三:-v 本地真实路径:/容器内路径(绑定挂载)

-v D:\Docker\Docker_project\MySQL\data:/var/lib/mysql

数据直接写进你能用文件资源管理器打开的真实文件夹。看得见、能备份、能拷走。适合学习阶段(有掌控感)。

三种对比

写法数据存哪删容器后你能直接看到吗
不写-v容器内部数据全没看不到
-v 卷名:/路径Docker 管理的卷数据还在 ✅要用docker volume命令查
-v 本地路径:/路径你指定的文件夹数据还在 ✅能,直接开文件夹

「卷」到底是什么

卷 = 一块由 Docker 管理的、独立于任何容器而存在的存储空间。它不属于哪个容器,容器死了它还在。

比喻:容器 = 租的房子,卷 = 你的行李箱。

  • 东西摊在房子地板上(写容器内部)→ 房子拆了东西清空。共存亡。
  • 东西装进自己的行李箱(写卷)→ 换房子拎着箱子走,东西一样不少。不共存亡。

背后的设计哲学

容器 = 会变的、随时可扔的躯壳;数据 = 不能变的、要留住的东西。这俩必须分开存。-v就是把两者解耦的机制:让数据活在容器外面,容器随便换,数据岿然不动。

升级场景实例(数据库版本升级,生产天天发生):

# 旧:接卷 mysql-data docker run ... -v mysql-data:/var/lib/mysql -d mysql:8.0 # ↓ 删掉旧容器(数据在卷里,毫发无伤) # 新:换个版本号,接同一个卷 docker run ... -v mysql-data:/var/lib/mysql -d mysql:8.4 # 新容器一启动就看到全部老数据,无缝续用

六、两种起容器的方式:docker runvsdocker-compose

Docker 起容器有两条路,它们是替代关系,做同一件事,只是形式不同:

docker run命令docker-compose(yml 文件)
怎么起命令行敲一行写个 yml 文件,然后docker compose up
适合单个容器一次起一堆容器

比喻

  • docker run= 口头下单:「一杯拿铁、加燕麦奶、大杯、不要糖」——一句话说完。适合就点一杯。
  • docker-compose = 写好的订单清单:要点 20 杯还要按顺序上,写在纸上交给店员照单全做。适合复杂的、多个的。

yml 的本质,就是把你本来要敲的那些docker run参数,写进一个文件里。逐行对应:

yml 里的对应 docker run 的
image: mysql:8.4命令结尾的mysql:8.4
container_name: my-mysql--name my-mysql
environment:每个-e
ports: - "3306:3306"-p 3306:3306
volumes: - ./data:/var/lib/mysql-v
depends_on:(多容器才需要)控制启动顺序
networks:(多容器才需要)让容器互相通信

七、读懂一个 yml 文件:数据位置是怎么写进指定文件夹的

以一个真实的 Milvus compose 片段为例:

volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd

这一行就是-v的 yml 写法。冒号左边是宿主机路径,右边是容器内路径。关键在左边那串${DOCKER_VOLUME_DIRECTORY:-.}

  • 语法${变量名:-默认值}意思是「有这个环境变量就用它的值,没有就用默认值」。
  • :-是「取不到就用后面兜底」。
  • 默认值是.,代表「这个 compose 文件所在的文件夹」

所以展开后:

${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd ↓ 没设变量,用默认 . . /volumes/etcd ↓ . = compose 文件所在文件夹(比如 D:\Docker\Docker_project\Milvus\) D:\Docker\Docker_project\Milvus\volumes\etcd

核心结论

它没把路径写死。数据落在和 yml 文件同一个文件夹下的volumes\子目录里,靠的是.指向「yml 所在文件夹」。 你「指定文件夹」的动作,其实是通过「把 yml 文件放在那个文件夹」间接完成的。 外层的${...:-.}是个灵活开关:想换位置,启动前设个环境变量即可,文件一个字不用改;默认就落在文件旁边。

⚠️相对路径的坑:因为用了.yml 文件不能单独挪走。只搬 yml 到新文件夹再up.会指向新位置,重新建一套空数据,找不到老数据(老数据还在原文件夹)。要搬就整个文件夹一起搬。


八、实操:用 compose 部署 MySQL

1. 建目录并写 yml

mkdir D:\Docker\Docker_project\MySQL cd D:\Docker\Docker_project\MySQL

在该文件夹建docker-compose.yml(注意别存成.txt,建议打开资源管理器的「文件扩展名」显示确认后缀):

services: mysql: image: mysql:8.4 container_name: my-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: MyPass123! MYSQL_DATABASE: substitute ports: - "3306:3306" volumes: - ./data:/var/lib/mysql

./data用了相对路径.,所以数据会落在 yml 旁边的data\文件夹里(和 Milvus 一个原理)。restart: unless-stopped让容器开机/崩溃自动重启。

用命令行直接生成文件(省得开记事本):

@" services: mysql: image: mysql:8.4 container_name: my-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: MyPass123! MYSQL_DATABASE: substitute ports: - "3306:3306" volumes: - ./data:/var/lib/mysql "@ | Out-File -FilePath "docker-compose.yml" -Encoding utf8

2. 启动

docker compose up -d
  • up= 按 yml 把服务起起来
  • -d= 后台运行

启动时会看到Network mysql_default Created——compose 默认给这组服务建了一个专属网络(单容器用不上,多容器时靠它互相找)。

3. 验证

docker ps # 看到 my-mysql,STATUS 是 Up netstat -ano | findstr "3306" # 3306 在 LISTENING Get-ChildItem D:\Docker\Docker_project\MySQL\data # data 文件夹自动生成,含 substitute 子目录

4. 日常管理三条命令

docker compose up -d # 启动 docker compose stop # 停止(数据保留) docker compose down # 停止并删容器(数据在 data\ 里,不丢)

九、连接数据库并验证

进容器内部用 MySQL 客户端登录:

docker exec -it my-mysql mysql -u root -p
  • docker exec -it my-mysql= 钻进容器(-it交互模式)
  • mysql -u root -p= 用 root 登录,-p表示要输密码(输密码时屏幕不显示字符,正常,盲打回车)

登录成功后:

SHOW DATABASES; -- 列出所有库,能看到 substitute 就说明建库成功 exit -- 退出

information_schema/mysql/performance_schema/sys是 MySQL 自带的系统库,不用管。


十、改密码:一个必须知道的坑

陷阱:改 yml 里的MYSQL_ROOT_PASSWORD对一个「已经初始化过的容器」是无效的。

因为MYSQL_ROOT_PASSWORD这个环境变量只在数据目录为空、MySQL 第一次初始化时才生效。数据目录一旦有了数据,MySQL 启动时发现「已初始化过」,就跳过读密码那步,继续用老密码。

环境变量的本质:它是「出生时的设置」,不是「实时同步的配置」。房子盖好时按图纸装了把锁(初始密码),之后你换锁芯(改密码),图纸上那把锁的记录不会自己变,也不影响你换锁。

方案 A:进数据库直接改(推荐,保留数据)

ALTER USER 'root'@'%' IDENTIFIED BY '新密码'; ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'; FLUSH PRIVILEGES;

⚠️密码必须用引号包起来!写成裸的ALTER USER ... IDENTIFIED BY 123456会报语法错(ERROR 1064)。 两条ALTER的原因:MySQL 里 root 账号按「从哪连来」分身份,%是任意地方连、localhost是本机连,两个都改才彻底。 若报「password policy」相关错,是嫌密码太弱,换个复杂点的或调整密码策略。

方案 B:删数据重来,让 yml 新密码生效(会删数据)

docker compose down Remove-Item -Recurse -Force D:\Docker\Docker_project\MySQL\data # 改 yml 里的 MYSQL_ROOT_PASSWORD 为新密码 docker compose up -d

适合库里没真数据、想让 yml 和实际密码彻底一致的情况。


十一、数据库的层级,与「改表名/用户名」

数据库世界是一层套一层的,像俄罗斯套娃:

MySQL 服务器(整个容器) └── 数据库/库 (database) ← 比如 substitute └── 表 (table) ← 比如 users、orders └── 字段/列 (column) ← 比如 id、name 用户 (user) ← root 等,独立于上面,管"谁能登录"

注意:「表名」是表这一层,「用户名」是用户这一层,完全不同的东西,改法也不同

-- 改库名(MySQL 不支持直接重命名,空库直接建新删旧) CREATE DATABASE new_name; DROP DATABASE old_name; -- 改表名 USE substitute; RENAME TABLE old_table TO new_table; -- 改字段名 ALTER TABLE 表名 RENAME COLUMN 旧列名 TO 新列名; -- 建一个专用用户(不要给 root 改名!) CREATE USER 'myuser'@'%' IDENTIFIED BY 'mypassword'; GRANT ALL PRIVILEGES ON substitute.* TO 'myuser'@'%'; FLUSH PRIVILEGES;

别改 root 的名字:很多工具默认找 root,改了容易把自己锁在外面。规范做法是 root 保持不动,另建业务专用用户——这正是别人给你的连接配置里那个user(如sa)的来历:不是把 root 改名,而是新建一个专用账号。

补充:真实开发里,建库/建表/建字段一般在应用代码(如 FastAPI + SQLModel)里创建;改结构用「数据库迁移」工具(如 Alembic)管理,有版本、能回滚。手敲RENAME/ALTER主要用于学习、临时修补或应急。


十二、边界:改这些,yml 要不要动?

一条清晰的分界线:

yml 管的是「容器怎么起」,不管「容器里的数据长什么样」。

比喻:yml 像盖房子的图纸,数据库里的表/用户/数据像住进去后你怎么摆家具。挪张桌子不会去改建筑图纸。

你要改的东西属于哪层要改 yml 吗
表名、字段名数据库内部不用
建表、加数据、删数据数据库内部不用
新建用户数据库内部不用
改用户密码数据库内部不用(可选同步记录)
改/建/删库名数据库内部不用
换端口容器怎么起要改ports:
换镜像版本容器怎么起要改image:
改数据存哪个文件夹容器怎么起要改volumes:
改容器名容器怎么起要改container_name:

判断口诀:问自己「我改的是壳,还是壳里的内容?」壳 → 改 yml;内容 → 不改。

改完 yml 后docker compose up -d生效。compose 会对比差异,只重建真变了的容器。只要卷/数据目录没变,数据库里的表、用户、数据全都还在(它们在卷里,不在容器里)。


附录 A:常用命令速查

# —— 容器 —— docker ps # 看正在跑的容器 docker ps -a # 看所有容器(含已停止) docker rm -f 容器名 # 强制删除容器 docker exec -it 容器名 bash # 进入容器内部的命令行 # —— compose(在 yml 所在目录执行)—— docker compose up -d # 启动 docker compose stop # 停止(保留容器) docker compose down # 停止并删除容器 docker compose logs -f # 查看日志 # —— 卷 —— docker volume ls # 列出所有卷 docker volume inspect 卷名 # 看卷的真实路径 docker volume rm 卷名 # 删除卷(= 删数据,慎用) # —— 镜像 —— docker images # 列出本地镜像 # —— MySQL 连接 —— docker exec -it my-mysql mysql -u root -p # 登录 MySQL # —— 排查 —— netstat -ano | findstr "3306" # 看端口有没有在监听

附录 B:术语表

术语一句话解释
镜像 (image)数据库程序的「安装包/模板」,如mysql:8.4。存在 Docker 统一仓库,位置不可自选。
容器 (container)由镜像跑起来的一个运行实例,是「随时可扔的躯壳」。
卷 (volume)独立于容器的存储空间,用来让数据活得比容器久。
绑定挂载 (bind mount)-v 真实文件夹:/容器路径,数据落到你能看见的文件夹。
端口映射-p 主机端口:容器端口,把容器内端口接到宿主机。
docker-compose用一个 yml 文件定义并管理一个或多个容器。
守护进程 (daemon)一直在后台待命的服务,如mysqld
CRUD增(Create)/查(Read)/改(Update)/删(Delete),数据库四种基本操作。

附录 C:一句话总结整条学习线

数据库部署 = 让一个服务守在端口上、配好账号密码、允许别人连进来。 本地安装和 Docker 装的是同一个数据库,区别只在「外面那层壳怎么打包和隔离」。 Docker 用-p通端口、用-v把数据存到容器外面(否则删容器丢数据)。docker run起单个、docker-compose(yml)起一堆,yml 每一行都对应一个 run 参数。 yml 管「容器怎么起」,数据库内部的表/用户/数据不归它管。 学会这套,你就从「拿别人连接信息的人」变成了「能提供连接信息的人」。

http://www.cnnetsun.cn/news/3513158.html

相关文章:

  • SSE 学习
  • 2026微博视频去水印合法方法:工具教程与侵权风险说明
  • 48V/60V/72V降压转12V/5A电源芯片的测试内容
  • Go 内存泄漏排查完整教程 (pprof)
  • 基于web的轻量级http测试工具——Restfox
  • VC++6.0在Win10/Win11兼容性修复:FileTool补丁原理与实战指南
  • Unity URP材质核心属性深度解析与实战调优指南
  • SQL原生机器学习:数据库内联推理与特征生命周期管理
  • AM62L DDR控制器寄存器深度解析:从时序配置到信号完整性调优
  • 梯度下降直觉手记:从θ²手算到工业级调参
  • AM62L硬件防火墙配置详解:从总线仲裁到寄存器实战
  • 2026六安黄金回收白银回收铂金回收市民首选无隐形扣费正规备案回收门店联系方式推荐
  • 深入解析AM62L DDR PHY寄存器:从原理到实战优化嵌入式内存性能
  • Flutter与Android原生混合开发实践指南
  • 3A之AE篇7
  • Spine 4.2骨架动画加载全解析:从原理到Unity实战
  • 追踪系统供电保障:适用于主动式RFID标签的纽扣型一次锂电池
  • MibSPI传输组寄存器TG4CTRL-TG7CTRL配置详解与实战
  • Qt框架开发实战:从跨平台原理到工业级应用
  • TI DCAN控制器IF3寄存器组:消息过滤与自动更新机制详解
  • STM32与MPU6050姿态传感器开发指南
  • iOS集成支付宝支付全流程指南与常见问题解决
  • C++代码零缺陷实战:静态分析工具链配置与团队集成指南
  • CIJ 小字符与 CO₂激光、光纤激光,在材质吸收系数、波长适配逻辑上如何区分行业选型边界?
  • ADT迁移Android Studio的常见问题与解决方案
  • 【软考备考】信息安全详解:对称与非对称加密、数字签名、数字摘要——两把钥匙的逻辑(附 10 道练习)
  • C2000 ePWM死区与故障保护:电机驱动与电源设计的核心安全机制
  • 回归分析实操导航:从模型选择到业务可解释性
  • 嵌入式视觉系统CSI-2接口配置实战:从协议层到物理层详解
  • Handler嵌套模式在多线程与异步编程中的应用