$sort没走索引是因为聚合管道未满足索引下推的三个硬条件:$match与$sort字段必须共用同一复合索引且$match为前缀匹配;索引字段顺序须与pipeline中$match(等值)、范围、$sort严格一致;$sort必须紧接$match后,中间不能有其他stage。

为什么$sort没走索引?三个硬条件缺一不可
聚合管道中 $sort 没走复合索引,不是索引建得不对,而是 pipeline 结构没满足 MongoDB 的索引下推规则。它只在极严格条件下才把排序下推到存储层,否则一律走内存排序。
必须同时满足以下三点:
-
$match条件字段 +$sort字段共同构成一个已存在的复合索引,且$match是前缀匹配(例如索引{status: 1, createdAt: -1},查询{status: "done"}可以;但{createdAt: {$gt: ISODate("...")}}就不行) - 索引字段顺序必须和 pipeline 顺序一致:先
$match等值字段,再范围字段,最后才是$sort字段 -
$sort必须紧跟在$match后面,中间不能插$project、$addFields、$lookup等任何 stage
hint() 在聚合里根本不起作用
你写 db.orders.aggregate([]).hint({status: 1, createdAt: -1}) 会直接报错 undefined is not a function,或者静默忽略——因为 hint() 只对 find() 有效,聚合不支持。
想强制走某个索引,唯一可行路径是让 pipeline 本身符合下推条件,或改用 find() + 游标操作替代部分逻辑。比如“查最新 10 条”这种需求,用 db.orders.find({status: "shipped"}).sort({createdAt: -1}).limit(10) 显式加 .hint({status: 1, createdAt: -1}) 是有效的,而聚合里不能这么干。
验证索引是否真被用了?别只看有没有 IXSCAN
执行 explain("executionStats") 时,重点不是有没有 "stage": "IXSCAN",而是它的位置和上下文:
- 如果
IXSCAN出现在最外层executionStages的inputStage里,且后面紧跟着"stage": "SORT",说明索引只用于过滤,排序仍走内存 - 真正生效的表现是:
"stage": "IXSCAN"直接输出已排序结果,整个 pipeline 里找不到SORTstage,或者sortStage的"works"为 0 -
allowDiskUse: true反而可能干扰优化器判断,建议先去掉再测
即使结构正确还慢?检查 collation 和数字字符串排序
即使 $match 和 $sort 顺序、索引都对了,还是没走索引?大概率卡在 collation 不匹配:
- 字符串字段启用
numericOrdering: true排序时,对应索引也必须用相同collation创建,否则无法命中 - 大小写敏感排序(如
{locale: "en", strength: 2})同理:查询加了 collation,索引也得建在相同配置下 - PyMongo 中写法示例:
collection.aggregate(pipeline).collation({"locale": "en", "numericOrdering": True})
真正难处理的,是那些字段结构松散、嵌套深、又无法加索引的场景——这时候 $project 的字段裁剪和 $match 前置比索引更关键。











