MongoDB 教程

MongoDB 是一个基于分布式文件存储的开源 NoSQL 数据库,由 C++ 编写,为 Web 应用提供可扩展的高性能数据存储方案。与关系型数据库不同,MongoDB 使用 文档(Document)作为数据模型,数据以 BSON(二进制 JSON)格式存储,天然贴近开发者的对象思维。

MongoDB 的核心特性

特性说明
文档模型数据以类 JSON 的文档存储,无需预定义表结构
高可扩展支持分片(Sharding)水平扩展
高可用副本集(Replica Set)自动故障转移
丰富查询支持索引、聚合管道、地理位置查询
多语言驱动官方支持 Java/Python/Node.js/PHP 等

适用场景

内容管理系统、日志与监控数据、实时分析、物联网传感器数据等写多读多、结构灵活的场景。

💡 提示
MongoDB 官方文档:https://www.mongodb.com/docs,中文社区也提供了大量教程。

NoSQL 简介

NoSQL(Not Only SQL)是一类非关系型数据库的总称,用于解决传统关系型数据库在大数据、高并发场景下的瓶颈。它放弃了部分 ACID 约束,换取更高的扩展性与性能。

NoSQL 四大类型

类型代表产品特点
键值存储Redis、Memcached读写极快,适合缓存、会话
文档存储MongoDB、CouchDB类 JSON 文档,结构灵活
列族存储HBase、Cassandra适合海量写入与分析
图数据库Neo4j、JanusGraph擅长关系与网络分析
⚠️ 注意
NoSQL 并非取代关系型数据库。金融交易等强一致性场景仍以 MySQL/PostgreSQL 为主,两者常配合使用。

MongoDB 简介

MongoDB 于 2009 年发布,由 MongoDB Inc.(原 10gen)开发,是目前最流行的文档型 NoSQL 数据库。它的基本概念包括:

概念MySQL 类比说明
数据库(database)database一个服务可含多个数据库
集合(collection)table类似表,但无固定结构
文档(document)row一条数据,BSON 格式
字段(field)column文档中的键值对
// 一个典型 MongoDB 文档
{
  "_id": ObjectId("5f8c9a1b2c3d4e5f6a7b8c9d"),
  "name": "张三",
  "age": 25,
  "tags": ["前端", "篮球"],
  "address": { "city": "北京", "street": "中关村大街" }
}
💡 提示
_id 是每个文档必有的主键字段,默认类型为 ObjectId。

Windows MongoDB

在 Windows 上安装 MongoDB 有两种常见方式:MSI 安装包 或 压缩包。

MSI 方式(推荐)

从官网下载 mongodb-windows-x86_64.msi,双击安装。安装时建议勾选 Install MongoDB Compass(图形化管理工具),并选择将 MongoDB 作为 Windows 服务安装,开机自启。

压缩包方式

:: 解压后进入 bin 目录,创建数据目录并启动
mkdir C:\data\db
cd C:\mongodb\bin
mongod --dbpath C:\data\db

:: 另开一个终端连接
mongo

Linux MongoDB

Linux 上最常用 apt / yum 包管理器安装 MongoDB。以 Ubuntu 为例:

# 导入 MongoDB 公钥并添加官方源
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

sudo apt-get update
sudo apt-get install -y mongodb-org

# 启动并设置开机自启
sudo systemctl start mongod
sudo systemctl enable mongod

OSX MongoDB

macOS 上推荐用 Homebrew 安装 MongoDB:

# 添加 MongoDB 官方 tap
brew tap mongodb/brew

# 安装
brew install mongodb-community

# 启动服务(后台运行)
brew services start mongodb-community

# 连接测试
mongosh

MongoDB Shell

mongosh 是 MongoDB 官方的交互式 Shell(旧版为 mongo),用于执行数据库操作命令。

// 连接本地 MongoDB
mongosh

// 显示所有数据库
show dbs

