问题描述
我在SO上浏览了近10篇类似的文章,但我仍然对得到的结果感到困惑:5万秒以上用于在42K文档集合之间的单个$ lookup聚合中对异类进行排序和19条唱片收藏。又名,总叉积为798K。
不幸的是,在这里非规范化不是一个好的选择,因为'to'集合中的文档被大量共享,并且在进行更改时将需要在数据库中进行大量更新。
话虽这么说,但我似乎无法理解以下内容为何要花这么长时间。我觉得我一定做错了事。
上下文:
-
一个4 vcpu,16 GB RAM VM作为单个节点副本集运行Debian 10 / MongoDB 4.4 其他。完全更新的.NET MongoDB驱动程序(我刚才已更新并重新测试)
-
所有聚合,索引和集合都使用默认排序规则。
-
“ to”集合中的外部字段具有索引。是的,仅对这19条记录有所帮助。
-
其中一篇有关$ lookup性能较慢的文章提到,如果在$ lookup阶段的嵌套管道中未使用$ eq,则不会使用索引。因此,我确保聚合管道使用$ eq运算符。
这是管道:
[{ "$lookup" :
{ "from" : "4","let" : { "key" : "$1" },"pipeline" :
[{ "$match" :
{ "$expr" :
{ "$eq" : ["$id",{ "$substrCP" : ["$$key",{ "$indexOfCP" : ["$$key","_"] }] }] } } },{ "$unwind" : { "path" : "$RF2" } },{ "$match" : { "$expr" : { "$eq" : ["$RF2.id","$$key"] } } },{ "$replaceRoot" : { "newRoot" : "$RF2" } }],"as" : "L1" } },{ "$sort" : { "L1.5" : 1 } },{ "$project" : { "L1" : 0 } },{ "$limit" : 100 }]
取出嵌套的$ unwind / $ match / $ replaceRoot组合可节省大约30%的运行时间,使其降至3.5秒,但是,这些阶段对于链接/查找到正确的子文档是必需的。不需要查找就可以对'from'集合进行排序。
我在这里做错了什么?预先感谢!
编辑:
我刚刚用更大的记录集(“ from”集合中的38K记录,“ to”集合中的26K记录,一对一关系)测试了同一件事。花了7分钟以上的时间来完成排序。我检查了Compass后,发现实际上正在使用“ id”上的索引(在7分钟内保持刷新状态,看到它上升了,我是数据库的唯一用户)。
这是管道,比第一个管道更简单:
[{ "$lookup" :
{ "from" : "1007","let" : { "key" : "$13" },"pipeline" :
[{ "$match" :
{ "$expr" : { "$eq" : ["$id","$$key"] } } }],{ "$sort" : { "L1.1" : -1 } },{ "$limit" : 100 }]
根据上述信息,听起来7分钟合理吗?
编辑2:
shell代码创建具有两个字段(名称:字符串,uid:整数)的两个40k记录集合(prod和prod2):
var randomName = function() {
return (Math.random()+1).toString(36).substring(2);
}
for (var i = 1; i <= 40000; ++i) {
db.test.insert({
name: randomName(),uid: i });
}
我在prod2的'uid'字段上创建了一个索引,将Compass的示例文档限制增加到50k,然后进行了以下查找,这花费了整整两分钟的时间:
{ from: 'prod2',localField: 'uid',foreignField: 'uid',as: 'test' }
编辑3:
我还直接从Shell运行了聚合管道,并在几秒钟而不是两分钟内获得了结果:
db.test1.aggregate([{ $lookup:
{ from: 'test2',as: 'test' } }]).toArray()
是什么原因导致Shell与Compass和.NET驱动程序之间出现差异?
解决方法
对于任何绊倒这篇文章的人,以下对我有用:使用$ lookup运算符的localField / foreignField版本。
在Compass中监视索引时,let / pipeline版本和localField / foreignField版本都达到了适当的索引,但是使用let / pipeline版本时,速度要慢几个数量级。
我将查询构建逻辑重组为仅使用localField / foreignField,这带来了很大的改变。