MongoDB Multikey复合索引 - 需要帮助了解边界

时间:2013-12-24 15:13:52

标签: mongodb

我们最近决定重新访问一些MongoDB索引,并在使用包含多键部分的复合索引时遇到了一个奇特的结果。

重要的是要注意我们正在使用v2.4.5

TLDR :当使用具有多键部分的复合索引时,将删除用于范围限制的非多键字段的边界。

我将用一个例子来解释这个问题:

创建一些数据

db.demo.insert(
[{ "foo" : 1, "attr" : [  {  "name" : "a" },  {  "name" : "b" },  {  "name" : "c" } ]},
 { "foo" : 2, "attr" : [  {  "name" : "b" },  {  "name" : "c" },  {  "name" : "d" } ]},
 { "foo" : 3, "attr" : [  {  "name" : "c" },  {  "name" : "d" },  {  "name" : "e" } ]},
 { "foo" : 4, "attr" : [  {  "name" : "d" },  {  "name" : "e" },  {  "name" : "f" } ]}])

索引

db.demo.ensureIndex({'attr.name': 1, 'foo': 1})

查询&解释

查询'attr.name'但约束非多键字段'foo'的范围:

db.demo.find({foo: {$lt:3, $gt: 1}, 'attr.name': 'c'}).hint('attr.name_1_foo_1').explain()
{
    "cursor" : "BtreeCursor attr.name_1_foo_1",
    "isMultiKey" : true,
    "n" : 1,
    "nscannedObjects" : 2,
    "nscanned" : 2,
    "nscannedObjectsAllPlans" : 2,
    "nscannedAllPlans" : 2,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "attr.name" : [
            [
                "c",
                "c"
            ]
        ],
        "foo" : [
            [
                -1.7976931348623157e+308,
                3
            ]
        ]
    }
}

正如您所看到的,'foo'的范围不是在查询中定义的,一端被完全忽略,导致nscanned大于它应该。

更改范围操作数的顺序将改变删除的结尾:

db.demo.find({foo: {$gt: 1, $lt:3}, 'attr.name': 'c'}).hint('attr.name_1_foo_1').explain()
{
    "cursor" : "BtreeCursor attr.name_1_foo_1",
    "isMultiKey" : true,
    "n" : 1,
    "nscannedObjects" : 2,
    "nscanned" : 2,
    "nscannedObjectsAllPlans" : 2,
    "nscannedAllPlans" : 2,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "attr.name" : [
            [
                "c",
                "c"
            ]
        ],
        "foo" : [
            [
                1,
                1.7976931348623157e+308
            ]
        ]
    }
}

我们要么错过了一些多键索引基础知识,要么我们正面临一个错误。

我们经历过类似的话题,包括:

不幸的是,这些帖子解决了一个不同的用例,其中在多键值上设置了范围。

我们尝试过的其他事情:

  • 更改复合索引排序,从非多键字段开始。

  • 将'foo'值放在'attr'数组的每个子文档中,索引('attr.name','attr.foo')并在'attr'上使用$ elemMatch 'foo'的范围限制。

  • 在定义范围时使用$和运算符:

    db.demo.find({'attr.name': 'c', $and: [{num: {$lt: 3}}, {num: {$gt: 1}}]})
    
  • 使用MongoDB v2.5.4

以上都没有任何影响(v2.5.4通过完全倾倒范围的两端使事情变得更糟)。

非常感谢任何形式的帮助!

非常感谢,

Roi的

2 个答案:

答案 0 :(得分:2)

对于其中一个索引字段是数组的复合索引,MongoDB将仅使用范围查询的下限或上限来确保返回正确的匹配。请参阅SERVER-958以获取一个示例,其中约束上下索引边界将找不到预期文档。

如果您的范围查询位于数组字段上,则可以使用$elemMatch运算符在预期的索引范围内优化查询。与MongoDB 2.4一样,$elemMatch运算符不适用于非数组字段,因此遗憾的是,这对您的用例没有帮助。您可以在MongoDB问题跟踪器中观看/ upvote SERVER-6050: Consider allowing $elemMatch applied to non arrays

还有一个描述此行为的未解决问题SERVER-7959: Potentially unexpected scans with compound indexes when some fields are multikey

答案 1 :(得分:1)

$min$max运算符可以通过允许您显式指定索引边界来帮助解决此问题。

示例:

db.demo.find({foo: {$lt:3, $gt: 1}, 'attr.name': 'c'}).
 hint('attr.name_1_foo_1').
 min({'attr.name': 'c', foo: 1.000001}).
 max({'attr.name': 'c', foo: 3}).explain()

结果:

{
    "cursor" : "BtreeCursor attr.name_1_foo_1",
    "isMultiKey" : true,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 1,
    "nscannedAllPlans" : 1,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "start" : {
            "attr.name" : "c",
            "foo" : 1.000001
        },
        "end" : {
            "attr.name" : "c",
            "foo" : 3
        }
    }
}

但有一些重要的警告:

  1. $ min始终包含(如$ gte),$ max始终是独占的(如$ lt)。您可能需要调整值才能获得$ gt或$ lte。
  2. 的效果
  3. $ min和$ max中的字段必须与索引中的字段完全匹配。
  4. 每个查询只能有一组索引边界。 $ in或$或查询没有等价物。
  5. 虽然记录了操作员,但似乎不建议用于正常使用情况。
  6. Point 3对我来说是一个拦截器(我需要在数组字段上执行$ in),所以我仍在寻找另一种解决方案。

    来源:https://groups.google.com/forum/#!msg/mongodb-user/oxL8wuVdITA/uWJHVbMd_-8J