1. 项目背景
业务场景:本地生活电商的技术负责人决定用 MongoDB 替代 MySQL。消息一出,全员懵圈——开发说"我本机装不上 MongoDB",测试说"每次重新部署环境都要半小时",运维说"生产集群我们用 Docker,但本地开发怎么跟生产对齐?"更麻烦的是,不同的开发人员用 macOS、Windows、Linux,安装 MongoDB 的方式千差万别,导致"我电脑上能跑"成了每日口头禅,联调的时候经常因为版本、配置不一致而翻车。
痛点:没有标准化的本地实验环境,会给团队带来以下连锁问题:版本不统一,8.0 的新特性在旧版本上报语法错误;端口和账号密码各配各的,配置文件散落在各人电脑上;数据初始化和清理没有自动化脚本,每次手动执行容易遗漏或出错;连接报错时缺少统一的排查思路,比如认证库配错、网络不通、驱动版本不兼容等。最终结果是:同样的代码,张三能用李四不能,排查半天发现李四连的是他三周前忘记关闭的测试容器。
2. 项目设计
小胖(把键盘往前一推):大师,我 C 盘 Program Files 里已经装了 3 个版本的 MongoDB 了!每次重启电脑服务就起不来,注册表也有残留。能不能换一种不用"安装"的方式?
大师:当然可以,而且业内普遍这么干——用 Docker。你把它想象成"集装箱",把 MongoDB 和它的依赖全部打包好,不管在 Windows、Mac 还是 Linux 上,拿过来就能直接用。
小胖:集装箱?这不就跟外卖打包盒一样吗——食物(数据库)和环境(筷子、纸巾)全装好,拆开就能吃。
大师:这个比喻精准。Docker 的镜像(image)就是你点的菜品,容器(container)就是打包盒。你只需要一个命令,就能在任何有 Docker 的机器上,吃到完全一样的"MongoDB 套餐"。
技术映射:Docker 通过容器化技术将应用和依赖打包,实现"一次构建,到处运行"。MongoDB 官方维护着覆盖多个版本的 Docker 镜像,在 Docker Hub 上搜mongo即可找到。
小白(放下手中的技术博客):那 Docker Compose 又是什么?我看到很多项目用docker-compose.yml来管理多个服务。
大师:好问题。Docker 只管单个容器,但我们的业务往往需要多个服务协作——比如 MongoDB 加个 Redis 缓存、加个应用服务器。Compose 就是用一个 YAML 文件描述所有服务的关系、网络、数据卷,一条命令全部启动。
小胖:等等,数据卷是什么?容器一删数据不就没了?
大师:这就是关键。容器本身是无状态的——删了容器,里面的数据确实没了。但我们可以把宿主机的某个目录"挂载"到容器里,数据的实际存储位置在宿主机上。这就像你在一楼的储物柜(宿主机目录)放东西,二楼的住客(容器)通过一个管道(挂载)来取用。储物柜不拆,东西就不会丢。
技术映射:Docker Volume 是数据持久化的核心机制。MongoDB 的dbPath目录必须挂载到宿主机,否则容器重建后数据丢失。
小胖:那 Compass 又是干啥的?跟 phpMyAdmin 一样吗?
大师:对,Compass 就是 MongoDB 官方的图形化管理工具。你可以用鼠标点点点来浏览数据、建索引、跑聚合,不用每次都敲命令。对于刚接触 MongoDB 的同学来说,Compass 能把数据"可视化",降低学习曲线。
小白:但我看很多人用 VS Code 的 MongoDB 插件,和 Compass 怎么选?
大师:互补关系。VS Code 插件适合在写代码时快速查看数据,不离开编辑器;Compass 适合做数据分析、索引优化、模式校验,功能更完整。日常开发我建议两个都装上——“轻活儿用 VS Code 插件,重活儿开 Compass”。
小胖:那 URI 连接串呢?我见过各种奇怪的格式——mongodb://、mongodb+srv://,还有authSource、replicaSet参数,搞得我眼花缭乱。
大师:握手式讲解来了。MongoDB 的连接串叫 Connection String URI,所有客户端(Driver、mongosh、Compass)都用它来建立连接。标准格式:
mongodb://[用户名:密码@]主机1[:端口1][,主机2[:端口2]]/[数据库][?选项]小白:那mongodb+srv://呢?
大师:DNS Seedlist 格式,通过一条 DNS SRV 记录自动发现集群中的所有节点。MongoDB Atlas(云服务)默认给的就是 srv 格式。用了 srv 你不需要写一堆 IP 和端口,MongoDB 自动从 DNS 解析出来。
技术映射:Connection String URI 是 MongoDB 连接的唯一入口。authSource指定认证库(默认 admin),replicaSet指定复制集名称,retryWrites=true启用写操作自动重试。
小胖:学会了!那我们现在能动手搭了吗?我迫不及待想看到数据了。
大师:走,从 Docker Compose 开始。
3. 项目实战
3.1 环境准备
| 组件 | 版本 | 用途 |
|---|---|---|
| Docker Desktop | 24+ | 容器运行时 |
| Docker Compose | v2+(集成在 Docker Desktop 中) | 多容器编排 |
| MongoDB 镜像 | mongo:8.0 | 数据库服务 |
| mongosh | 最新版(容器自带或本机安装) | 命令行客户端 |
| MongoDB Compass | 最新版 | 图形管理工具 |
| VS Code 插件 | MongoDB for VS Code | 编辑器内数据查看 |
3.2 分步实现
步骤一:规划目录结构
目标:创建规范的项目目录,方便后续章节共享使用。
# 在项目根目录创建统一目录结构mkdir-pmongodb-lab/data# MongoDB 数据持久化目录mkdir-pmongodb-lab/init-scripts# 初始化脚本目录mkdir-pmongodb-lab/config# 配置文件目录# Windows 下的等效命令New-Item-ItemType Directory-Path"mongodb-lab\data"-ForceNew-Item-ItemType Directory-Path"mongodb-lab\init-scripts"-ForceNew-Item-ItemType Directory-Path"mongodb-lab\config"-Force步骤二:编写 docker-compose.yml
目标:创建一个可重复启动、数据持久化的单节点 MongoDB 环境。
在mongodb-lab/目录下创建docker-compose.yml:
version:"3.8"services:mongodb:image:mongo:8.0container_name:mongodb-labrestart:unless-stoppedports:-"27017:27017"# MongoDB 默认端口environment:MONGO_INITDB_ROOT_USERNAME:adminMONGO_INITDB_ROOT_PASSWORD:admin123# 如果不需要认证(仅本地开发),可注释掉上面两行volumes:-./data:/data/db# 数据持久化-./init-scripts:/docker-entrypoint-initdb.d:ro# 初始化脚本(只读)-./config/mongod.conf:/etc/mongod.conf:ro# 自定义配置(可选)networks:-mongo-netnetworks:mongo-net:driver:bridge可能遇到的坑:
| 错误现象 | 根因 | 解决方法 |
|---|---|---|
port is already allocated | 本机已有进程占用 27017 端口 | 修改ports为27018:27017,或者停掉占用端口的进程 |
chown: changing ownership of '/data/db': Permission denied | Windows 上 Docker 对 NTFS 目录权限问题 | 在 Docker Desktop 设置中启用该目录的 File Sharing |
| 认证失败 | MONGO_INITDB_ROOT_PASSWORD未生效 | 原因是 data 目录已有旧数据,清除./data目录后重启 |
| 容器反复重启 | 配置文件语法错误或路径不存在 | 检查volumes挂载的宿主机路径是否存在 |
步骤三:启动容器并验证
# 启动(在 mongodb-lab 目录下执行)dockercompose up-d# 查看容器状态dockercomposeps# 期望输出:STATUS = Up (healthy)# 查看日志(排查问题用)dockercompose logs mongodb|Select-Object-Last20进入 mongosh:
# 方式一:通过 docker exec 进入容器内的 mongoshdockerexec-itmongodb-lab mongosh-uadmin-padmin123--authenticationDatabaseadmin# 方式二:如果本机安装了 mongosh,直接连接mongosh"mongodb://admin:admin123@localhost:27017/?authSource=admin"进入 mongosh 后验证:
// 查看版本db.version()// 输出示例:8.0.x// 查看所有数据库show dbs// 默认只有 admin、config、local 三个系统库// 切换到 local_life 库并插入测试数据use local_life db.test.insertOne({msg:"Hello MongoDB 8.0!"})db.test.find()步骤四:连接 Compass
- 打开 MongoDB Compass,在连接串输入框填入:
mongodb://admin:admin123@localhost:27017/?authSource=admin- 点击 “Connect”(连接)
- 左侧数据库列表展开
local_life→ 点击test集合 - 在数据视图区查看刚才插入的文档
Compass 常用功能速览:
| 功能 | 位置 | 用途 |
|---|---|---|
| 浏览数据 | DATABASES → 选库 → 选集合 | 表格化查看文档,支持 JSON 编辑 |
| 创建索引 | 集合页 → Indexes 标签 | 可视化建索引,查看索引大小和使用情况 |
| 执行聚合 | 集合页 → Aggregations 标签 | 拖拽式构建聚合管道,实时预览 |
| Schema 分析 | 集合页 → Schema 标签 | 自动分析字段类型分布,发现脏数据 |
| Explain 计划 | 输入查询条件后 → Explain 按钮 | 可视化查看执行计划和性能指标 |
步骤五:编写初始化脚本
目标:让容器首次启动时自动创建 database、collection 和样例数据。
创建mongodb-lab/init-scripts/01-init-data.js:
// 该脚本在容器首次启动且 data 目录为空时自动执行// 注意:use 在脚本中有效,切换到目标库db=db.getSiblingDB("local_life");// 创建用户集合db.createCollection("users");db.users.insertMany([{name:"测试张三",phone:"13800000001",tags:["新用户","深圳"],registeredAt:newDate(),balance:NumberDecimal("100.00")},{name:"测试李四",phone:"13800000002",tags:["会员","广州"],registeredAt:newDate(),balance:NumberDecimal("500.00")}]);// 创建商品集合db.createCollection("products");db.products.insertMany([{name:"测试商品A",category:"数码",price:199,stock:50},{name:"测试商品B",category:"家居",price:59,stock:200}]);// 建索引db.products.createIndex({category:1});db.users.createIndex({phone:1},{unique:true});print("=== 初始化数据完成 ===");步骤六:连接 VS Code 插件
- 在 VS Code 中安装插件 “MongoDB for VS Code”
- 左侧出现 MongoDB 图标(叶子),点击后选择 “Add Connection”
- 填入连接串:
mongodb://admin:admin123@localhost:27017/?authSource=admin - 连接成功后,可以在 VS Code 中直接:
- 浏览 databases/collections/documents
- 右键集合 → “Playground” 创建可执行脚本
- 执行查询并查看结果
创建一个 MongoDB Playground 文件test-playground.mongodb.js:
// VS Code MongoDB Playground// 选中代码后按 Ctrl+Shift+Enter 执行use("local_life");// 查询商品constproducts=db.products.find({price:{$lt:100}}).toArray();console.log("低价商品:",JSON.stringify(products,null,2));// 聚合统计constresult=db.products.aggregate([{$group:{_id:"$category",count:{$sum:1}}}]).toArray();console.log("类目统计:",JSON.stringify(result,null,2));3.3 完整代码清单
| 文件 | 用途 |
|---|---|
mongodb-lab/docker-compose.yml | 单节点 MongoDB 启动配置 |
mongodb-lab/init-scripts/01-init-data.js | 首次初始化自动建库和数据 |
mongodb-lab/config/mongod.conf | MongoDB 服务端配置(可选) |
3.4 测试验证
# 1. 验证容器运行dockercompose-fmongodb-lab/docker-compose.ymlps# 期望 STATUS = Up# 2. 验证数据持久化:重启容器后数据仍在dockercompose-fmongodb-lab/docker-compose.yml restart mongodbdockerexec-itmongodb-lab mongosh-uadmin-padmin123--eval"db.getSiblingDB('local_life').users.countDocuments()"# 期望输出:2# 3. 销毁并重新创建,验证初始化脚本dockercompose-fmongodb-lab/docker-compose.yml down-v# -v 同时删除数据卷Remove-Item-Recurse-Forcemongodb-lab\data\*# 清空数据目录dockercompose-fmongodb-lab/docker-compose.yml up-d# 等待 10 秒后验证dockerexec-itmongodb-lab mongosh-uadmin-padmin123--eval"db.getSiblingDB('local_life').users.find().toArray()"# 期望输出:包含 测试张三、测试李四# 4. 验证 Compass 连接(用 GUI 操作,此处仅描述预期)# Compass 中应能看到 local_life 库及 users、products 集合4. 项目总结
4.1 Docker 部署 vs 传统安装 vs Atlas 云服务
| 维度 | Docker Compose(本地) | 原生安装 | MongoDB Atlas(云) |
|---|---|---|---|
| 部署速度 | 3 分钟 | 30 分钟(含环境配置) | 5 分钟(UI 创建集群) |
| 环境一致性 | 镜像统一,全平台一致 | 各操作系统体验不同 | 云端统一 |
| 数据持久化 | 需手动配置 Volume | 默认持久 | 自动持久 |
| 升级难度 | 改镜像 tag 重启 | 下载安装包,手动迁移 | 云端一键升级 |
| 离线使用 | 支持 | 支持 | 不支持 |
| 适用场景 | 本地开发、测试、CI | 个人长期实例 | 生产环境首选 |
| 成本 | 免费 | 免费(硬件自备) | 按量付费 |
4.2 适用场景
Docker Compose 部署适用:
- 本地开发和调试:秒级启动/销毁,环境隔离。
- 团队标准化:docker-compose.yml 入 Git 仓库,全员环境一致。
- 持续集成(CI):GitHub Actions / Jenkins 中快速起 MongoDB 跑测试。
- 学习和实验:随心所欲折腾,不行就
down -v重来。 - 多服务联调:和 Redis、Kafka、应用服务一起编排启动。
不适用场景:
- 生产环境(虽然也能用,但推荐 Atlas 或手动部署做调优)。
- 超大规模集群(Kubernetes + Operator 更合适,见第 30 章)。
4.3 注意事项
| 注意事项 | 说明 |
|---|---|
| Windows 路径挂载 | Docker Desktop 需先在 Settings → Resources → File Sharing 中加入项目目录 |
| 默认无认证 | MONGO_INITDB_ROOT_*变量仅在首次启动且 data 目录为空时生效,已有数据后修改密码需用db.updateUser() |
init-scripts执行时机 | 脚本只在 data 目录首次初始化时执行,后续重启不会重复执行 |
| 端口映射 | 生产环境建议不对外暴露 27017,仅通过内部网络访问 |
| WSL2 资源限制 | Windows 上 Docker Desktop 使用 WSL2 引擎,默认内存上限为系统内存的 50%,可在.wslconfig中调大 |
4.4 常见踩坑经验
故障案例一:认证库配错导致连接失败
某开发在docker-compose.yml中设置了MONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD,但连接串中忘写authSource=admin,一直报 “Authentication failed”。根因:root 用户的认证库是 admin,如果连接串未指定 authSource,Driver 默认使用连接的数据库做认证。解决:连接串末尾加上?authSource=admin。
故障案例二:数据目录残留导致密码不生效
测试同学想重置密码,修改了 docker-compose.yml 后执行docker compose down -v && docker compose up -d,但发现新密码无效。根因:他忘了-v参数只删除 Docker 创建的匿名卷,显式挂载的宿主机目录./data不会被删除。解决:手动Remove-Item -Recurse -Force mongodb-lab\data\*清空数据目录。
故障案例三:ARM vs AMD64 镜像兼容问题
某 M1 Mac 开发使用mongo:8.0镜像正常,但推送到 x86 CI 服务器后报 “exec /usr/local/bin/docker-entrypoint.sh: exec format error”。根因:他在 M1 上构建的镜像默认是 ARM64 架构,无法在 x86 上运行。解决:CI 服务器上直接 pull 官方多架构镜像(Docker Hub 的 mongo 镜像支持 amd64 和 arm64),无需本地构建。
4.5 思考题
- 如果要在同一个 docker-compose.yml 中同时启动 MongoDB 和 Spring Boot 应用,它们之间如何通信?网络需要怎么配置?
- 初始化脚本中创建了一个
phone的唯一索引,如果某个文档没有phone字段,唯一索引会如何表现?这和 MySQL 的唯一键约束有什么不同?
(答案将在第 3 章末尾揭晓)
上一章思考题答案:
如果在
_id字段上创建索引,MongoDB 会报错或忽略,因为每个集合的_id字段默认就有唯一索引(名为_id_),这个索引无法删除、无法通过createIndex同名创建。所以不需要手动建。MongoDB 的嵌套文档存储在一块连续的 BSON 数据中,访问第 99 层字段不需要"跨表查找",但需要遍历整个 BSON 结构找到目标字段路径——深度越深,解析开销越大。RDBMS 的 JOIN 则需要在不同表之间匹配数据,存在磁盘 I/O 和内存开销。两者在"关联数据查找"上本质不同:MongoDB 是"在同一个文件里往下翻",MySQL 是"在多个文件之间往返跳转"。
延伸阅读与资源
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经
Milvus向量数据库实战修炼:从 0 到 1精通向量检索与生产落地
后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析