// 切换/创建数据库(切换即创建)
use shop

// 查看当前数据库
db

// 查看集合
show collections

MongoDB 概念解析

深入理解 MongoDB 的核心概念有助于正确建模:

概念含义
_id主键,唯一标识文档,默认 ObjectId
ObjectId12 字节:4 时间戳 + 5 机器标识 + 3 自增计数器
BSON二进制 JSON,支持嵌套文档与数组
Cursor查询结果游标,遍历式读取
索引类似 MySQL 索引,加速查询
// ObjectId 结构
// 5f8c9a1b 2c3d4e5f 6a7b8c9d
// 时间戳   机器/进程  计数器

MongoDB 用户管理

生产环境必须为 MongoDB 开启认证并创建用户。

// 切换到 admin 库创建管理员
use admin
db.createUser({
  user: "admin",
  pwd: "admin123",
  roles: [{ role: "root", db: "admin" }]
})

// 创建业务库专用用户
use shop
db.createUser({
  user: "app",
  pwd: "app123",
  roles: [{ role: "readWrite", db: "shop" }]
})
⚠️ 注意
启用认证需在配置文件中设置 security.authorization: enabled,重启服务后生效。

MongoDB 连接

MongoDB 连接字符串(URI)格式统一,主流驱动均支持:

// 格式:mongodb://[用户:密码@]主机[:端口]/数据库
mongodb://localhost:27017/shop

// 带认证
mongodb://app:app123@localhost:27017/shop?authSource=admin

// 副本集
mongodb://node1:27017,node2:27017,node3:27017/shop?replicaSet=rs0
# Python 连接示例
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["shop"]

MongoDB 创建集合

集合(collection)类似关系型数据库的表,但不需要预定义字段结构。

// 隐式创建:插入第一条文档时自动创建集合
db.users.insertOne({ name: "张三" })

// 显式创建:可设置选项
db.createCollection("orders", {
  capped: true,        // 固定大小集合
  size: 100000,        // 最大字节数
  max: 1000            // 最大文档数
})

// 查看集合
show collections

MongoDB 更新集合名

可以使用 renameCollection 重命名集合:

// 语法:db.旧名.renameCollection("新名")
db.users.renameCollection("members")

// 通过管理命令
db.adminCommand({
  renameCollection: "shop.users",
  to: "shop.members"
})

MongoDB 删除集合

删除集合使用 drop() 方法,集合连同其中的文档将永久删除:

// 删除集合
db.orders.drop()

// 返回 true 表示删除成功
// 注意:该操作不可恢复!

MongoDB 插入文档

MongoDB 提供三种插入方法:

// 插入单条
db.users.insertOne({
  name: "李四", age: 30, tags: ["技术"]
})

// 插入多条
db.users.insertMany([
  { name: "王五", age: 22 },
  { name: "赵六", age: 28 }
])

// 传统方法(可插入单条或数组)
db.users.insert({ name: "老七" })
💡 提示
插入时若集合不存在会自动创建;未指定 _id 时自动生成 ObjectId。

MongoDB 更新文档

更新文档需配合更新操作符使用,否则整个文档会被替换。

// 更新单条(匹配第一条)
db.users.updateOne(
  { name: "李四" },
  { $set: { age: 31, city: "上海" } }
)

// 更新多条
db.users.updateMany(
  { age: { $lt: 25 } },
  { $set: { level: "young" } }
)

// 常用操作符
// $set 设置字段  $inc 自增  $unset 删除字段
// $push 数组追加  $addToSet 数组去重追加
db.orders.updateOne(
  { _id: ObjectId("...") },
  { $inc: { total: 100 }, $push: { items: "商品A" } }
)
⚠️ 注意
不带操作符的更新会整体替换文档,生产环境慎用。

MongoDB 删除文档

删除文档使用 deleteOne / deleteMany:

// 删除匹配的第一条
db.users.deleteOne({ name: "王五" })

