我一直在解决为什么我创建的MongoDB View 这么慢的问题。该 View 以transactions
集合为目标,并返回openBalance
大于0
的记录。我还运行其他一些聚合阶段,以按照自己的方式调整数据形状。
为了加快 View 的执行,它通过在 View 的聚合管道的第一阶段中与索引字段进行匹配来利用目标集合上的索引,如下所示:
// View Stage 1
{ "transactions.details.openBalance" : { "$exists" : true, "$gt" : 0.0 } }
经过大量研究,我确定从 View 进行的聚合非常快速地返回了数据。缓慢的是作为端点一部分运行的计数:
let count = await db.collection('view_transactions_report').find().count();
因此,我现在要弄清楚的是,为什么 View 上的计数比基础集合上的计数慢得多,还有我可以做些什么来加快计数速度。或者,也许还有另一种方法来生成计数?
基础集合有大约80万条记录,但计数很快返回。但是 View 的计数返回的速度要慢得多,该 View 仅返回最初的800,000条记录中的10,000个过滤后的集合。在细节方面,我说的是集合返回的计数的3/4秒,而mongo View 返回的计数则是6秒。
因此,首先,为什么 View 的计数(其数据集要小得多)比基础集合慢得多,其次,我该如何处理 View 的计数速度?
我正在运行其他几个聚合查询,以确定
totalCustomers
和totalOpenBalance
,它们似乎也运行缓慢(请参见下面的代码)。我的端点功能代码的相关部分如下所示:
// previous code
let count = await db.collection('view_transaction_report').find(search).count();
let totalCustomers = await db.collection('view_transaction_report').find(search).count({
$sum: "customer._id"
});
let result = {};
if (totalCustomers > 0) {
result = await db.collection('view_transaction_report').aggregate([{
$match: search,
},
{
$group: {
_id: null,
totalOpenBalance: {
$sum: '$lastTransaction.details.openBalance'
}
}
}
]).next();
}
db.collection('view_transaction_report').find(search).skip(skip).limit(pagesize).forEach(function (doc) {
docs.push(doc);
}, function (err) {
if (err) {
if (!ioOnly) {
return next(err);
} else {
return res(err);
}
}
if (ioOnly) {
res({
sessionId: sessID,
count: count,
data: docs,
totalCustomers: totalCustomers,
totalOpenBalance: result.totalOpenBalance
});
} else {
res.send({
count: count,
data: docs,
totalCustomers: totalCustomers,
totalOpenBalance: result.totalOpenBalance
});
}
});
就
executionStats
而言,这是为生成的 View 的queryPlanner
部分显示的内容: "queryPlanner" : {
"plannerVersion" : 1.0,
"namespace" : "vio.transactions",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"transactions.details.openBalance" : {
"$gt" : 0.0
}
},
{
"transactions.destails.openBalance" : {
"$exists" : true
}
}
]
},
"winningPlan" : {
"stage" : "CACHED_PLAN",
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"transactions.details.openBalance" : {
"$exists" : true
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"transactions.details.openBalance" : 1.0
},
"indexName" : "openBalance",
"isMultiKey" : true,
"multiKeyPaths" : {
"transactions.details.openBalance" : [
"transactions",
"transactions.details"
]
},
"isUnique" : false,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2.0,
"direction" : "forward",
"indexBounds" : {
"transactions.details.openBalance" : [
"(0.0, inf.0]"
]
}
}
}
},
"rejectedPlans" : [
]
}
最佳答案
@Wan Bachtiar在评论中提到“openBalance”看起来像是multikey index。为了澄清,是的,在目标集合中,“openBalance”字段是数组内的嵌入式字段。即使在 View 中也是如此,即使数据以如下方式成形:“openBalance”是不在数组内的嵌入式字段。
问题所在就在于目标集合上的多键索引,因为Mongo需要遍历与该“openBalance”字段相关的每个数组元素,而不是一对一的文档情况,从逻辑上讲,这大大增加了扫描时间-因为有时有很多与此特定 Realm 有关的数组元素。
经过进一步检查后,我意识到我可以通过更改通过ETL将“openBalance”填充到我们的mongo集合中的方式来解决此问题。通过进行此更改,我将能够使“openBalance”成为标准索引,而不是多键索引,而后者又将使mongo搜索更小的数据集以返回我的计数。