MongoDB复合索引字段顺序和方向必须与sort()完全一致,否则无法走索引排序,将触发内存排序甚至Sort exceeded memory limit错误;等值查询字段需置前,范围查询后字段丧失排序能力;须用_id等唯一字段兜底保障排序稳定性。

复合索引字段顺序和方向必须与 sort() 完全一致
MongoDB 不会自动对齐排序方向,sort({ a: 1, b: -1, c: 1 }) 要走索引加速,索引就必须是 { a: 1, b: -1, c: 1 }。哪怕只错一个字段的方向(比如把 b: -1 写成 b: 1),或调换任意两个字段位置,MongoDB 就无法用该索引完成完整排序,大概率退化为内存排序,甚至触发 Sort exceeded memory limit 错误。
常见错误现象:
- 建了
{ status: 1, priority: 1, createdAt: -1 },但查询是.sort({ status: 1, createdAt: -1, priority: 1 })—— 字段顺序不匹配,索引失效 - 建了
{ timestamp: 1 },却执行.sort({ timestamp: -1 })—— 单字段方向不一致,仍可能不走索引(尤其在聚合 pipeline 中) - 只给最后一个字段写
-1,前面字段省略方向(如{ a: 1, b: 1, c: -1 }),MongoDB 不会把前两个默认补成1,而是按你写的字面量解析 —— 这个写法本身合法,但若你本意是a: 1, b: -1, c: -1,就完全错了
升序/降序混合时,每个字段都必须显式声明方向
MongoDB 的复合索引不接受“隐式方向”。1 和 -1 是独立指定的,不是默认值继承关系。即使你只关心最后一个字段倒序,前面所有字段也得明确写出方向,否则语义不清、易出错。
正确做法:
-
db.orders.createIndex({ userId: 1, createdAt: -1, status: 1 })✅ 显式、顺序、方向三者全对 -
db.orders.createIndex({ userId: 1, createdAt: -1, status: -1 })✅ 合法,只要和你的sort()一致即可 -
db.orders.createIndex({ userId: 1, createdAt: -1, status: 1 })和.sort({ userId: 1, createdAt: -1, status: -1 })❌ 方向冲突,索引不用于排序
注意:createIndex() 成功返回 ok: 1 ≠ 查询真用了这个索引。务必用 .explain("executionStats") 验证是否出现 IXSCAN 并无 SORT stage。
等值查询字段要放前面,范围查询会截断排序能力
如果你的查询带 .find({ status: "done", createdAt: { $gte: ISODate("...") } }),再 .sort({ priority: 1 }),那索引设计优先级是:status(等值)→ createdAt(范围)→ priority(排序)。但注意:一旦出现范围查询($gt/$gte/$lt 等),它之后的所有字段就**失去排序能力**——即 priority 字段无法被该索引用于排序,MongoDB 会回退到内存排序。
所以更合理的索引是:
-
{ status: 1, priority: 1, createdAt: -1 }—— 如果你常查status+ 按priority排序,且createdAt只用于过滤(不参与排序) - 若必须按
createdAt倒序,又带status等值条件,则索引应为{ status: 1, createdAt: -1 },此时priority排序需靠_id兜底(见下一条)
用 _id 或其他唯一字段兜底保障排序稳定性
即使复合索引字段顺序和方向全对,只要存在多个文档在这些字段上取值完全相同,排序结果仍可能不稳定(尤其在 aggregate 中)。MongoDB 不保证相同排序键下的物理顺序。
解决办法是加一个唯一字段作为最终排序依据:
-
.sort({ status: 1, createdAt: -1, _id: 1 })——_id天然唯一、递增(ObjectId 时间戳部分),适合兜底 - 对应索引必须包含它:
{ status: 1, createdAt: -1, _id: 1 },否则仍会触发内存排序 - 别用非唯一字段(如
name)兜底,否则问题照旧;也别漏掉它 —— 少一个字段,整个排序链就断了
最麻烦的地方往往不在建索引本身,而在于你没意识到:排序稳定性和索引是否“被用上”,是两个独立又强耦合的问题。方向写错、字段漏写、兜底缺失,任一环节出问题,分页结果就可能翻车。

















