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 -p mongodb-lab/data # MongoDB 数据持久化目录
mkdir -p mongodb-lab/init-scripts # 初始化脚本目录
mkdir -p mongodb-lab/config # 配置文件目录
# Windows 下的等效命令
New-Item -ItemType Directory -Path "mongodb-lab\data" -Force
New-Item -ItemType Directory -Path "mongodb-lab\init-scripts" -Force
New-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 目录下执行)
docker compose up -d# 查看容器状态
docker compose ps
# 期望输出:STATUS = Up (healthy)# 查看日志(排查问题用)
docker compose logs mongodb | Select-Object -Last 20
进入 mongosh:
# 方式一:通过 docker exec 进入容器内的 mongosh
docker exec -it mongodb-lab mongosh -u admin -p admin123 --authenticationDatabase admin# 方式二:如果本机安装了 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: new Date(),balance: NumberDecimal("100.00")},{name: "测试李四",phone: "13800000002",tags: ["会员", "广州"],registeredAt: new Date(),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");// 查询商品
const products = db.products.find({ price: { $lt: 100 } }).toArray();
console.log("低价商品:", JSON.stringify(products, null, 2));// 聚合统计
const result = 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. 验证容器运行
docker compose -f mongodb-lab/docker-compose.yml ps
# 期望 STATUS = Up# 2. 验证数据持久化:重启容器后数据仍在
docker compose -f mongodb-lab/docker-compose.yml restart mongodb
docker exec -it mongodb-lab mongosh -u admin -p admin123 --eval "db.getSiblingDB('local_life').users.countDocuments()"
# 期望输出:2# 3. 销毁并重新创建,验证初始化脚本
docker compose -f mongodb-lab/docker-compose.yml down -v # -v 同时删除数据卷
Remove-Item -Recurse -Force mongodb-lab\data\* # 清空数据目录
docker compose -f mongodb-lab/docker-compose.yml up -d
# 等待 10 秒后验证
docker exec -it mongodb-lab mongosh -u admin -p admin123 --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 实战修炼与源码剖析

微信公众号: 架构师日常笔记 欢迎关注!