You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB聚合查询性能远低于SQL Server,如何优化至4分钟以内?

MongoDB聚合查询性能优化问题

我了解到MongoDB相比SQL Server更擅长数据聚合,于是尝试将业务中原有SQL Server查询转换为MongoDB查询,但是转换后的查询性能远不达标:相同统计逻辑下,统计128天数据时SQL Server仅需约4分钟即可完成,而当前MongoDB查询耗时高达34分钟。

现有查询逻辑

function(thresholdObs, days) 
{       
    var name = "period-" + days + "j"
    var startDate = new Date('2020', '00', '01');
    var endDate = new Date(startDate.getTime() + 1000 * 60 * 60 * 24 *[days]);


    db.getCollection('measures').aggregate([
        { 
            $match: { 
                $and: [
                    {
                        measureDate: {
                            $gte: startDate
                        }
                    },
                    {
                        measureDate: {
                            $lte: endDate
                        }
                    }
                ]
     
            } 
        }, 
        { 
            $addFields: {
                multiplyIahobs: { 
                    $cond: [
                        {$or: [
                            {$eq: ["$averageIAH", null]},
                            {$eq: ["$obs", null]}
                        ]}, 
                        0, 
                        { $multiply: ["$averageIAH", "$obs"] }
                    ]
                },
                multiplyPressionobs: { 
                    $cond: [
                        {$or: [
                            {$eq: ["$averagePressure", null]},
                            {$eq: ["$obs", null]}
                        ]}, 
                        0, 
                        { $multiply: ["$averagePressure", "$obs"] }
                    ]
                },
                multiplyFuitesobs: {
                    $cond: [
                        {$or: [
                            {$eq: ["$averageLeakage", null]},
                            {$eq: ["$obs", null]}
                        ]}, 
                        0, 
                        { $multiply: ["$averageLeakage", "$obs"] }
                    ]
                },
                multiplyinspiratoryPressureobs: { 
                    $cond: [
                        {$or: [
                            {$eq: ["$inspiratoryPressure", null]},
                            {$eq: ["$obs", null]}
                        ]}, 
                        0, 
                        { $multiply: ["$inspiratoryPressure", "$obs"] }
                    ]    
                },
                multiplyexpiratoryPressureobs: {
                    $cond: [
                        {$or: [
                            {$eq: ["$expiratoryPressure", null]},
                            {$eq: ["$obs", null]}
                        ]}, 
                        0, 
                        { $multiply: ["$expiratoryPressure", "$obs"] }
                    ]    
                },
                enoughDays: {
                    $cond: [ { $gte: ["$obs", thresholdObs ] }, 1, 0]
                },
                missingDays: {
                    $cond: [ { $lt: ["$obs", thresholdObs ] }, 1, 0]
                },
                daysWithoutUsage: {
                    $cond: [ { $eq: ["$obs", 0 ] }, 1, 0]
                },
                daysWithData: { $sum: 1 }
            }
        },
        {
            $group: {
                _id: "$deviceID",
                obsUsage: { $avg: "$obs" }, 
                enoughDays: { $sum: "$enoughDays" },
                missingDays: { $sum: "$missingDays" },
                daysWithoutUsage: { $sum: "$daysWithoutUsage" },
                daysWithData: { $sum: "$daysWithData" },
                sumobs: { $sum: "$obs" },
                sumMultiplyIahobs: { $sum: "$multiplyIahobs" },
                sumMultiplyPressureObs: { $sum: "$multiplyPressionobs" },
                sumMultiplyLeakageObs: { $sum: "$multiplyFuitesobs" },
                sumMultiplyinspiratoryPressureobs: { $sum: "$multiplyinspiratoryPressureobs" },
                sumMultiplyexpiratoryPressureobs: { $sum: "$multiplyexpiratoryPressureobs" },
            } 
        },
        {
            $addFields: {
                fullObs: { $divide: [ "$sumobs", days ] }, 
                daysWithoutData: { $subtract: [ days, "$daysWithData" ]},
                averageIAH: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyIahobs", "$sumobs"] } ] },
                averagePressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyPressureObs", "$sumobs"] } ] },
                averageLeakage: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyLeakageObs", "$sumobs"] } ] },
                inspiratoryPressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyinspiratoryPressureobs", "$sumobs"] } ] },
                expiratoryPressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyexpiratoryPressureobs", "$sumobs"] } ] },
            }
        },
        {
            $project: {
                _id: 1,
                [name] : {
                    obsUsage: "$obsUsage", 
                    enoughDays: "$enoughDays",
                    missingDays: "$missingDays",
                    daysWithoutUsage: "$daysWithoutUsage",
                    daysWithData:  "$daysWithData",
                    sumobs: "$sumobs",
                    fullObs: "$fullObs", 
                    daysWithoutData: "$daysWithoutData",
                    averageIAH: "$averageIAH",
                    averagePressure: "$averagePressure",
                    averageLeakage: "$averageLeakage",
                    inspiratoryPressure: "$inspiratoryPressure",
                    expiratoryPressure: "$expiratoryPressure"
                }
            }       
        },
        { 
            $merge: {
             into: "periods",
             on: "_id",         
             whenMatched: "merge",
             whenNotMatched: "insert"
            }
        }
    ],{allowDiskUse: true})
}

集合说明

measures集合共有374670449条文档,样例数据如下:

