1

Mongo文档$all操作员的评论。

在当前版本中,使用 $all 运算符的查询必须扫描与查询数组中第一个元素匹配的所有文档。因此,即使使用索引来支持查询,操作也可能需要长时间运行,特别是当数组中的第一个元素不是很有选择性时。

鉴于我的数据库结构(如下所示),我正在考虑如何确保每当我$all使用 Mongo API 进行查询时,第一个参数将是more selective字段。

{ 
 _id: 1,
 records : [ {n: "Name", v: "Kevin"}, {n: "Age", v: 100} ]
}

我的第一个想法是定期创建“选择性”查询字段的动态列表。在代码中执行查询时,我会查看通过$all运算符搜索的 2+ 个字段,然后将更多/最具“选择性”的项目作为第一个元素。

有没有更好的方法或最佳实践来处理这个问题?

4

5 回答 5

2

如果您的数据库结构仅限于几个字段,我认为最好(也是最简单)的选择是将数据库结构从

{ 
 _id: 1,
 records : [ {n: "Name", v: "Kevin"}, {n: "Age", v: 100} ]
}

{ 
 _id: 1,
 Name: "Kevin",
 Age: 100
}

这样您就可以在 {Name: 1, Age: 1} 上的索引上查询您的数据库

但是,如果“记录”数组具有通用键/值结构并且您必须使用 $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 查询。

我希望这个解决方案能回答你的问题

于 2013-10-09T15:05:22.857 回答
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 }

在前两个比较行中,姓氏和名字可以是非常有选择性的(当名字或姓氏很少见时)或不是特别有选择性的(当它在整个数据库中很常见时)。

在第三行,如果我们不知道数据库的内容是什么,我们无法判断“年龄”值中的任何一个是否具有选择性。如果您要查询的是大学生的集合,那么第二个值最终将具有极高的选择性,但如果是高中生,则第一个值将是高度选择性的值。

如果您的任何查询是不等式,那么我会说它们不会成为一个好的第一位置元素,除非其他元素都不是选择性的。

但是,您如何跟踪哪些是选择性的,哪些不是选择性的呢?

您可以执行以下操作来跟踪“统计信息”(可以这么说):

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进行投票,它正在跟踪此类查询的索引使用改进。

于 2013-10-15T17:00:07.643 回答
0

看看这篇文,它介绍了使用通用结构时的一些注意事项,例如您在上面显示的结构。通常,选择性条件会对您产生不同的影响,因为 $all 中的每个条件都将针对同一个字段。要考虑的另一件事是在查询中使用explain()运算符来查看哪些执行得更好,等等。

于 2013-10-05T21:30:49.190 回答
0

如果您在数组字段上定义了索引,则对字段值运行多次计数将比 $all 阶段中的错误顺序更快。然而,这听起来不是最理想的,但事实并非如此,因此他们将使用索引来确定选择性(元素数量)。这允许更高级的应用程序设计,同时您可以完全并行运行计数查询,因此它们可以同时在不同的线程中运行。运行索引覆盖查询会更优化,但不幸的是,数组字段不支持它。

如果资源不足(大量用户请求和 $all 数组中的大量元素),我不会尝试始终将查询优化到最佳状态,在这种情况下,如果 $all 数组中有多个元素我将始终选择最多 3 个作为检查的合理数量,并从中选择最好的。或者会定义一些可接受的值,例如,如果前 3 个并行计数返回少于 200 个文档,我会接受它作为 $all 数组中的第一个元素。当然,这取决于您是否必须处理大量并发用户请求或仅处理一些请求(针对低延迟进行优化的少数请求将使您能够一次准备和运行多个计数查询,而不会降低数据库压力)。

于 2013-10-13T14:29:07.120 回答
0

旁注:我认为 $all 总是会像 MongoDB 文档中提到的那样缓慢执行。使用 MongoDB 时要考虑的一件事是查询应始终尝试将投影减少到最低形式,然后处理剩余的数据。任何数组操作都将从在查找查询中添加另一个条件中受益匪浅。

话虽这么说,因为 $all 应该在内部检查所有元素,即使单个值是索引,它仍然有 m*n 组合。从技术上讲,顺序应该无关紧要,但我想确实如此。

始终尽量避免 $all、$nin、$ne 操作。即使在 SQL 中也是如此。

于 2013-10-09T00:21:15.573 回答