// 删除所有匹配
db.users.deleteMany({ age: { $lt: 18 } })

// 清空集合所有文档(保留集合)
db.users.deleteMany({})

// 返回 { deletedCount: n }

MongoDB 查询文档

查询是 MongoDB 最常用的操作,核心方法是 find():

// 查询全部
db.users.find()

// 条件查询
db.users.find({ age: { $gt: 25 } })

// 投影:只返回指定字段(1 显示 0 隐藏)
db.users.find({}, { name: 1, age: 1, _id: 0 })

// 链式操作:排序 + 限制 + 跳过
db.users.find()
  .sort({ age: -1 })   // 1 升序 -1 降序
  .limit(10)
  .skip(20)

// 计数
db.users.countDocuments({ age: { $gt: 20 } })

MongoDB 条件操作符

MongoDB 提供丰富的比较与逻辑操作符:

操作符含义示例
$eq / $ne等于 / 不等于{ age: { $eq: 25 } }
$gt / $gte大于 / 大于等于{ age: { $gt: 25 } }
$lt / $lte小于 / 小于等于{ age: { $lt: 30 } }
$in / $nin在/不在数组中{ city: { $in: ["北京","上海"] } }
$and / $or与 / 或{ $or: [{a:1},{b:2}] }
$not取反{ age: { $not: { $gt: 50 } } }
// 组合查询示例
db.orders.find({
  $and: [
    { total: { $gte: 100 } },
    { status: { $in: ["paid", "shipped"] } }
  ]
})

MongoDB $type 操作符

$type 用于按字段类型过滤文档:

// 查询 age 字段为 double 类型的文档
db.users.find({ age: { $type: "double" } })

// 查询 age 为 int 或 long
db.users.find({ age: { $type: ["int", "long"] } })
BSON 类型别名编号
Doubledouble1
Stringstring2
Objectobject3
Arrayarray4
ObjectIdobjectId7
Booleanbool8
Datedate9
Nullnull10
Int32int16

MongoDB Limit 与 Skip 方法

limit() 限制返回条数,skip() 跳过前 N 条,常用于分页:

// 每页 20 条,取第 3 页
db.users.find()
  .sort({ _id: 1 })
  .skip(40)    // (页码-1) * 页大小
  .limit(20)

// 只取前 5 条
db.users.find().limit(5)
✅ 建议
数据量大时优先用 _id > 上一页最后一条 的游标分页,性能远优于 skip。

MongoDB 排序

sort() 对查询结果排序,1 升序、-1 降序:

// 按年龄降序,再按姓名升序
db.users.find().sort({ age: -1, name: 1 })

// 中文排序需注意 collation
db.users.find().sort({ name: 1 })
  .collation({ locale: "zh" })
✅ 建议
排序字段建议建立索引,否则大集合下性能很差。

MongoDB 索引

索引是 MongoDB 高性能查询的关键。

// 创建单字段索引
db.users.createIndex({ age: 1 })

// 复合索引
db.users.createIndex({ city: 1, age: -1 })

// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true })

// 查看索引
db.users.getIndexes()

// 删除索引
db.users.dropIndex("age_1")
💡 提示
索引命名规则:字段名_方向,如 city_1_age_-1。

MongoDB 聚合

聚合管道(Aggregation Pipeline)用于复杂的数据处理与分析,类似 SQL 的 GROUP BY + 多表关联:

// 按城市分组统计平均年龄
db.users.aggregate([
  { $match: { age: { $gt: 0 } } },       // 过滤
  { $group: {                            // 分组
      _id: "$city",
      avgAge: { $avg: "$age" },
      total: { $sum: 1 }
  } },
  { $sort: { total: -1 } },              // 排序
  { $limit: 10 }                         // 取前 10
])
阶段作用
$match过滤文档
$group分组聚合($sum/$avg/$max/$min/$push)
$project字段投影/计算
$lookup类似 LEFT JOIN 关联集合
$unwind展开数组
$sort / $limit排序 / 限制