{
    _id: ObjectId('6127a15fef44a9ed52a5bf62'),
    deviceId: 5,
    measureDateAdded: ISODate('2013-03-15T10:30:35.753Z'),
    measureDate: ISODate('2012-03-20T06:00:00.000Z'),
    obs: 20,
    averageIAH: NumberDecimal('5.50'),
    averagePressure: NumberDecimal('6.30'),
    averageLeakage: NumberDecimal('13.00'),
    inspiratoryPressure: null,
    expiratoryPressure: null
},
{
    _id: ObjectId('6127a15fef44a9ed52a5bfc6'),
    deviceId: 5,
    measureDateAdded: ISODate('2013-03-15T10:30:40.063Z'),
    measureDate: ISODate('2012-06-28T05:00:00.000Z'),
    obs: 197,
    averageIAH: NumberDecimal('5.30'),
    averagePressure: NumberDecimal('6.90'),
    averageLeakage: NumberDecimal('15.00'),
    inspiratoryPressure: null,
    expiratoryPressure: null
},
{
    _id: ObjectId('6127aa2bef44a9ed52a0922a'),
    deviceId: 367959,
    measureDateAdded: ISODate('2019-01-19T14:13:19.620Z'),
    measureDate: ISODate('2019-01-16T11:00:00.000Z'),
    obs: 375,
    averageIAH: NumberDecimal('2.00'),
    averagePressure: NumberDecimal('9.60'),
    averageLeakage: NumberDecimal('6.00'),
    inspiratoryPressure: null,
    expiratoryPressure: null
}

已排查的性能瓶颈

最耗时的步骤是按deviceID分组的阶段,仅该阶段耗时就超过4分钟。不希望直接放弃使用MongoDB,因此想确认:查询编写逻辑是否存在问题?能否通过调整查询将总耗时降低到4分钟以内?如果可行,具体应该如何优化?


优化方案

1. 先修复致命逻辑bug

你代码中$group阶段的分组键写的是$deviceID,但样例数据里的实际字段名为deviceId(大小写不一致),会导致所有数据被分到同一个分组里,不仅计算结果错误,也会大幅提升分组阶段的开销,首先修正字段名拼写。

2. 新增覆盖索引(核心优化,性能可提升10倍以上)

创建以下复合覆盖索引,让整个查询可以直接从索引取数,完全不需要读取原始文档,也无需额外哈希排序开销:

db.measures.createIndex(
  {measureDate: 1, deviceId: 1}, 
  {include: ["obs", "averageIAH", "averagePressure", "averageLeakage", "inspiratoryPressure", "expiratoryPressure"]}
)
  • 首字段measureDate匹配$match过滤条件,可快速定位指定时间范围内的所有数据
  • 第二个字段deviceId匹配分组键,MongoDB可直接按索引顺序完成分组,无需额外计算
  • include字段覆盖了查询用到的所有业务字段,避免了全量文档的IO读取开销

3. 简化聚合逻辑,消除冗余步骤

删除第一个多余的$addFields阶段,把所有计算逻辑直接下沉到$group阶段,减少中间数据的内存开销:

// 优化后的聚合核心逻辑
db.getCollection('measures').aggregate([
    { 
        $match: { 
            measureDate: {
                $gte: startDate,
                $lte: endDate
            }
        } 
    }, 
    {
        $group: {
            _id: "$deviceId",
            obsUsage: { $avg: "$obs" }, 
            enoughDays: { $sum: { $cond: [ { $gte: ["$obs", thresholdObs ] }, 1, 0] } },
            missingDays: { $sum: { $cond: [ { $lt: ["$obs", thresholdObs ] }, 1, 0] } },
            daysWithoutUsage: { $sum: { $cond: [ { $eq: ["$obs", 0 ] }, 1, 0] } },
            daysWithData: { $sum: 1 },
            sumobs: { $sum: "$obs" },
            sumMultiplyIahobs: { 
                $sum: {
                    $cond: [
                        {$or: [{$eq: ["$averageIAH", null]}, {$eq: ["$obs", null]}]}, 
                        0, 
                        { $multiply: ["$averageIAH", "$obs"] }
                    ]
                } 
            },
            sumMultiplyPressureObs: { 
                $sum: {
                    $cond: [
                        {$or: [{$eq: ["$averagePressure", null]}, {$eq: ["$obs", null]}]}, 
                        0, 
                        { $multiply: ["$averagePressure", "$obs"] }
                    ]
                } 
            },
            sumMultiplyLeakageObs: { 
                $sum: {
                    $cond: [
                        {$or: [{$eq: ["$averageLeakage", null]}, {$eq: ["$obs", null]}]}, 
                        0, 
                        { $multiply: ["$averageLeakage", "$obs"] }
                    ]
                } 
            },
            sumMultiplyinspiratoryPressureobs: { 
                $sum: {
                    $cond: [
                        {$or: [{$eq: ["$inspiratoryPressure", null]}, {$eq: ["$obs", null]}]}, 
                        0, 
                        { $multiply: ["$inspiratoryPressure", "$obs"] }
                    ]
                } 
            },
            sumMultiplyexpiratoryPressureobs: { 
                $sum: {
                    $cond: [
                        {$or: [{$eq: ["$expiratoryPressure", null]}, {$eq: ["$obs", null]}]}, 
                        0, 
                        { $multiply: ["$expiratoryPressure", "$obs"] }
                    ]
                } 
            },
        } 
    },
    // 后续$addFields、$project、$merge逻辑保持不变即可
],{allowDiskUse: true})

该优化可减少30%以上的内存占用和数据传输开销,分组速度会明显提升。

4. 可选进阶优化

  • 如果MongoDB版本>=5.0,可将measures集合改为时间序列集合,针对按时间查询的场景性能可再提升50%以上
  • 提前按天做预聚合生成中间汇总表,查询128天数据时直接从中间表汇总,耗时可降到秒级
  • 确认实例内存足够容纳索引大小,避免索引频繁换入换出磁盘拖慢性能

按照以上方案调整后,整体查询耗时完全可以降到4分钟以内,甚至优于SQL Server的性能表现。


内容的提问来源于stack exchange,提问作者Su4p

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 16:48:00