$sort阶段必须紧贴$match后才能利用索引,否则触发内存排序;复合索引字段顺序、方向及collation须与$sort完全一致,且$limit必须置于$sort之后。

$sort 阶段必须紧贴 $match 后,否则索引失效
MongoDB 的 $sort 要用上索引,前提是它前面的 $match 已经把数据范围大幅缩小,且排序字段在索引中位置靠前。如果 $sort 放在管道开头,或夹在 $project/$addFields 之后,数据库就无法利用索引做排序,只能走内存排序(in-memory sort),一旦数据量超 100MB 就会报 "Sort exceeded memory limit"。
常见错误写法:
-
db.col.aggregate([{$sort: {status: 1, amount: -1}}, {$match: {status: "active"}}])—— 先全表排序,再过滤,完全浪费索引 -
db.col.aggregate([{$match: {status: "active"}}, {$project: {amount: 1, status: 1, total: {$sum: "$items"}}}, {$sort: {total: -1}}])——total是计算字段,不可能有对应索引
正确顺序是:$match → $sort → 其他阶段。只要 $match 过滤条件能命中索引,$sort 字段又在该索引中按相同顺序声明,就能触发索引扫描。
复合索引字段顺序必须和 $sort 完全一致
假设你要执行 {$sort: {a: 1, b: -1, c: 1}},那么有效索引只能是 {a: 1, b: -1, c: 1} 或 {a: 1, b: -1, c: 1, _id: 1}。哪怕只调换一个方向,比如建了 {a: 1, b: 1, c: 1},MongoDB 也不会用它服务 b: -1 的排序需求。
注意几个边界情况:
- 升序索引
{a: 1}可服务{a: -1}排序,但混合方向时不行 - 索引
{a: 1, b: -1}可服务{a: 1, b: -1}和{a: -1, b: 1},但不能服务{a: 1, b: 1} - 如果
$sort只用了前缀字段(如索引是{a: 1, b: -1, c: 1},但只排{a: 1, b: -1}),依然能命中
字符串数字排序必须配 collation 索引
字段值是 "10"、"2" 这类字符串时,{$sort: {price_str: 1}} 默认按字典序排,结果是 "10", "2", "20"。要数值序,必须同时满足两个条件:
- 聚合调用加
.collation({locale: "en", numericOrdering: true}) - 对应字段的索引也得带同样 collation:
db.col.createIndex({price_str: 1}, {collation: {locale: "en", numericOrdering: true}})
漏掉任一条件,排序就会退化为内存排序,且无法利用索引加速。PyMongo 中写法示例:collection.aggregate(pipeline).collation({"locale": "en", "numericOrdering": True})。
$limit 必须放在 $sort 之后,且不能干扰索引使用
$limit 本身不参与索引匹配,但它影响查询计划是否启用索引排序。如果写成 {$sort: {...}}, {$limit: 10},MongoDB 会尝试用索引直接取前 10 条,这是最优路径;但如果误写成 {$limit: 10}, {$sort: {...}},就只对这 10 条排序,结果错乱,且完全绕过索引排序逻辑。
还要注意:$limit 不能“提前”压低数据量来“帮助”$sort——因为 $sort 是阻塞式阶段,必须等上游所有文档到达才开始工作,$limit 在它前面毫无意义。
真正影响性能的关键点往往不在语法对错,而在索引定义是否与聚合阶段的字段顺序、方向、collation 三者严丝合缝。少一个匹配项,就退回内存排序;多一个无关字段,就增加索引体积和写入开销。


















