MongoDB多键复合索引-需要了解边界的帮助吗

MongoDB多键复合索引-需要了解边界的帮助吗,mongodb,Mongodb,我们最近决定重新访问一些MongoDB索引,并在使用包含多键部分的复合索引时遇到了一个特殊的结果 需要注意的是,我们使用的是v2.4.5 TLDR:当使用包含多键部分的复合索引时,用于范围限制的非多键字段的边界将被删除 我将用一个例子来解释这个问题: 创建一些数据 索引 查询和解释 查询“attr.name”,但约束非多键字段“foo”的范围: db.demo.find({foo: {$lt:3, $gt: 1}, 'attr.name': 'c'}).hint('attr.name_1_foo

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

需要注意的是,我们使用的是v2.4.5

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

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

创建一些数据

索引

查询和解释

查询“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”的范围与查询中定义的范围不同,一端被完全忽略,这导致nscaned大于它应该的范围

更改范围操作数的顺序将改变删除的端点:

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
            ]
        ]
    }
}
我们要么错过了一些多键索引的基础知识,要么面临一个bug

我们也讨论过类似的话题,包括:

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

我们尝试做的其他事情:

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

  • 将'foo'值放入'attr'数组中的每个子文档中,按('attr.name','attr.foo')索引,并对'attr'执行$elemMatch,对'foo'执行范围约束

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

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

上述任何一项都没有产生任何影响(v2.5.4通过完全倾倒量程两端使情况变得更糟)

任何形式的帮助都将不胜感激

非常感谢,


Roi

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

如果范围查询位于数组字段上,则可以使用运算符在预期索引范围内优化查询。与MongoDB 2.4一样,
$elemMatch
操作符不适用于非数组字段,因此不幸的是,这对您的用例没有帮助。您可以在MongoDB问题跟踪器中观看/投票

还有一个未解决的问题描述了这种行为。

and运算符允许您显式指定索引边界,从而有助于解决此问题

例如:

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
        }
    }
}
不过,有一些重要的注意事项:

  • $min总是包含的(如$gte),而$max总是独占的(如$lt)。您可能需要调整您的值以获得$gt或$lte的效果
  • $min和$max中的字段必须与索引中的字段完全匹配
  • 每个查询只能有一组索引边界。$in或$or查询没有等效项
  • 虽然操作员有文档记录,但似乎不建议在正常使用情况下使用
  • 第3点对我来说是一个拦截器(我需要在数组字段上做$in),所以我仍然在寻找另一个解决方案


    来源:

    您是否使用了不同的数据集?为什么n:0?@Stennie,我注意到你的答案时已经很晚了,所以我没有注意到Asya也提到的“n:0”结果。在我庞大的数据集(以及演示)上尝试此优化时,报告的边界似乎正常,但返回的结果总是空的。知道如何享受正确的界限和正确的结果吗?@AsyaKamsky:应该是相同的数据集,但我在测试答案时混淆了一些东西;将重新测试。@RoiTal:将重新检查。。我以为$elemMatch应该做这件事,但对我来说也太晚了:)。当我在做一个正确的答案时,请不要介意我的回答。@Stennie最后,这个问题似乎已经解决了!问题是所有索引都标记为“多键”,因此IndexBond分辨率不正确
    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
            }
        }
    }