MongoDB 复制(副本集)

副本集(Replica Set)是一组 mongod 实例,提供数据冗余与自动故障转移:

角色职责
Primary(主)接受所有写操作,定期写 oplog
Secondary(从)同步主节点数据,可读(需配置)
Arbiter(仲裁)只参与选举,不存数据
// 初始化副本集(任意节点执行一次)
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "node1:27017" },
    { _id: 1, host: "node2:27017" },
    { _id: 2, host: "node3:27017", arbiterOnly: true }
  ]
})

// 查看副本集状态
rs.status()

MongoDB 分片

分片(Sharding)将数据水平拆分到多台服务器,突破单机容量与性能上限。MongoDB 分片集群包含:

组件作用
mongos路由,客户端唯一入口
Config Server保存集群元数据
Shard存储数据分片(每片通常是一个副本集)
// 启用分片并选择片键
sh.enableSharding("shop")
sh.shardCollection("shop.orders", { user_id: "hashed" })
⚠️ 注意
片键选择至关重要:应选择基数高、分布均匀的字段(如 hashed 用户 ID),避免数据倾斜。

MongoDB 备份与恢复

MongoDB 提供 mongodump / mongorestore 逻辑备份工具:

# 备份整个数据库
mongodump --db shop --out /backup/shop_$(date +%F)

# 备份单个集合
mongodump --db shop --collection users --out /backup/

# 恢复
mongorestore --db shop /backup/shop_2026-08-12/shop/
✅ 建议
生产环境建议结合 mongodump 定时任务与副本集 oplog 实现接近实时的恢复能力。

MongoDB 监控

监控 MongoDB 健康状态常用内置命令与工具:

// 查看服务器状态
db.serverStatus()

// 查看当前操作
db.currentOp()

// 连接数统计
db.serverStatus().connections

// 慢查询阈值(默认 100ms)
db.setProfilingLevel(1, { slowms: 50 })
db.system.profile.find().sort({ ts: -1 }).limit(5)

官方图形化监控工具为 Atlas / Ops Manager,社区常用 MongoDB Compass 与 Prometheus exporter。

MongoDB Java

Java 使用官方 mongodb-driver-sync 驱动:

// Maven 依赖
// <dependency>
//   <groupId>org.mongodb</groupId>
//   <artifactId>mongodb-driver-sync</artifactId>
//   <version>4.11.0</version>
// </dependency>

MongoClient client = MongoClients.create(
    "mongodb://localhost:27017");
MongoDatabase db = client.getDatabase("shop");
MongoCollection<Document> users = db.getCollection("users");

// 插入
users.insertOne(new Document("name", "张三")
    .append("age", 25));

// 查询
Document d = users.find(Filters.gt("age", 20)).first();
System.out.println(d.toJson());
client.close();

MongoDB PHP

PHP 使用官方 mongodb 扩展(配合 mongodb/mongodb 库):

<?php
require 'vendor/autoload.php';
use MongoDB\Client;

$client = new Client("mongodb://localhost:27017");
$users = $client->shop->users;

// 插入
$users->insertOne(['name' => '张三', 'age' => 25]);

// 查询
$cursor = $users->find(['age' => ['$gt' => 20]]);
foreach ($cursor as $doc) {
    echo $doc['name'], PHP_EOL;
}

MongoDB 关系

MongoDB 中处理实体关系有两种策略:

策略做法适用场景
嵌入式(Embedded)把子文档直接放进父文档一对一、少量子记录
引用式(Reference)存 _id 关联另一集合一对多、多对多
// 嵌入式:订单含多个商品条目
{
  _id: ObjectId("..."),
  user: "张三",
  items: [
    { sku: "A1", qty: 2, price: 10 },
    { sku: "B2", qty: 1, price: 50 }
  ]
}

