MongoDB执行aggregate与$group查询时内存溢出问题求优化方案
MongoDB亿级allvoice集合聚合报错优化方案
报错核心原因
- MongoDB存在16MB单个BSON文档硬限制,你当前的聚合逻辑是将单日所有通话记录通过
$push存入单个文档的data数组内,当日通话量超过约8万条(按单条记录200字节估算)就会超出该限制,这是报错的根本原因,和服务器配置无关,即使开启allowDiskUse也无法突破该限制。 - 你后续优化的按月查询逻辑没有解决单文档大小超限的核心问题,当月单日数据量过大时仍会触发报错。
优化方案
方案1:拆分存储结构(最推荐,无大小限制性能最优)
放弃将单日所有记录塞入单个数组的设计,拆分为两个集合分别存储统计指标和明细数据:
dailyvoice_stats:按天存储统计指标,单天仅1条记录,无大小超限风险dailyvoice_details:存储单条通话明细,每条新增date(格式为YYYY-MM-DD)字段,对date建立索引后按天查询性能和原方案一致,且无大小限制
优化后聚合语句
首先跑明细数据(按月分批执行,避免全量压力):
// 单月明细数据聚合 db.getCollection('allvoice').aggregate([ // 按月过滤,提前给month字段建索引可大幅提升过滤速度 { $match: { month: "202001" } }, { $addFields: { // 直接从originaltimestamp字符串截取拼接日期,比两次类型转换效率高30%以上 "date": { $concat: [ { $substr: ["$originaltimestamp", 0, 4] }, "-", { $substr: ["$originaltimestamp", 4, 2] }, "-", { $substr: ["$originaltimestamp", 6, 2] } ] } } }, // 可按需保留需要的字段,减少写入数据量 { $project: { subscriber_id: 1, callednumber: 1, originaltimestamp: 1, duration: 1, maincost: 1, type: 1, type_network: 1, month: 1, date: 1 } }, { $out: "dailyvoice_details_202001" } ], { allowDiskUse: true })
再跑统计数据:
// 单月统计数据聚合 db.getCollection('allvoice').aggregate([ { $match: { month: "202001" } }, { $group: { _id: { $concat: [ { $substr: ["$originaltimestamp", 0, 4] }, "-", { $substr: ["$originaltimestamp", 4, 2] }, "-", { $substr: ["$originaltimestamp", 6, 2] } ] }, count: { $sum: 1 }, total_duration: { $sum: { $toDouble: "$duration" } }, total_cost: { $sum: { $toDouble: "$maincost" } } // 可按需新增其他统计维度 } }, { $out: "dailyvoice_stats_202001" } ], { allowDiskUse: true })
使用示例
按天查询明细:
// 给date建索引后查询性能极高 db.dailyvoice_details_202001.find({date: "2020-01-13"})
按天查询统计值:
db.dailyvoice_stats_202001.find({_id: "2020-01-13"})
方案2:保留原结构的折中方案
如果必须保留单日一条带数组的结构,可将单日数据按小时分桶拆分,降低单文档大小:
db.getCollection('allvoice').aggregate([ { $match: { month: "202001" } }, { $group: { _id: { date: { $concat: [ { $substr: ["$originaltimestamp", 0, 4] }, "-", { $substr: ["$originaltimestamp", 4, 2] }, "-", { $substr: ["$originaltimestamp", 6, 2] } ] }, // 按小时分桶,单文档大小降低为原来的1/24 hour: { $substr: ["$originaltimestamp", 8, 2] } }, data: { $push: { subscriber_id: "$subscriber_id", type: "$type", date: { "$toDate": "$originaltimestamp" }, month: "$month" } }, count: { "$sum": 1 } } }, { $out: 'dailyvoice_202001' } ], { allowDiskUse: true })
额外优化建议
- 给
allvoice集合的month字段建立索引,可大幅提升按月过滤的效率 - 全量数据同步时按月分批执行,不要一次性跑全量数据,降低数据库压力,出错也只需重跑单月任务
- 若后续需要跨月查询,可将多个月份的明细/统计集合合并为全局集合,给
date、month建联合索引即可
内容的提问来源于stack exchange,提问作者cauchuyennhocuatoi
相关产品推荐
相关产品推荐

