【问题标题】:MongoDB Multikey Compound Index - Need Help Understanding BoundsMongoDB 多键复合索引 - 需要帮助了解边界
【发布时间】:2015-05-06 03:21:24
【问题描述】:

我们最近决定重新审视我们的一些 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' 的范围限制。

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

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

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

我们将不胜感激任何形式的帮助!

非常感谢,

投资回报率

【问题讨论】:

    标签: mongodb


    【解决方案1】:

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

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

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

    【讨论】:

    • 您是否使用了不同的数据集? n:0怎么来的?
    • @Stennie,当我注意到您的回答时,已经是深夜了,所以我没有注意到 Asya 也提到的 'n:0' 结果。在我的大量数据集(以及演示)上尝试此优化时,报告的边界似乎没问题,但返回的结果始终为空。知道如何同时享受正确的界限和正确的结果吗?
    • @AsyaKamsky:应该是相同的数据集,但我在测试答案时混淆了一些东西;将重新测试。
    • @RoiTal:将重新检查.. 认为 $elemMatch 应该可以解决问题,但对我来说也为时已晚:)。在我处理更正的答案时,请随意不接受。
    • @Stennie 最后,这个问题似乎已经解决了!见jira.mongodb.org/browse/SERVER-15086问题是所有索引都被标记为“multikey”,所以indexbound解析不正确
    【解决方案2】:

    $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. $min 和 $max 中的字段必须与索引中的字段完全匹配。
    3. 每个查询只能有一组索引范围。 $in 或 $or 查询没有等价物。
    4. 虽然记录了操作符,但似乎不建议将其用于正常用例。

    第 3 点对我来说是一个障碍(我需要在数组字段上做一个 $in),所以我仍在寻找另一种解决方案。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-17
      • 2017-06-19
      • 2016-05-03
      • 1970-01-01
      • 2021-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多