// 引用式:文章关联作者
{ title: "教程", authorId: ObjectId("...") }
💡 提示
经验法则:读多写少且不独立变化的数据用嵌入,独立增长的数据用引用。

MongoDB 数据库引用

DBRef 是一种标准化的引用格式,包含集合名与 _id,便于跨集合解析:

// DBRef 格式
{
  author: {
    $ref: "users",          // 目标集合
    $id: ObjectId("..."),   // 文档 _id
    $db: "shop"             // 可选,目标数据库
  }
}
⚠️ 注意
DBRef 需要应用层自行解析,并不自动 join。简单场景直接存 _id 引用即可,更高效。

MongoDB 覆盖索引查询

覆盖索引查询(Covered Query)指查询所需字段全部在索引中,无需回表读取文档,速度极快:

// 建立复合索引
db.users.createIndex({ name: 1, age: 1 })

// 查询只投影索引字段(且 _id: 0)
db.users.find(
  { name: "张三" },
  { name: 1, age: 1, _id: 0 }
).explain().queryPlanner.winningPlan
// 若出现 "IXSCAN" 且无 FETCH,即为覆盖查询

MongoDB 查询分析

用 explain() 分析查询执行计划,定位慢查询根因:

// 查看执行计划
db.users.find({ age: { $gt: 25 } }).explain("executionStats")

// 关键字段
// winningPlan: 选中的执行计划
// stage: COLLSCAN(全表扫描)或 IXSCAN(走索引)
// executionTimeMillis: 执行耗时
// nReturned / totalDocsExamined: 返回数/扫描数
✅ 建议
当 totalDocsExamined 远大于 nReturned 时,说明查询未走索引,需要创建合适的索引。

MongoDB 原子操作

MongoDB 对单文档的更新是原子的。借助 findAndModify 可实现计数器、队列等原子操作:

// 原子自增计数器
db.counters.findOneAndUpdate(
  { _id: "visits" },
  { $inc: { seq: 1 } },
  { upsert: true, returnDocument: "after" }
)
// 返回 { _id: "visits", seq: 1024 }

MongoDB 高级索引

MongoDB 支持多种高级索引类型:

// 文本索引(中文需指定分词器)
db.articles.createIndex(
  { content: "text" },
  { default_language: "zh" }
)

// 地理位置索引(2dsphere 支持球面查询)
db.places.createIndex({ loc: "2dsphere" })
db.places.find({
  loc: {
    $near: {
      $geometry: { type: "Point", coordinates: [116.4, 39.9] },
      $maxDistance: 5000
    }
  }
})

// 稀疏索引(只索引含该字段的文档)
db.users.createIndex({ vip: 1 }, { sparse: true })

// TTL 索引(自动过期删除,常用于日志/验证码)
db.logs.createIndex({ created_at: 1 }, { expireAfterSeconds: 86400 })

MongoDB 索引限制

使用索引时有几个限制需要了解:

限制说明
每个索引大小不能超过 1KB 的索引键(高版本已放宽)
索引总数每个集合索引数量过多会拖慢写入
内存索引必须能放入内存,否则性能骤降
正则非前缀正则查询无法使用普通索引
取反操作$not/$nin 往往无法走索引
✅ 建议
控制索引数量(一般 5 个以内),并定期用 explain() 检查索引是否真正被使用。

MongoDB ObjectId

ObjectId 是 MongoDB 默认的主键类型,12 字节结构如下:

字节含义
4 字节Unix 时间戳(秒)
5 字节随机值(机器 + 进程)
3 字节自增计数器
// 生成 ObjectId
const id = ObjectId()
// 5f8c9a1b2c3d4e5f6a7b8c9d

// 提取时间戳(可用于按时间排序/查询)
id.getTimestamp()
// ISODate("2020-10-18T07:33:47Z")
💡 提示
12 字节在 4 字节时间戳 + 5 字节随机 + 3 字节计数器保证全局唯一性,无需协调即可分布式生成。

