数据库部署入门从本地安装到 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 部署 MySQLdocker run 方式先看单条命令的方式PowerShell行尾反引号是换行符docker run --name my-mysql -e MYSQL_ROOT_PASSWORDMyPass123! -e MYSQL_DATABASEsubstitute -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_DATABASEsubstitute自动建一个叫 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-composeDocker 起容器有两条路它们是替代关系做同一件事只是形式不同docker run命令docker-composeyml 文件怎么起命令行敲一行写个 yml 文件然后docker compose up适合起单个容器一次起一堆容器比喻docker run 口头下单「一杯拿铁、加燕麦奶、大杯、不要糖」——一句话说完。适合就点一杯。docker-compose 写好的订单清单要点 20 杯还要按顺序上写在纸上交给店员照单全做。适合复杂的、多个的。yml 的本质就是把你本来要敲的那些docker run参数写进一个文件里。逐行对应yml 里的对应 docker run 的image: mysql:8.4命令结尾的mysql:8.4container_name: my-mysql--name my-mysqlenvironment:每个-eports: - 3306:3306-p 3306:3306volumes: - ./data:/var/lib/mysql-vdepends_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 部署 MySQL1. 建目录并写 ymlmkdir 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 utf82. 启动docker compose up -dup 按 yml 把服务起起来-d 后台运行启动时会看到Network mysql_default Created——compose 默认给这组服务建了一个专属网络单容器用不上多容器时靠它互相找。3. 验证docker ps # 看到 my-mysqlSTATUS 是 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 -pdocker 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 rootlocalhost 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-composeyml起一堆yml 每一行都对应一个 run 参数。 yml 管「容器怎么起」数据库内部的表/用户/数据不归它管。 学会这套你就从「拿别人连接信息的人」变成了「能提供连接信息的人」。