sort() + limit() 慢是因为未建对索引时需全表扫描再内存排序;必须创建覆盖查询条件、sort字段及投影字段的复合索引,且字段顺序为等值查询字段在前、sort字段紧随其后、方向一致。

为什么 sort() + limit() 会慢?
没建对索引时,MongoDB 会先全表扫描匹配文档,再内存排序、截断——一旦数据量超内存或文档数多,cursor.explain("executionStats") 就会显示 "stage": "SORT" 和大量 "nReturned" 与 "totalDocsExamined" 不一致。这不是“加个索引就行”,而是必须让索引覆盖 sort 字段 + 查询条件 + projection(如果用了 .project())。
复合索引字段顺序怎么排?
顺序决定索引能否跳过排序阶段。核心原则:查询条件字段在前,sort 字段紧接其后,且方向一致(1 或 -1)。例如查“2024年销售额前10的北京门店”:
db.stores.find(
{ "region": "Beijing", "year": 2024 },
{ "name": 1, "sales": 1 }
).sort({ "sales": -1 }).limit(10)
对应索引必须是:
db.stores.createIndex({ "region": 1, "year": 1, "sales": -1 })
- 不能把
sales放最前——那样无法高效过滤region和year - 不能混用方向(如
{"region": 1, "sales": -1})——当有等值查询时,MongoDB 只能利用索引中连续同向部分做排序 - 如果还有范围查询(如
"year": { $gte: 2023 }),sort字段必须放在范围字段之后,否则仍需额外排序
什么时候需要包含(Covered)索引?
当查询只返回索引字段(即 .find({}, { field1: 1, field2: 1 }) 且无 _id),MongoDB 可直接从索引读取全部数据,避免回表。这对高频 Top-N 接口很关键:
db.logs.createIndex({ "status": 1, "timestamp": -1 }, { "name": "status_ts_idx" })
执行:
db.logs.find({ "status": "ERROR" }, { "timestamp": 1, "_id": 0 })
.sort({ "timestamp": -1 })
.limit(5)
就能命中索引并完全避免磁盘文档读取。但注意:_id 默认总被返回,除非显式排除("_id": 0);若投影里有非索引字段,就一定回表。
聚合管道里的 $sort + $limit 怎么优化?
聚合中 $sort 后紧跟 $limit 时,MongoDB 会自动尝试用索引——但前提是 $sort 是第一阶段(或前面只有 $match),且字段顺序满足索引结构。常见陷阱:
-
$lookup后再$sort:索引失效,因为数据已出分片/跨集合 -
$unwind后$sort:索引通常无效,因展开后文档结构变化 - 用
$expr或聚合表达式写$match条件(如{ $expr: { $gt: [{ $size: "$tags" }, 2] } }):无法走索引,更别说支撑排序
真正能走索引的聚合 Top-N,基本等价于“带条件的 find + sort + limit”,只是语法不同。
索引能消除排序开销,但字段顺序、查询模式、投影控制这三者必须咬合。一个 explain 里还出现 "stage": "SORT" 的 Top-N 查询,大概率是索引字段顺序错了,或者条件里混进了没法走索引的操作。