MongoDB Map Reduce

MapReduce 是 MongoDB 早期的分布式计算框架,现在更推荐用聚合管道替代:

// 统计每个城市的人数(MapReduce 写法)
db.users.mapReduce(
  function() { emit(this.city, 1); },      // map
  function(key, values) {                  // reduce
    return Array.sum(values);
  },
  { out: "city_stats" }
)

// 等价聚合管道写法(更高效)
db.users.aggregate([
  { $group: { _id: "$city", count: { $sum: 1 } } }
])
⚠️ 注意
从 MongoDB 5.0 起 MapReduce 已被标记为弃用(deprecated),新代码一律使用聚合管道。

MongoDB 全文检索

MongoDB 内置文本索引,支持对字符串字段做全文搜索:

// 创建文本索引(可多字段)
db.articles.createIndex(
  { title: "text", content: "text" },
  { default_language: "zh" }
)

// 全文搜索
db.articles.find({
  $text: { $search: "MongoDB 教程" }
})

// 带评分排序
db.articles.find(
  { $text: { $search: "数据库" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
✅ 建议
文本索引适合轻量级搜索;复杂的中文搜索建议接入 Elasticsearch。

MongoDB 正则表达式

MongoDB 查询支持 PCRE 正则,用于模糊匹配:

// 查询 name 以"张"开头的用户
db.users.find({ name: /^张/ })

// 等价写法(正则对象)
db.users.find({ name: { $regex: "^张" } })

// 忽略大小写
db.users.find({ email: { $regex: "@qq.com$", $options: "i" } })
⚠️ 注意
正则查询无法利用普通索引(前缀锚定的除外),大集合上慎用。

MongoDB 管理工具

MongoDB 生态常用的管理工具:

工具用途
MongoDB Compass官方 GUI,可视化查看/编辑数据
mongosh命令行 Shell
MongoDB Atlas官方云托管服务
Robo 3T / Studio 3T第三方图形化工具
mongostat / mongotop命令行实时监控
# 实时监控读写频率
mongostat --host localhost:27017

# 查看集合操作耗时 TOP
mongotop --host localhost:27017

MongoDB GridFS

GridFS 用于在 MongoDB 中存储超过 16MB 的大文件,它将文件拆分为多个 chunks:

集合作用
fs.files文件元数据(名称、大小、md5)
fs.chunks文件内容分块(默认每块 255KB)
# 命令行上传/下载文件
mongofiles --db mydb put video.mp4
mongofiles --db mydb get video.mp4

# 列出文件
mongofiles --db mydb list
✅ 建议
GridFS 适合存储大文件元数据 + 大文件;小文件直接以 BinData 存文档内更高效。

MongoDB 固定集合

固定集合(Capped Collection)是大小固定的循环队列式集合,自动淘汰最旧数据:

// 创建 1MB 固定集合
db.createCollection("logs", {
  capped: true,
  size: 1048576,
  max: 5000
})

// 插入不受限制,满了自动覆盖最旧
db.logs.insertOne({ level: "info", msg: "hello" })
✅ 建议
固定集合写入快、自动过期,非常适合日志、消息队列场景;且支持 Tailable Cursor 持续监听新数据。

MongoDB 自动增长

MongoDB 没有内置自增 ID(默认 ObjectId),若业务需要自增序列(如订单号),需自行实现计数器:

// 维护一个计数器集合
db.counters.insertOne({ _id: "orderNo", seq: 0 })

// 原子获取下一个序列值
function nextSeq(name) {
  const doc = db.counters.findOneAndUpdate(
    { _id: name },
    { $inc: { seq: 1 } },
    { upsert: true, returnDocument: "after" }
  );
  return doc.seq;
}

// 使用
const no = nextSeq("orderNo")  // 1, 2, 3 ...
第 1 / 44 节