mongodb中大型嵌套数据的查询性能问题
我试图从一个名为“tasks”的大型数据集查询结果,其中包含187297个文档,这些文档嵌套在另一个名为“workers”的数据集中,而这个数据集又嵌套在一个名为“production\u units”的集合中 生产单位->工人->任务 (顺便说一句,这是生产单元的简化版本): 为了实现这一点,我使用了以下聚合命令:mongodb中大型嵌套数据的查询性能问题,mongodb,aggregation-framework,query-performance,Mongodb,Aggregation Framework,Query Performance,我试图从一个名为“tasks”的大型数据集查询结果,其中包含187297个文档,这些文档嵌套在另一个名为“workers”的数据集中,而这个数据集又嵌套在一个名为“production\u units”的集合中 生产单位->工人->任务 (顺便说一句,这是生产单元的简化版本): 为了实现这一点,我使用了以下聚合命令: db.production_units.aggregate([{ '$project': { 'workers': '$workers' } },
db.production_units.aggregate([{
'$project': {
'workers': '$workers'
}
}, {
'$unwind': '$workers'
}, {
'$project': {
'tasks': '$workers.tasks',
'worker_number': '$workers.worker_number'
}
}, {
'$unwind': '$tasks'
}, {
'$project': {
'task_number': '$tasks.task_number',
'pieces_actual': '$tasks.pieces_actual',
'minutes_elapsed': '$tasks.minutes_elapsed',
'worker_number': 1,
'start': '$tasks.start',
'inbound_order_number': '$tasks.inbound_order_number',
'pause_from': '$tasks.pause_from',
'date': '$tasks.date',
'_id': '$tasks._id',
'pause_to': '$tasks.pause_to'
}
}, {
'$match': {
'start': {
'$exists': true
}
}
}, {
'$group': {
'entries_count': {
'$sum': 1
},
'_id': null,
'entries': {
'$push': '$$ROOT'
}
}
}, {
'$project': {
'entries_count': 1,
'_id': 0,
'entries': 1
}
}, {
'$unwind': '$entries'
}, {
'$project': {
'task_number': '$entries.task_number',
'pieces_actual': '$entries.pieces_actual',
'minutes_elapsed': '$entries.minutes_elapsed',
'worker_number': '$entries.worker_number',
'start': '$entries.start',
'inbound_order_number': '$entries.inbound_order_number',
'pause_from': '$entries.pause_from',
'date': '$entries.date',
'entries_count': 1,
'_id': '$entries._id',
'pause_to': '$entries.pause_to'
}
}, {
'$sort': {
'start': 1
}
}, {
'$skip': 187290
}, {
'$limit': 10
}], {
allowDiskUse: true
})
而退回的文件为:
{ "entries_count" : 187297, "task_number" : 100, "pieces_actual" : 68, "minutes_elapsed" : 102, "worker_number" : 411, "start" : 1594118400, "inbound_order_number" : 8569, "pause_from" : 1594119600, "date" : "2020-07-07", "_id" : ObjectId("5ac9f6d3e1a668d6d3a06351"), "pause_to" : 1594119600 } { "entries_count" : 187297, "task_number" : 130, "pieces_actual" : 20, "minutes_elapsed" : 30, "worker_number" : 549, "start" : 1596531600, "inbound_order_number" : 7683, "pause_from" : 1596538800, "date" : "2020-08-04", "_id" : ObjectId("5ac9f6cde1a668d6d39f1b26"), "pause_to" : 1596538800 } { "entries_count" : 187297, "task_number" : 210, "pieces_actual" : 84, "minutes_elapsed" : 180, "worker_number" : 734, "start" : 1601276400, "inbound_order_number" : 8330, "pause_from" : 1601290800, "date" : "2020-09-28", "_id" : ObjectId("5ac9f6d0e1a668d6d39fd677"), "pause_to" : 1601290800 } { "entries_count" : 187297, "task_number" : 20, "pieces_actual" : 64, "minutes_elapsed" : 90, "worker_number" : 114, "start" : 1601800200, "inbound_order_number" : 7690, "pause_from" : 1601809200, "date" : "2020-10-04", "_id" : ObjectId("5ac9f6cee1a668d6d39f3032"), "pause_to" : 1601811900 } { "entries_count" : 187297, "task_number" : 140, "pieces_actual" : 70, "minutes_elapsed" : 84, "worker_number" : 49, "start" : 1603721640, "inbound_order_number" : 4592, "pause_from" : 1603710000, "date" : "2020-10-26", "_id" : ObjectId("5ac9f6c8e1a668d6d39df664"), "pause_to" : 1603712700 } { "entries_count" : 187297, "task_number" : 80, "pieces_actual" : 20, "minutes_elapsed" : 30, "worker_number" : 277, "start" : 1796628600, "inbound_order_number" : 4655, "pause_from" : 1796641200, "date" : "2026-12-07", "_id" : ObjectId("5ac9f6c8e1a668d6d39e1fc0"), "pause_to" : 1796643900 } { "entries_count" : 187297, "task_number" : 40, "pieces_actual" : 79, "minutes_elapsed" : 123, "worker_number" : 96, "start" : 3802247580, "inbound_order_number" : 4592, "pause_from" : 3802244400, "date" : "2090-06-27", "_id" : ObjectId("5ac9f6c8e1a668d6d39de218"), "pause_to" : 3802244400 }
但是,查询需要几秒钟才能显示结果,而不是几毫秒。这是探查器返回的结果:
db.system.profile.findOne().millis 3216
(更新)
即使是以下简化的计数查询也会在312毫秒内执行,而不是很少的时间:
db.production_units.aggregate([{
"$unwind": "$workers"
}, {
"$unwind": "$workers.tasks"
},
{
"$count": "entries_count"
}
])
这是explain()
为上述查询返回的结果:
{
"stages" : [
{
"$cursor" : {
"query" : {
},
"fields" : {
"workers" : 1,
"_id" : 0
},
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "my_db.production_units",
"indexFilterSet" : false,
"parsedQuery" : {
},
"winningPlan" : {
"stage" : "COLLSCAN",
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 28,
"executionTimeMillis" : 13,
"totalKeysExamined" : 0,
"totalDocsExamined" : 28,
"executionStages" : {
"stage" : "COLLSCAN",
"nReturned" : 28,
"executionTimeMillisEstimate" : 0,
"works" : 30,
"advanced" : 28,
"needTime" : 1,
"needYield" : 0,
"saveState" : 1,
"restoreState" : 1,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 28
},
"allPlansExecution" : [ ]
}
}
},
{
"$unwind" : {
"path" : "$workers"
}
},
{
"$unwind" : {
"path" : "$workers.tasks"
}
},
{
"$group" : {
"_id" : {
"$const" : null
},
"entries_count" : {
"$sum" : {
"$const" : 1
}
}
}
},
{
"$project" : {
"_id" : false,
"entries_count" : true
}
}
],
"ok" : 1
}
我不是一个经验丰富的DBA,所以我不知道我在解决我所面临的性能问题的聚合管道中到底缺少了什么。我也对这个问题进行了调查和研究,但没有找到任何解决办法
我缺少什么?如果没有查询的explain()
,就不可能确定查询的瓶颈是什么。但是,以下是关于如何改进此查询的一些建议
在管道末尾使用单个
$project
阶段
该查询包含5个$project
阶段,实际上只需要一个阶段。这会增加很多开销,尤其是应用于大量文档时。
相反,请使用点表示法查询嵌套字段,例如:
{ "$unwind": "$workers.tasks" }
尽早调用$match
$match
允许删除一些文档,因此尽早添加它,以便在数量较少的文档上应用进一步的聚合阶段
在$project
由于查询只返回10个文档,因此无需在180000个其他文档上应用$project
阶段
正确索引用于排序的字段
这可能是瓶颈。确保字段workers.tasks.start
已编制索引(有关详细信息,请参阅)
不要计算查询中返回的文档的nb
不要使用$group
/$unwind
阶段对匹配文档进行计数,而是同时运行另一个查询,仅对匹配文档的数量进行计数
现在,主查询如下所示:
db.collection.aggregate([{
"$unwind": "$workers"
}, {
"$unwind": "$workers.tasks"
}, {
"$match": {
"workers.tasks.start": {
"$ne": null
}
}
},
{
"$sort": {
"workers.tasks.start": 1
}
}, {
"$skip": 0
}, {
"$limit": 10
},
{
"$project": {
"task_number": "$workers.tasks.task_number",
"pieces_actual": "$workers.tasks.pieces_actual",
"minutes_elapsed": "$workers.tasks.minutes_elapsed",
"worker_number": "$workers.worker_number",
"start": "$workers.tasks.start",
"inbound_order_number": "$workers.tasks.inbound_order_number",
"pause_from": "$workers.tasks.pause_from",
"date": "$workers.tasks.date",
"_id": "$workers.tasks._id",
"pause_to": "$workers.tasks.pause_to"
}
}
])
您可以在这里尝试:
计数查询将是
db.collection.aggregate([{
"$unwind": "$workers"
}, {
"$unwind": "$workers.tasks"
}, {
"$match": {
"workers.tasks.start": {
"$ne": null
}
}
},
{
"$count": "entries_count"
}
])
计数查询看起来像您是否尝试将$match放在聚合的开头?它将减少通过不同阶段的文档数量这有点难理解您试图通过查询实现什么,您能否添加一个集合的示例文档,以及预期的结果?@felix该查询将为每个工作人员嵌套的任务文档连接起来,然后返回最后10个文档(以适合分页)。张贴的文件够了吗,还是我需要提供更多信息?@Nicolas抱歉,我不明白你的确切意思。你的意思是一个空的$match(即
$match:{}
)?我的意思是,你可以尝试在第一时间放入`$match:{“workers.tasks.start”:{$exists:true}}来过滤你需要的文档谢谢你的回答。但是在测试了您上面提到的查询之后,我真的不知道为什么计数查询需要312ms,而主查询只需要26ms。是什么导致了$count的开销?@Kais很难说。。。您可能想查看一下查询,这将准确地告诉您mongodb在哪里花费时间我更新了我的问题,以便提供explain()
@Ashish的结果目前在json模式下不可能,但您可以在mgodatagen模式下实现这一点(例如,)。如果您发现任何错误,请在此处报告:!
db.collection.aggregate([{
"$unwind": "$workers"
}, {
"$unwind": "$workers.tasks"
}, {
"$match": {
"workers.tasks.start": {
"$ne": null
}
}
},
{
"$count": "entries_count"
}
])