Mongo docs对$all
运营商的评论。
在当前版本中,使用$ all运算符的查询必须扫描 与查询数组中第一个元素匹配的所有文档。如 结果,即使有索引支持查询,操作也可以 长时间运行,特别是当数组中的第一个元素是 不是很有选择性。
鉴于我的数据库结构(如下所示),我在考虑如何确保每当我使用Mongo API进行$all
查询时,第一个参数将是more selective
字段。 / p>
{
_id: 1,
records : [ {n: "Name", v: "Kevin"}, {n: "Age", v: 100} ]
}
我的第一个想法是定期创建一个“选择性”查询字段的动态列表。在代码中执行查询时,我会查看通过$all
运算符搜索的2+个字段,然后将更多/最“选择性”的项目作为第一个元素。
处理这个问题有更好的方法或最佳做法吗?
答案 0 :(得分:2)
如果您的数据库结构仅限于几个字段,我认为最好(也很简单)的选项是更改数据库结构
{
_id: 1,
records : [ {n: "Name", v: "Kevin"}, {n: "Age", v: 100} ]
}
到
{
_id: 1,
Name: "Kevin",
Age: 100
}
这样您就可以在{Name:1,Age:1}
上的索引上查询数据库但是 ,如果“records”数组具有通用键/值结构,并且您必须使用$ all运算符,您可以使用聚合框架(或MapReduce)来构建“选择性”查询字段的动态列表。
我将用一个例子解释我的想法(只有一个编码样本):
也许您的查询属于这种类型:
db.structure.find({ records: {
$all: [ {n: "Name", v: "Kevin"}, {n: "Age", v: 100} ]
} } )
例如
db.structure.find({
records: {n: "Name", v: "Kevin"}
} ).count() --> 1000
db.structure.find({
records: {n: "Age", v: 100}
} ).count() --> 100 // -> most selective!
因此,最快的查询将以{n:“Age”,v:100}作为第一项......
您可以编写类似
的批处理var result = db.structure.aggregate([
{ $unwind: "records" },
{ $group: {
_id: "$records",
record_count: { $sum: 1 }
}
}
]);
db.selective_items.save(result.result);
然后,当您想要使用$ all运算符进行查询时,首先需要查询selective_items集合并查找具有较低值record_count的记录,然后使用正确的第一个元素构建$ all查询。 / p>
我希望这个解决方案能回答你的问题
答案 1 :(得分:1)
跟踪“选择性”属性的问题是它需要不断重新采样数据。此外,它假定选择的每个属性都将保留该属性,无论向查询提供什么值。
以下是一组可能属性的示例:
{ n:"lastName", v:"Kamsky" } vs. { n:"lastName", v:"Smith" }
{ n:"firstName", v:"Asya" } vs. { n:"firstName", v:"Jessica" }
{ n:"Age", v: 21 } vs. { n:"Age", v: 15 }
在前两个比较行中,姓氏和名字可以是非常有选择性的(当第一个或最后一个名称很少时)或者不是特别有选择性(当它在数据库的群体中很常见时)。
在第三行,如果我们不知道数据库的内容是什么,我们无法判断“Age”值中的任何一个是否具有选择性。如果它是你正在查询的大学生的集合,那么第二个价值将最终具有极高的选择性,但如果它是高中生,那么第一个价值将是高度选择性的。
如果你的任何查询是不等式,那么我会说他们不会做出好的第一个位置元素,除非其他元素都没有选择性。
但是,如何追踪什么是选择性的?
您可以采取以下措施来跟踪“统计数据”(可以这么说):
var X = 3; // assign a threshold equal to some number that's "too high" to scan
db.<collection>.aggregate(
{$unwind : "$records"},
{$group : {_id:{n:"$records.n",v:"$records.v"}, count:{$sum:1}}},
{$group : {_id:"$_id.n", totalDistinctValues:{$sum:1}, values:{$push:{value:"$_id.v", appears:"$count"}} } },
{$project : {_id:0, AttributeName:"$_id", totalDistinctValues:1, values:1}},
{$match : {"values.appears":{$not:{$gte: X }} }},
{$sort : {totalDistinctValues:1}},
{$limit : 10}
)
上述聚合将返回具有最独特(不同)值的十个属性。此外,每个文档都将包含可能值的列表以及它出现的次数。根据您的确切字段和可能的数据分布,有很大的空间可以调整 - 您当然可以将其保存到要查询的集合中,但我只是将其缓存在应用程序中,因为这不是有意义的事情。坚持你描述的用例。
在我的小测试集合中,返回的结果是这个(我也有“雇主”字段,但是一半的记录列出了同一雇主,因此将其删除):
[
{
"totalDistinctValues" : 5,
"AttributeName" : "firstName",
"values" : [
{
"value" : "Sheldon",
"appears" : 1
},
{
"value" : "Raj",
"appears" : 1
},
{
"value" : "Penny",
"appears" : 1
},
{
"value" : "Asya",
"appears" : 1
},
{
"value" : "John",
"appears" : 2
}
]
},
{
"totalDistinctValues" : 6,
"AttributeName" : "lastName",
"values" : [
{
"value" : "Kumar",
"appears" : 1
},
{
"value" : "Smith",
"appears" : 1
},
{
"value" : "Lane",
"appears" : 1
},
{
"value" : "Williams",
"appears" : 1
},
{
"value" : "Kamsky",
"appears" : 1
},
{
"value" : "Cooper",
"appears" : 1
}
]
},
{
"totalDistinctValues" : 6,
"AttributeName" : "Age",
"values" : [
{
"value" : 31,
"appears" : 1
},
{
"value" : 21,
"appears" : 1
},
{
"value" : 22,
"appears" : 1
},
{
"value" : 29,
"appears" : 1
},
{
"value" : 49,
"appears" : 1
},
{
"value" : 59,
"appears" : 1
}
]
}
]
结论:一旦您运行此聚合并查看数据中的实际分布,可能很明显某些属性是自然选择性的,并且将始终(或经常)出现在查询中。把它们放在第一位否则,请使用统计信息首先动态放置更具选择性的属性,然后对https://jira.mongodb.org/browse/SERVER-2348进行投票,这会跟踪此类查询的索引使用情况的改进。
答案 2 :(得分:0)
看一下这个blog post,它会介绍使用通用结构时的一些注意事项,例如上面显示的结构。通常,选择性条件会对您产生不同的影响,因为$ all中的每个条件都是针对同一个字段的。另一件需要考虑的事情是在查询中使用explain()运算符来查看哪些运算符效果更好等等。
答案 3 :(得分:0)
旁注: 我认为$ all将总是表现得很慢,就像MongoDB文档中提到的那样。使用MongoDB时要考虑的一件事是查询应该总是尝试将您的投影减少到最低的形式,然后处理剩余的数据。 任何数组操作都将从查找查询中添加其他条件中获益匪浅。
话虽如此,因为$ all应该在内部检查所有元素,即使各个值都是索引,它仍然有m * n个组合。从技术上讲,顺序无关紧要,但我想确实如此。
总是尽量避免$ all,$ nin,$ ne操作。即使在SQL中也是如此。
答案 4 :(得分:0)
如果在数组字段上定义了索引,则对字段值运行多个计数将比$ all阶段中的错误顺序更快。然而,这种声音极端不理想不是这种情况,因此他们将使用索引来确定选择性(元素数量)。这允许更高级的应用程序设计,同时您可以完全并行计数查询,因此它们可以在同一时间在不同的线程中运行。运行index covered queries会更优化,但不幸的是,数组字段不支持它。
如果资源不足(许多用户请求,以及$ all数组中的大量元素),我不会尝试始终优化查询,在这种情况下,如果您在$中有多个元素所有数组我会选择总是最多3个作为合理的金额来检查,并选择其中最好的。或者将定义一些可接受的值,例如,如果前3个并行计数返回少于200个文档,我将接受它作为$ all数组中的第一个元素。当然,这取决于你是否必须处理大量的并发用户请求或者只是一些(为低延迟而优化的几个请求将使你能够一次准备和运行几个计数查询,而不会有压力降低数据库的风险)。