一、业务应用场景
传统的关系型数据库 (MySQL) 在数据操作的三高需求以及应对 Web 2.0 的网站需求面前显得力不从心,而 MongoDB 可完美应对该类需求:
- High performance:对数据库高并发读写的需求
- Huge Storage:对海量数据的高效率存储和访问的需求
- High Scalability && High Avilaility:对数据库的高可扩展性和高可用性的需求
MongoDB 核心特性
- MySQL 需提前定义表结构、表关系,列固定,扩展代价高;MongoDB 为松散型结构,无需提前固定格式
- 文档型 NoSQL 数据库,适用于数据量大、写入操作频繁、价值较低(对事务性要求不高)的数据场景
二、数据类型
表格
| 数据类型 | 说明 | 示例 | 典型使用场景 |
|---|---|---|---|
| 字符串 (String) | 存储 UTF-8 编码的文本,长度无硬性限制 | "name": "MongoDB 指南 " |
名称、描述、文本内容 |
| 整数 (int/long) | int 为 32 位整数,long 为 64 位整数 (需显式声明) | "age": 25、"id": NumberLong("123456789") |
年龄、计数、ID (大数值) |
| 浮点数 (double) | 64 位双精度浮点数,默认数值类型 | "price": 99.99、"weight": 65.5 |
价格、重量等带小数的数值 |
| 高精度小数 (Decimal) | 高精度十进制数,适合金融计算 (避免浮点数精度丢失) | "balance": NumberDecimal("10000.00") |
金额、汇率等对精度敏感的场景 |
| 布尔值 (Boolean) | 表示真 (true) 或假 (false) | "isActive": true |
状态标记 (是否启用、是否完成) |
| Null | 表示空值或不存在的字段 | "address": null |
字段未设置或值为空的场景 |
| 日期 (Date) | 存储 UTC 时间,精确到毫秒 (基于 Unix 时间) | "createTime": new Date() |
创建时间、过期时间、生日 |
| 嵌入式文档 | 文档中嵌套另一个文档,形成层级结构 | "user":{"name":"张三","age": 30} |
表示包含关系 (如用户包含详细信息) |
| 数组 (Array) | 有序值列表,元素可混合多种类型 (包括数组 / 文档) | "tags":["数据库","NoSQL"] |
标签、列表数据 (如爱好、成绩) |
| ObjectId | 12 字节唯一标识符,默认作为文档主键_id |
"_id": ObjectId("60d21b4667d0d8992e610c85") |
文档唯一标识 |
| 二进制数据 | 存储二进制数据 (如图片、文件),适合小文件 | "avatar": BinData(,"base64数据") |
存储小型二进制资源 (头像、证书) |
| 正则表达式 | 存储正则表达式对象,用于字符串匹配查询 | "pattern": /^Mongo/ |
模糊查询条件定义 |
| 时间戳 (Timestamp) | 64 位值,前 32 位为时间戳,后 32 位为操作计数器 (内部用于复制) | "ts": Timestamp(1624300800, 1) |
MongoDB 内部复制和操作追踪 |
| 地理位置 (GeoJSON) | 存储地理坐标 (点、线、面),支持地理空间查询 | "location": { "type": "Point", "coordinates": [116.4, 39.9]} |
位置信息 (如用户地址、店铺位置) |
| 代码 (Code) | 存储 JavaScript 代码,存储简单脚本逻辑 | "script": Code("function() { return 1; }") |
简单脚本执行 |
| 代码 + 作用域 | 存储 JavaScript 代码及关联的作用域 (变量环境) | "script": CodeWithScope("function(){ return x; }", {x: 10}) |
带变量的脚本执行 |
备注 :_id为 MongoDB 自动生成的主键字段
三、示例数据库 / 集合 / 字段定义
数据库:articledb
集合:专栏文章评论 comment
表格
| 字段名称 | 字段含义 | 字段类型 | 备注 |
|---|---|---|---|
| _id | ID | ObjectId 或 String | Mongo 的主键字段 |
| articleid | 文章 ID | String | - |
| content | 评论内容 | String | - |
| userid | 评论人 ID | String | - |
| nickname | 评论人昵称 | String | - |
| createdatetime | 评论的日期时间 | Date | - |
| likenum | 点赞数 | Int32 | - |
| replynum | 回复数 | Int32 | - |
| state | 状态 | String | 0: 不可见;1: 可见 |
| parentid | 上级 ID | String | 如果为 0 表示文章的顶级评论 |
四、数据库操作
1. 选择和创建数据库
mongodb
use 数据库名
- 若数据库不存在则自动创建
- 示例:
use articledb - 规则:数据库名称需全部小写、不能有空格
- 特性:创建后先存放在内存,当数据库有数据时会自动持久化到磁盘
2. 查看数据库
mongodb
show dbs
# 或
show databases
系统默认数据库说明
- admin:权限角度的 root 库,添加至此的用户自动继承所有数据库权限
- local:数据永远不会被复制,用于存储本地单台服务器的任意集合
- config:Mongo 用于分片设置时内部使用,保存分片相关信息
3. 删除数据库
mongodb
db.getName() // 输出当前数据库名,确认是否为要删除的数据库
db.dropDatabase() // 删除已持久化的数据库
五、集合操作
集合类似关系型数据库中的表
1. 显示创建
mongodb
db.createCollection(名称)
# 示例
db.createCollection("mycollection")
2. 隐式创建
向不存在的集合中插入文档时,集合会自动创建:
mongodb
// 插入单条文档,隐式创建 comment 集合
db.comment.insertOne({
"articleid":"100000", "content":"今天天气真好,阳光明媚",
"userid":"1001",
"nickname":"Rose",
"likenum":NumberInt(10), "createdatetime":new Date(),
"sate":null
})
3. 查看当前库中的集合
mongodb
show collections
# 或
show tables
4. 删除集合
mongodb
db.collection.drop()
# 或
db.集合名.drop()
# 示例
db.mycollection.drop()
六、文档操作
(一)增:插入文档
1. 单个文档插入
mongodb
db.集合名.insertOne(
{ 文档内容 }, // 要插入的文档(JSON格式)
{ 可选配置参数 } // 可选,如写入确认级别等
)
# 示例
db.comment.insert( {
"articleid":"100001", "content":"今天天气真好,阳光明媚",
"userid":"1001",
"nickname":"Rose",
"createdatetime":new Date(), "likenum":NumberInt(10),
"sate":null
})
2. 多个文档插入
mongodb
db.集合名.insertMany([{文档1}, {文档2}, ...])
# 示例
db.comment.insertMany([
{
"_id": "1",
"articleid": "100001",
"content": "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。",
"nickname": "相忘于江湖",
"createdatetime": new Date("2019-08-05T22:08:15.522Z"),
"likenum": NumberInt(1000),
"userid": "1002",
"state": "1"
},
{
"_id": "2",
"articleid": "100002",
"content": "我夏天空腹喝凉开水,冬天喝温开水",
"userid": "1005",
"likenum": NumberInt(888),
"nickname": "伊人憔悴",
"createdatetime": new Date("2019-08-05T23:58:51.485Z"),
"state": "1"
}
]);
批量插入关键特性
insertMany()默认ordered: true,非原子操作:若某文档插入失败,已成功的保留,后续的停止插入- 若设置
ordered: false,会跳过失败文档,继续插入后续文档 - 示例:
_id冲突时,ordered: true后续文档不插入,ordered: false后续文档正常插入
插入通用规则
- Mongo 中的数字默认是 double 类型,存储整型需使用
NumberInt函数 - 插入当前日期使用
new Date() - 未指定
_id时,Mongo 会自动生成主键值 insert返回结果:包含插入成功状态 (acknowledged: true) 和插入文档的_id
(二)删:删除文档
1. 删除单个文档
mongodb
db.collection.deleteOne(
<filter>, // 查询条件,匹配要删除的文档
{
writeConcern: <document>, // 写入安全级别(可选)
collation: <document> // 字符串比较规则(可选)
}
)
# 示例1
db.comment.deleteOne({ articleid: "100005" });
# 示例2:指定写入安全级别
db.users.deleteOne(
{ name: "张三" },
{ writeConcern: { w: "majority", j: true, wtimeout: 1000 } }
)
writeConcern 参数说明 :{w:"majority",j:true,wtimeout:1000}
- 操作需同步到副本集的大多数节点
- 每个确认节点需将操作写入磁盘日志
- 1 秒内未满足上述条件则操作失败
2. 删除多个文档
mongodb
db.collection.deleteMany(
<filter>, // 查询条件,匹配所有要删除的文档
{
writeConcern: <document>, // 可选
collation: <document> // 可选
}
)
# 示例:删除所有点赞数小于100的文档
db.comment.deleteMany({ likenum: { $lt: 100 } });
3. 查询并删除单个文档
mongodb
db.collection.findOneAndDelete(
<filter>, // 查询条件
{
projection: <document>, // 指定返回的字段(可选)
sort: <document>, // 排序规则(多个匹配时按排序删除第一个)
writeConcern: <document>, // 可选
collation: <document> // 可选
}
)
# 示例:删除articleid为100005且likenum最小的文档,并返回该文档
db.comment.findOneAndDelete(
{ articleid: "100005" },
{ sort: { likenum: 1 } }
)
4. 清空集合文档(保留集合结构)
mongodb
db.comment.remove({});
(三)改:更新文档
核心语法:
mongodb
// 更新单个匹配文档
db.collection.updateOne(<filter>, <update>, <options>)
// 更新所有匹配文档
db.collection.updateMany(<filter>, <update>, <options>)
三个核心参数说明
- Filter(筛选器) :等同于
find()的第一个参数,决定更新的文档;updateOne仅修改第一个匹配文档,updateMany修改所有匹配文档 - Update(更新操作) :必须使用更新操作符(以 $ 开头),禁止 "裸奔" 更新
- Options(配置项) :高阶配置,如
upsert(无则加)、hint(强制索引)等
常用更新操作符
表格
| 操作符 | 作用 | 示例 |
|---|---|---|
| $set | 设定值,字段不存在则创建,存在则修改 | {$set:{"age":18,"is_student":true}} |
| $unset | 删除字段 | {$unset:{"temporary_field":""}} |
| $inc | 自增 / 自减数值 | {$inc:{"score":10,"money":-5}} |
| $rename | 重命名字段 | {$rename:{"sex":"gender"}} |
| $push | 向数组追加元素(允许重复) | {$push:{"comments":"好文章"}} |
| $pull | 从数组剔除指定元素 | {$pull:{"comments":"这是垃圾广告"}} |
| $currentDate | 更新时间字段 | {$currentDate:{"lastdified":true}} |
| $mul | 数值乘法运算 | {$mul:{"likenum":1.2}} |
| $max | 仅当新值大于当前值时更新 | {$max:{"likenum":5000}} |
| $min | 仅当新值小于当前值时更新 | {$min:{"likenum":3000}} |
| $addToSet | 向数组追加不重复元素 | {$addToSet:{"tags":"生活小贴士"}} |
| $pop | 删除数组第一个 / 最后一个元素 | {$pop:{"arr":1}}(删最后)、{$pop:{"arr":-1}}(删最前) |
| $slice | 配合 $push 使用,限制数组长度 | {$push:{"logs":{"$each":["log3"],$slice:-3}}} |
| $setOnInsert | 仅在 upsert 插入新文档时生效 | {$setOnInsert:{"age":22}} |
关键更新示例
- 多操作符组合更新
mongodb
db.comment.updateOne(
{ _id: "5" },
{
$set: { content: "研究表明,刚烧开的水不宜立即饮用(避免烫伤)", authorLevel: "VIP" },
$inc: { likenum: 500 },
$rename: { "nickname": "username" },
$mul: { likenum: 1.2 },
$max: { likenum: 5000 },
$push: { tags: "健康知识" },
$addToSet: { tags: "生活小贴士" }
}
);
- 批量更新
mongodb
db.comment.updateMany(
{ articleid: "100005", state: "1" }, // 查询条件
{
$set: { state: "2" },
$inc: { likenum: 50 },
$rename: { "nickname": "username" }
},
{ upsert: false } // 无匹配时不插入
);
- 无则加(upsert)
mongodb
// 找到name为Eve的用户则更新,未找到则插入
db.users.updateOne(
{ name: "Eve" },
{ $set: { age: 22, email: "eve@example.com" } },
{ upsert: true }
)
- 数组专用更新
mongodb
// 向数组批量追加元素
db.users.updateOne( { _id: 1 }, { $push: { hobbies: { $each: ["gaming", "painting"] } } } )
// 追加不重复元素
db.users.updateOne({id:1}, {$addToSet:{"tags":"帅"}})
// 按条件删除数组元素
db.users.updateOne( { _id: 2 }, { $pull: { scores: { score: 70 } } } )
// 追加后保留最后3个元素
db.logs.updateOne(
{_id:1},
{ $push: { "logs":{ $each:["log3","log4"], $slice:-3 } } }
)
5. 替换文档
用新文档完全替换第一个符合条件的文档(_id字段除外):
mongodb
db.collection.replaceOne(<filter>, <replacement>, <options>)
# 示例
db.users.replaceOne(
{ name: "Bob" },
{ name: "Robert", age: 26, status: "updated" }
)
更新返回结果
updateOne()和updateMany()返回操作状态信息(匹配数、修改数),不返回被修改的文档本身。
(四)查:查询文档
1. 基本查询语法
mongodb
db.集合.find(<query>,<fields>).sort(key).skip(m).limit(n)
<query>:查询条件(等同于 SQL 的 where)<fields>:投影,指定返回 / 隐藏的字段(等同于 SQL 的 select)sort(key):排序,1升序,-1降序skip(m):跳过前 m 条文档limit(n):限制返回 n 条文档
2. 游标特性
执行var result = db.集合.find()时,返回的是游标而非直接返回所有数据:
- 懒加载:仅遍历(如
forEach()、toArray())时才从数据库批量获取数据 - 分批获取:默认每次获取 101 个文档或 1MB 数据(取较小值)
- 生命周期:创建→遍历→耗尽(一次性,无法回头)→关闭(超时默认 10 分钟)
findOne():直接返回第一个符合条件的文档(对象形式),无匹配则返回null
3. 条件查询
(1)比较运算符
表格
| 操作符 | 含义 | 类比 SQL | 示例 |
|---|---|---|---|
| $eq | 等于 | = | {likenum:{$eq:1000}} |
| $ne | 不等于 | ≠ | {likenum:{$ne:3000}} |
| $gt | 大于 | > | {likenum:{$gt:2000}} |
| $gte | 大于等于 | ≥ | {likenum:{$gte:1000}} |
| $lt | 小于 | < | {likenum:{$lt:100}} |
| $lte | 小于等于 | ≤ | {likenum:{$lte:1000}} |
| $in | 在指定数组内 | IN | {userid:{$in:["1002","1003"]}} |
| $nin | 不在指定数组内 | NOT IN | {state:{$nin:["0","2"]}} |
(2)逻辑运算符
表格
| 操作符 | 含义 | 示例 |
|---|---|---|
| $and | 逻辑与(可省略) | {likenum:{$gte:1000},$lte:2000} |
| $or | 逻辑或(数组包裹条件) | {$or:[{userid:"1002"},{state:"0"}]} |
| $not | 逻辑非 | {likenum:{$not:{$gt:30}}} |
| $nor | 逻辑或非(都不满足) | - |
(3)其他常用运算符
表格
| 操作符 | 含义 | 示例 |
|---|---|---|
| $exists | 字段是否存在 | {score:{$exists:true}}(存在 score 字段) |
| $regex | 正则表达式匹配 | {content:{$regex:/水/}} |
(4)数组查询
表格
| 查询方式 | 语法 | 说明 |
|---|---|---|
| 完全匹配 | {skills: ["Java", "Python"]} |
顺序、元素数量必须完全一致 |
| 包含查询 | {skills: "Java"} |
数组包含指定元素即可(最常用) |
| 全包含 | {skills:{$all:["Java","Go"]}} |
数组同时包含所有指定元素,顺序无关 |
(5)日期查询
mongodb
// 查询2019年8月6日之后发布的评论
db.comment.find({ createdatetime: { $gt: new Date("2019-08-06") } })
4. 分页查询
基础分页(适合小数据量)
mongodb
// 公式:skip=(页码-1)*每页条数,limit=每页条数
// 示例:查询articleid=100001的评论,按时间倒序,每页2条,第2页
db.comment.find({ articleid: "100001" })
.sort({ createdatetime: -1 })
.skip(2)
.limit(2);
注意 :skip(n)当 n 很大时性能下降,需扫描并跳过大量文档。
高效分页(适合大数据量,基于条件)
利用上一页最后一条数据的唯一字段(如_id)作为查询条件,避免skip:
mongodb
// 示例:上一页最后一条文档_id为"2",查询下一页
db.comment.find({
articleid: "100001",
_id: { $gt: "2" }
})
.sort({ _id: 1 })
.limit(2);
5. 投影(Projection)
核心铁律 :除_id外,不能混用0(隐藏)和1(显示),支持白名单 (只显示)和黑名单(只隐藏)。
白名单模式(只显示指定字段)
mongodb
// 显示name和score,隐藏其他(_id默认显示)
db.students.find({}, {name:1, score:1})
// 显示name和score,隐藏_id
db.students.find({}, {name:1, score:1, _id:0})
黑名单模式(隐藏指定字段)
mongodb
// 隐藏skills,显示其他所有字段
db.students.find({}, {skills:0})
6. 正则查询
mongodb
// 完整语法
db.collection.find({ 字段名: { $regex: /正则表达式/, $options: "匹配选项" } })
// 简写
db.collection.find({ 字段名: /正则表达式/ })
常用示例
mongodb
db.comment.find({ content: /^研究表明/ }) // 以"研究表明"开头
db.comment.find({ content: /水|烫/ }) // 包含"水"或"烫"
db.comment.find({ content: /m/i }) // 包含m/M(忽略大小写)
db.comment.find({ content: /烫嘴。$/ }) // 以"烫嘴。"结尾
db.comment.find({ content: { $not: /专家/ } }) // 不包含"专家"
匹配选项
i:忽略大小写m:多行模式,^/$匹配每行开头 / 结尾x:忽略正则中的空白字符
七、索引
1. 索引基础
- 作用:支持高效查询,避免全集合扫描
- 数据结构:MongoDB 使用B-Tree(MySQL 为 B+Tree)
- 核心原则:最左前缀原则、等值查询字段放复合索引前
2. 索引类型
表格
| 索引类型 | 说明 | 适用场景 |
|---|---|---|
| 单字段索引 | 单个字段的升序 / 降序索引 | 单字段等值、排序查询 |
| 复合索引 | 多个字段的组合索引,按字段顺序排序 | 多字段联合查询、排序 |
| 地理空间索引 | 包含二维索引、二维球面索引 | 地理坐标查询(如店铺位置、用户地址) |
| 文本索引 | 支持字符串内容搜索,自动过滤停止词、提取词干 | 全文检索场景 |
| 哈希索引 | 对字段值的散列进行索引,值分布随机 | 基于散列的分片,仅支持等值查询 |
3. 索引失效场景
- 索引字段使用函数 / 运算(如
{ $where: "this.age + 1 > 25" }) - 正则表达式非前缀匹配(如
/张三/,仅/^张三/可利用索引) - 隐式类型转换(如索引字段是数字,查询用字符串
{age: "25"}) - 使用
$not/$ne(视场景而定,可能失效) - 多字段查询未匹配复合索引的最左前缀
4. 索引操作管理
(1)查看索引
返回集合中所有索引的数组:
mongodb
db.集合.getIndexes()
# 示例返回结果
[
{
"v": 2, // 索引版本
"key": { "_id": 1 }, // 索引字段及排序方向(1升序,-1降序)
"name": "_id_", // 索引名称(系统自动创建_id索引)
"ns": "articledb.comment" // 数据库.集合
}
]
(2)创建索引
mongodb
db.集合.createIndex(keys, options)
keys :索引字段及排序方向,如{userid:1, likenum:-1}options:索引配置,常用如下:
表格
| 选项 | 说明 |
|---|---|
| unique | 唯一索引,确保字段值唯一,null视为一个值 |
| sparse | 稀疏索引,仅包含有该字段的文档 |
| expireAfterSeconds | TTL 索引,设置文档过期时间(字段需为 Date 类型) |
创建示例
mongodb
// 单字段索引
db.comment.createIndex({userid:1})
// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 复合索引
db.comment.createIndex({articleid: 1, likenum: -1, createdatetime: 1})
// 唯一稀疏索引
db.users.createIndex({ email: 1 }, { unique: true, sparse: true })
// TTL索引(1小时后过期)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
(3)移除索引
mongodb
// 按索引字段删除
db.集合.dropIndex({字段名:排序方向})
// 按索引名称删除
db.集合.dropIndex("索引名")
# 示例
db.comment.dropIndex({userid:1})
db.comment.dropIndex("userid_1_nickname_-1")
(4)分析查询性能
通过explain()查看查询是否使用索引、扫描方式等:
mongodb
db.集合.find(query,options).explain(options)
5. 复合索引核心原则
(1)最左前缀原则
查询条件必须匹配复合索引中最左边的 N 个字段 ,索引才能有效使用。示例:索引{age:1, name:1}
表格
| 查询条件 | 是否使用索引 | 原因 |
|---|---|---|
{age:25} |
是 | 匹配最左前缀 age |
{age:25, name:"Charlie"} |
是 | 匹配所有索引字段,效率最高 |
{name:"Alice"} |
否 | 跳过最左前缀 age,全集合扫描 |
{age:{$gt:20}, name:"Bob"} |
是 | 匹配最左前缀,范围查询后过滤 |
(2)等值查询 VS 范围查询
复合索引中,等值查询字段放前面,范围查询字段放后面,提升效率。
- 推荐:索引
{age:1, name:1}+ 查询{age:25, name:/^C/} - 不推荐:索引
{name:1, age:1}+ 查询{age:25, name:/^C/}
(3)排序与索引方向
多字段排序时,排序方向需与复合索引方向完全匹配 / 相反 ,才能利用索引。示例:索引{age:1, name:1}
- 支持:
sort({age:1, name:1})、sort({age:-1, name:-1}) - 不支持:
sort({age:1, name:-1})
八、聚合
聚合操作用于处理数据记录并返回计算结果(统计、求和、分组等),将多个文档的数值分组计算,返回单个 / 多个结果。
聚合类型
- 单一作用聚合:简单的聚合函数,如计数、去重
- 聚合管道:核心聚合框架,多阶段流水线处理数据(推荐)
- MapReduce:分 map 和 reduce 阶段,适用于复杂聚合场景
(一)单一作用聚合
简单高效,适用于常见的聚合需求,仅作用于单个集合。
常用函数
表格
| 函数 | 说明 | 示例 |
|---|---|---|
| estimatedDocumentCount() | 忽略条件,返回集合文档总数(估算,速度快) | db.books.estimatedDocumentCount() |
| countDocuments() | 统计匹配条件的文档数(精确,全表扫描) | db.users.countDocuments({age:{$lt:30}}) |
| distinct() | 查找指定字段的不同值,返回数组 | db.books.distinct("type", {favCount:{$gt:90}}) |
(二)聚合管道(Aggregation Framework)
MongoDB 聚合的核心,基于流水线概念,文档依次经过多个 ** 阶段(Stage)** 处理,每个阶段的输出作为下一个阶段的输入,最终返回结果。
核心语法
mongodb
db.collection.aggregate([
{ $stage1: { <expression> } },
{ $stage2: { <expression> } },
// ... 更多阶段
])
常用管道阶段(Stage)
表格
| 阶段 | SQL 等价 | 作用 | 示例 |
|---|---|---|---|
| $match | WHERE | 过滤文档,尽可能放在管道最前面 | {$match: {age:{$gt:25}, gender:"male"}} |
| $project | AS | 重塑文档结构,选择 / 重命名 / 计算字段 | {$project: {_id:0, name:1, isAdult:{$gte:["$age",18]}}} |
| $sort | ORDER BY | 对文档排序 | {$sort: {age:-1, name:1}} |
| $limit | LIMIT | 限制返回文档数量 | {$limit:10} |
| $skip | SKIP | 跳过指定数量文档 | {$skip:5} |
| $count | COUNT | 统计管道内文档数,返回单个计数字段 | {$count: "total_male_users"} |
| $addFields | - | 为文档添加新字段,保留原有字段 | {$addFields: {isAdult:{$gte:["$age",18]}}} |
| $group | GROUP BY | 按条件分组,执行聚合计算 | {$group: {_id:"$userId", total:{$sum:1}}} |
| $lookup | LEFT OUTER JOIN | 左连接另一个集合,结果为数组 | 见下文示例 |
| $unwind | - | 将数组字段展开为多个文档 | 见下文示例 |
关键聚合阶段示例
1. $group 分组聚合
核心语法:
mongodb
{
$group: {
_id: <分组依据>, // 必选,字段名/表达式/null(全局分组)
<新字段>: { <聚合操作符>: <表达式> }, // 可选,聚合计算
...
}
}
常用聚合操作符:
表格
| 操作符 | 含义 | 类比 SQL |
|---|---|---|
| $sum | 求和 | SUM |
| $avg | 计算平均值 | AVG |
| $max | 取最大值 | MAX |
| $min | 取最小值 | MIN |
| $first | 取每组第一个文档 | LIMIT 0,1 |
| $last | 取每组最后一个文档 | - |
| $push | 将字段值加入数组(允许重复) | - |
| $addToSet | 将字段值加入集合(无重复) | - |
分组示例:按用户 ID 统计订单信息
mongodb
db.orders.aggregate([
{
$group: {
_id: "$userId",
orderCount: { $sum: 1 }, // 订单数
totalAmount: { $sum: { $multiply: ["$price", "$quantity"] } } // 总消费
}
},
{
$project: {
userId: "$_id",
orderCount: 1,
totalAmount: 1,
_id: 0
}
}
])
2. $lookup 左连接
核心语法:
mongodb
{
$lookup: {
from: "<目标集合>", // 要连接的集合
localField: "<当前集合匹配字段>", // 本集合的关联字段
foreignField: "<目标集合匹配字段>", // 目标集合的关联字段
as: "<结果数组字段名>" // 存储匹配结果的数组
}
}
示例:订单集合关联产品集合
mongodb
// 订单集合orders + 产品集合products
db.orders.aggregate([
{
$lookup: {
from: "products",
localField: "productId",
foreignField: "_id",
as: "productDetails"
}
}
])
特性 :匹配结果为数组,无匹配则返回空数组[]。
3. $unwind 数组展开
将数组字段展开为多个文档,每个文档包含数组的一个元素,配合$lookup使用:
mongodb
db.orders.aggregate([
{ $lookup: { from: "products", localField: "productId", foreignField: "_id", as: "productDetails" } },
{ $unwind: "$productDetails" } // 展开productDetails数组
])
特性 :若数组为空,$unwind会丢弃该文档。
聚合管道数学操作符
用于数值计算,常配合$project/$addFields/$group使用:
$abs:绝对值$add:求和$subtract:求差$multiply:乘积$divide:求商$mod:取余$sqrt:平方根
(三)MapReduce
分map 和reduce两个阶段,适用于复杂的聚合场景,略(日常开发中聚合管道已满足大部分需求)。
九、findAndModify 原子操作
将查找 和修改 (/ 删除)合并为一个原子操作,适用于基于文档当前状态更新并立即获取结果的场景(如计数器、任务队列)。
核心语法
mongodb
db.collection.findAndModify({
query: <document>, // 查询条件
sort: <document>, // 多匹配时排序,仅修改第一个
update: <document>, // 更新操作(与remove互斥)
fields: <document>, // 指定返回字段
new: <boolean>, // 是否返回更新后文档,默认false
upsert: <boolean>, // 无匹配时是否插入,默认false
remove: <boolean> // 是否删除找到的文档,默认false(与update互斥)
});
关键示例
- 更新并返回最新文档(库存扣减)
mongodb
var result = db.products.findAndModify({
query: { name: "iPhone" },
update: { $inc: { stock: -1 } },
new: true
});
printjson(result); // 返回更新后的文档
- 无则加(upsert)
mongodb
var result = db.products.findAndModify({
query: { name: "Google Pixel" },
update: {
$setOnInsert: { price: 5499 },
$inc: { stock: 20 }
},
new: true,
upsert: true
});
findAndModify VS updateOne 核心区别
表格
| 特性 | findAndModify | updateOne/updateMany |
|---|---|---|
| 核心功能 | 查找并原子修改 / 删除一个文档 | 原子修改一个 / 多个匹配文档 |
| 返回结果 | 返回被修改 / 删除的文档(前 / 后版本) | 返回操作状态(匹配数、修改数),不返回文档 |
| 文档选择 | 多匹配时通过 sort 指定修改哪个 | 多匹配时修改第一个(内部自然顺序,无法指定) |
| 适用场景 | 1. 更新后需立即获取文档2. 基于当前状态更新3. 计数器 / 任务队列 | 1. 只需更新,无需获取文档2. 明确指定文档更新3. 批量更新 |
| 原子性 | 操作本身是原子的 | 操作本身是原子的 |