Appearance
MongoDB 索引、查询与聚合
这篇笔记专门整理 MongoDB 里最影响性能表现的一条主线:索引设计 -> 查询路径 -> 聚合管道 -> 分页与治理。
如果说文档模型决定了数据怎么放,那么索引和查询路径决定的就是:这些数据到底能不能被稳定、高效地取出来。
1. 为什么 MongoDB 也同样依赖索引
MongoDB 虽然不是关系型数据库,但只要进入真实业务场景,性能问题依然会落到类似的几个核心点上:
- 是否需要扫描大量文档
- 查询条件和排序是否命中索引
- 返回字段是否过多
- 聚合阶段是否过早放大数据量
所以不能因为 MongoDB 结构灵活,就把索引设计当成次要问题。
更具体一点:MongoDB 的灵活性解决的是建模问题,索引设计解决的是查询效率问题。
2. 🌟 常见索引类型
MongoDB 常见的索引类型包括:
| 类型 | 适合什么场景 | 需要特别注意什么 |
|---|---|---|
| 单字段索引 | 单一高频过滤字段 | 只能覆盖有限查询模式 |
| 复合索引 | 多条件过滤、排序 | 字段顺序非常重要 |
| 唯一索引 | 业务唯一约束 | 写入时会做唯一性校验 |
| 文本索引 | 文本搜索 | 更适合基础全文检索,不等于搜索引擎 |
| 地理空间索引 | 地图、坐标、附近搜索 | 需要配合地理查询操作符 |
| TTL 索引 | 自动过期数据 | 适合日志、会话、缓存类数据 |
| 多键索引 | 数组字段查询 | 数组展开后要注意索引行为 |
其中最常用、也最值得优先掌握的,还是:
- 单字段索引
- 复合索引
- 唯一索引
- TTL 索引
3. 🌟 复合索引为什么最容易设计错
MongoDB 中大量真实查询都不是只按一个字段过滤,因此复合索引往往比单字段索引更常见。
例如:
js
db.orders.find({
userId: 1001,
status: "PAID"
}).sort({ createTime: -1 })这种查询是否稳定,关键往往不在于“有没有索引”,而在于:索引字段顺序是否贴合过滤和排序路径。
例如下面这个复合索引:
js
{ userId: 1, status: 1, createTime: -1 }它通常就比三个单列索引更贴近这条查询。
设计复合索引时,可以优看这几件事:
- 高频过滤字段是谁
- 排序字段是谁
- 哪些条件经常一起出现
- 哪些字段区分度更高
如果索引顺序和真实查询路径不一致,即使建了索引,也可能仍然出现扫描量过大或者排序无法充分利用索引的情况。
4. 查询路径怎么判断是否合理
MongoDB 查询是否高效,核心看的是:
- 过滤条件是否先缩小了范围
- 排序是否沿着索引顺序执行
- 返回结果是否只取了需要的字段
- 是否避免了大范围跳页和无谓扫描
4.1 先过滤,再考虑排序和返回
一般来说,查询设计可以按这个顺序思考:
- 哪些字段决定过滤范围
- 结果需要按什么字段排序
- 最终到底需要返回哪些字段
如果一开始就 find({}) 再去排序、再去聚合,性能成本通常会很快放大。
4.2 Projection 也值得重视
例如:
js
db.users.find(
{ status: "ACTIVE" },
{ name: 1, email: 1, _id: 0 }
)只返回必要字段,不只是“看起来整洁”,它还能减少网络传输和对象反序列化成本。
4.3 深分页仍然是性能问题
像下面这种写法:
js
db.orders.find({ status: "PAID" }).skip(100000).limit(20)在数据量大时仍然会有明显代价,因为数据库往往还是要跳过前面大量结果。
更稳妥的做法通常是:
- 用有序字段做游标分页
- 用上一次结果的边界值继续翻页
- 尽量避免无限深页码跳转
5. 🌟 聚合管道应该怎么理解
MongoDB 的 Aggregation Pipeline 很像一条数据处理流水线。常见阶段包括:
$match$project$group$sort$limit$lookup$unwind
可以把它理解成:先尽量缩小输入,再做变形、分组、排序和关联。
一个很常见的统计例子:
js
db.orders.aggregate([
{ $match: { status: "PAID" } },
{ $group: { _id: "$userId", totalAmount: { $sum: "$amount" } } },
{ $sort: { totalAmount: -1 } },
{ $limit: 10 }
])这条链路的性能关键,通常不是 $group 语法会不会写,而是:
$match有没有尽量提前- 输入数据是不是已经足够小
- 排序和分组是不是处理了过大的中间结果
6. $lookup 能用,但不要把它当成天然的多表方案
MongoDB 支持 $lookup 做关联,但这不代表它就应该被默认当成“和关系型数据库一样自然的多表联查引擎”。
更稳妥的理解是:
$lookup适合有限度地补充关联能力- 如果大量核心查询都依赖复杂
$lookup,往往说明数据模型可能不够贴合文档思路 - 文档型数据库更强调把高频一起读取的数据尽量聚合在一起
所以看到 $lookup 时,更值得先问的是:这块数据到底应该引用,还是本来就应该嵌入到主文档里。
7. 怎么排查 MongoDB 查询慢
排查时可以优看这些方向:
- 是否命中了合适索引
- 查询条件是否过宽
- 排序是否借助了索引
- 是否返回了过多字段
- 是否用了高成本的
$lookup、$unwind、$group - 是否出现了深分页
一个比较实用的排查顺序是:
- 看高频查询长什么样
- 再看索引是否贴合这些查询
- 再看聚合链路是不是把过滤放晚了
- 最后再看是否需要改文档模型
也就是说,MongoDB 查询慢不一定先去怪“数据库不够快”,很多时候真正需要调整的是:
数据结构和查询路径之间有没有对齐。
8. 一份实用的索引与查询检查清单
可以优先检查这些问题:
- 高频查询有没有对应索引
- 复合索引顺序是否贴合过滤和排序
- 是否有大量只靠
skip的深分页 - 聚合管道是否把
$match提前 $lookup是必要补充,还是已经变成主查询路径- 是否只返回必要字段
- TTL、唯一索引、文本索引是否各自用在合适场景
MongoDB 的查询优化,本质上不是语法技巧,而是索引、数据分布和查询路径的共同设计。