如何加速按分类获取最新2条文档的MongoDB聚合查询?
解决MongoDB按分类取最新文档的性能问题
嘿,我来帮你搞定这个查询慢的难题!你的场景是数百万文档、日增数千条,要取每个分类下最新2条,现有的单字段索引确实不够用——这就是速度慢的核心原因,下面给你一步步的优化方案:
一、先搞懂现有索引的问题
你现在创建的是{time:-1}和{category:1}两个单字段索引,但MongoDB没法高效把这两个索引结合起来完成「按分类分组+取最新N条」的查询。它要么先扫全表按time排序,再分组;要么先按category找全部分类,再每个分类单独排序,这两种方式都会带来大量的IO和计算,自然慢得离谱。
二、核心优化:创建复合索引
直接创建**{category: 1, time: -1}的复合索引**,这是解决问题的关键!
db.testing.createIndex({category: 1, time: -1})
这个索引的逻辑是:先按category升序把所有文档分组,每个分组内的文档又按time降序排列——这样每个分类下的最新文档直接就在分组的最前面,取前2条根本不需要额外排序,MongoDB可以直接从索引里捞数据,性能会提升N个档次。
三、优化查询语句(两种方案可选)
根据你的MongoDB版本,选对应的高效查询方式:
方案1:通用版(支持所有MongoDB版本)
用聚合框架的$group+$push+$slice组合,配合上面的复合索引,性能会比你之前的查询好很多:
db.testing.aggregate([ // 利用复合索引完成排序,避免内存排序 { $sort: { category: 1, time: -1 } }, // 按分类分组,把每个分类的文档按时间顺序存入数组 { $group: { _id: "$category", latestDocs: { $push: "$$ROOT" } } }, // 截取每个分类数组的前2条最新文档 { $project: { _id: 0, category: "$_id", latestDocs: { $slice: ["$latestDocs", 2] } } } ])
⚠️ 注意:如果某个分类下的文档特别多(比如几十万条),$push会占用较多内存,这时候推荐用下面的方案2。
方案2:高性能版(MongoDB 5.0+支持)
用$setWindowFields窗口函数,它能直接利用复合索引计算每个文档在分类内的排名,不需要把整个分类的文档都存入数组,内存占用更低,性能更优:
db.testing.aggregate([ // 按分类分区,每个分区内按时间降序计算排名 { $setWindowFields: { partitionBy: "$category", sortBy: { time: -1 }, output: { rank: { $rank: {} } } } }, // 只保留每个分类下排名前2的文档 { $match: { rank: { $lte: 2 } } }, // 按分类整理结果(可选,根据你的输出需求调整) { $group: { _id: "$category", latestDocs: { $push: "$$ROOT" } } }, // 移除临时的rank字段,整理输出格式 { $project: { _id: 0, category: "$_id", latestDocs: { $map: { input: "$latestDocs", as: "doc", in: { $unset: ["$$doc.rank"] } } } } } ])
四、额外优化建议
- 验证索引是否生效:在聚合语句末尾加
.explain("executionStats"),查看查询计划,确认是否用到了我们创建的复合索引,避免出现全表扫描的情况。 - 清理旧数据:如果不需要保留所有历史文档,定期删除过期数据,减少集合体积,能显著提升查询速度。
- 分片考虑:如果数据量持续增长到数亿级别,可以考虑按
category或time分片,把数据分散到多个节点,进一步提升查询效率。
内容的提问来源于stack exchange,提问作者user8818478
相关产品推荐
相关产品推荐

