能,但需满足两个硬条件:查询字段与排序字段共同构成已存在的复合索引,且查询为前缀匹配;索引字段顺序和排序方向必须一致。

聚合前的 $sort 能不能走索引?
能,但必须满足两个硬条件:查询条件字段 + 排序字段共同构成一个已存在的复合索引,且查询条件是前缀匹配(比如索引是 {status: 1, createdAt: -1},那 {status: "active"} 可以用上,{createdAt: {$gt: ...}} 就不行)。
如果只建了单字段索引 {createdAt: -1},而聚合里又带了 {status: "active"} 查询条件,那 $sort 阶段大概率触发内存排序——你看到的慢,八成卡在这儿。
- 用
explain("executionStats")查看stage是否为IXSCAN,不是就说明没走索引 - 确保索引字段顺序和
$sort的方向一致(升序索引不支持降序$sort直接利用) - MongoDB 6.0 对时序集合做了聚簇索引优化,如果集合是
timeSeries类型,metaField+ 时间字段的组合索引效果更好
为什么加了 $limit 还那么慢?
因为 $limit 放错位置了。它得尽可能往前放,最好在 $sort 之后、$group 之前——否则 MongoDB 仍会把全部匹配文档拉进内存排序,再截取前 N 条。
典型错误写法:[{$match}, {$group}, {$sort}, {$limit}];正确顺序应是:[{$match}, {$sort}, {$limit}, {$group}]。
-
$limit在$group后只能限制分组数,对每个组内数组大小毫无影响 - 若目标是“每个用户只取最新 5 条订单”,必须先
$sort+$limit再$group,而不是靠$push后再切数组 - 6.0 支持
$setWindowFields,适合替代部分需要“组内 Top-N”的场景,避免大数组堆积
$push 内嵌 $sort 为什么没生效?
常见于想实现“按 userId 分组,每组内按 amount 降序存 orders 数组”,结果数组顺序还是乱的——根本原因是漏掉了前置 $sort,或 $push 写法不合法。
内嵌 $sort 不是万能的,它只控制该次 $push 的输入顺序,前提是上游数据已经按你期望的逻辑排好序。
- 必须先有全局
{$sort: {userId: 1, amount: -1}},再$group,否则$push的$sort字段找不到上下文 -
$push: {$each: ["$orderData"], $sort: {amount: -1}}中的$each必须是数组,哪怕只推一个字段 - 字段含点号(如
"items.price")时,$sort键名必须完整写成{"items.price": -1},不能简写 - 这个语法在 4.4+ 才支持,6.0 没问题,但别在 4.2 集群上试
allowDiskUse 是救命稻草,但别滥用
当聚合报错 "Sort exceeded memory limit",加 allowDiskUse: true 确实能跑通,但它只是把内存压力转成磁盘 I/O,延迟更高,且在事务中完全不可用。
真正要盯住的是数据规模是否合理:一个 $group 产生的数组有没有可能存几万条?业务上是否真的需要全量?
- 优先用
$limit下推压缩中间数据量,比开allowDiskUse更治本 - 若必须保留大量数据,考虑拆成两阶段:第一阶段
$group只收_id列表,第二阶段用find({_id: {$in: [...]}})查详情并由应用层排序 - 6.0 的时序集合
lastPoint优化对“取每个设备最新一条”类聚合很友好,可替代手写$sort+$group
最容易被忽略的一点:索引能帮上聚合的,仅限于 $match 和最前面的 $sort;后面的 $group、$project、$unwind 全部不走索引。别指望靠一个复合索引让整个管道飞起来——得一段一段压,从源头开始控数据量。


















