如何加速百万级文档的MongoDB聚合查询?
MongoDB百万级订单集合Top3销量产品聚合优化方案
问题分析
从执行计划能看出,当前聚合的核心瓶颈是全表扫描(COLLSCAN),需要读取全部100万+文档,这占了大部分耗时;其次是$unwind后生成1100万+条中间数据,进一步增加了处理负载。常规索引未生效是因为没针对聚合所需字段创建覆盖索引,导致MongoDB无法跳过全表扫描。
优化步骤
1. 创建覆盖索引
创建包含聚合所需字段的覆盖索引,让MongoDB直接从索引中读取数据,避免全表扫描:
db.orders.createIndex( { "products._id": 1, "products.quantity": 1 }, { projection: { _id: 0 } } )
- 索引包含
products._id和products.quantity,刚好匹配聚合中分组和求和的需求 projection: { _id: 0 }指定不包含_id字段,进一步缩小索引体积
2. 简化聚合管道
移除不必要的$project阶段(覆盖索引已自动过滤所需字段),简化后的管道如下:
[ { $unwind: { path: '$products' } }, { $group: { _id: '$products._id', unitsSold: { $sum: '$products.quantity' } } }, { $sort: { unitsSold: -1 } }, { $limit: 3 } ]
- 移除
$project减少一个处理阶段,降低内存开销 - 保留核心逻辑:展开数组→按产品ID分组求和→排序取Top3
3. 预聚合(非实时场景最优解)
如果不需要实时统计销量,建议定期预聚合结果到单独集合,彻底解决实时聚合的性能问题:
步骤1:创建预聚合脚本
// 定期执行(如通过MongoDB定时任务或外部脚本) db.product_sales.deleteMany({}); // 清空旧数据 db.orders.aggregate([ { $unwind: { path: '$products' } }, { $group: { _id: '$products._id', unitsSold: { $sum: '$products.quantity' }, productName: { $first: '$products.name' } // 可选,存储产品名称方便查询 } }, { $out: 'product_sales' } // 将结果写入product_sales集合 ]);
步骤2:快速查询Top3
db.product_sales.find().sort({ unitsSold: -1 }).limit(3)
- 预聚合后查询仅需毫秒级响应,适合报表、后台统计等非实时场景
效果验证
创建覆盖索引后,执行计划中的COLLSCAN会变为IXSCAN(索引扫描),totalDocsExamined会大幅降低,聚合耗时可缩短至数秒内;预聚合方案则能实现亚毫秒级查询。
内容的提问来源于stack exchange,提问作者m3k_1
相关产品推荐
相关产品推荐

