使用sort()在不同的字段上为MongoDB索引更快的find()

时间:2013-09-25 07:29:37

标签: mongodb indexing

我正在运行大量此类类型的查询:

db.mycollection.find({a:{$gt:10,$lt:100}, b:4}).sort({c:-1, a:-1})

我应该使用什么样的索引加快速度?我想我需要同时拥有{a:1, b:1}{c:-1, a:-1},我是对的吗?或者这些索引会以某种方式相互干扰而不会增加性能?

编辑:对我来说,实际问题是我在一个循环中运行了很多查询,其中一些是小范围的,另一些是大范围的。如果我将索引放在{a:1, b:1}上,它会非常快速地选择小块,但是当涉及到大范围时,我会看到错误“sort()没有索引的数据太多”。如果,否则,我将索引放在{c:-1, a:-1}上,没有错误,但较小的块(以及更多的块)处理得慢得多。那么,如何保持较小范围的选择速度,但不会在大量数据上出错?

如果重要,我会通过Python的pymongo运行查询。

5 个答案:

答案 0 :(得分:4)

如果你已经阅读了文档,你会发现在这里使用两个索引是没用的,因为MongoDB每个查询只使用一个索引(除非它是$or),直到:https://jira.mongodb.org/browse/SERVER-3071被实现

不仅如此,而且在使用复合排序时,索引中的顺序必须与正确使用索引的排序顺序相匹配,如下所示:

  

或者这些索引会以某种方式相互干扰而不会增加性能?

如果实施了交叉,那么{a:1,b:1}与排序不匹配,而{c:-1,a:-1}对于回答find()而言a加上{a:-1,b:1,c:-1} 不是前缀的次优那个化合物。

因此,最佳索引的迭代将立即:

$gt

但这不是完整的故事。由于$lt$in实际上是范围,例如{{1}}它们会遇到与索引相同的问题,因此本文应该提供答案:http://blog.mongolab.com/2012/06/cardinal-ins/并没有真正看到任何理由重复其内容。

答案 1 :(得分:3)

  

免责声明:适用于MongoDB v2.4

使用提示是一个不错的解决方案,因为它会强制查询使用您选择的索引,因此您可以使用不同的索引优化查询,直到您满意为止。 缺点是您为每个请求设置自己的索引。 我更喜欢设置整个集合的索引,让Mongo为我选择正确的(最快的)索引,特别是对于重复使用的查询。

您的查询中有两个问题:

  • 从不对未编入索引的参数进行排序。您将收到此错误:“如果.find()中的文档数量非常大,那么sort()的数据太多而没有索引”,大小取决于mongo的版本你用。这意味着您必须拥有AC 的索引才能使查询正常运行。
  • 现在出现更大的问题。您正在对param $lt)执行范围查询($gtA),这不能与Mongo一起使用。 MongoDB一次只使用一个索引,您在同一个参数上使用两个索引。在您的代码中有几种解决方案可以处理它:

    1. r = range( 11,100 )
      db.mycollection.find({a:{$in: r }, b:4}).sort({c:-1, a:-1})

    2. 在查询中仅使用$lt$gt
      db.mycollection.find({ a: { $lt:100 }, b:4}).sort({c:-1, a:-1})
      获取结果并在python代码中过滤它们。 此解决方案将返回更多数据,因此如果您有数百万的结果小于A=11,请不要使用它!
      如果您选择此选项,请确保使用compound key AB

在您的查询中使用$or时请注意,因为$或less efficiently optimized$in使用索引一样。

答案 2 :(得分:2)

如果你定义一个索引{c:-1,a:-1,b:1},它将有助于你做一些考虑。

使用此选项将完全扫描索引,但根据索引值,将仅访问适当的文档,并且将按正确的顺序访问它们,以便在获得结果后不需要订购阶段。如果索引是巨大的,我不知道它将如何表现,但我假设当结果很小时,如果结果集很大,它会更慢。

关于前缀匹配。如果您提示索引并且较低级别可用于提供查询,则将使用这些级别。为了证明这种行为,我做了一个简短的测试。

我准备了测试数据:

> db.createCollection('testIndex')
{ "ok" : 1 }
> db.testIndex.ensureIndex({a:1,b:1})
> db.testIndex.ensureIndex({c:-1,a:-1})
> db.testIndex.ensureIndex({c:-1,a:-1,b:1})
> for(var i=1;i++<500;){db.testIndex.insert({a:i,b:4,c:i+5});}
> for(var i=1;i++<500;){db.testIndex.insert({a:i,b:6,c:i+5});}

使用提示查询的结果:

> db.testIndex.find({a:{$gt:10,$lt:100}, b:4}).hint('c_-1_a_-1_b_1').sort({c:-1, a:-1}).explain()
{
    "cursor" : "BtreeCursor c_-1_a_-1_b_1",
    "isMultiKey" : false,
    "n" : 89,
    "nscannedObjects" : 89,
    "nscanned" : 588,
    "nscannedObjectsAllPlans" : 89,
    "nscannedAllPlans" : 588,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 1,
    "indexBounds" : {
        "c" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ],
        "a" : [
            [
                100,
                10
            ]
        ],
        "b" : [
            [
                4,
                4
            ]
        ]
    },
    "server" :""
}

输出的说明是扫描索引,这就是nscanned为588(扫描的索引条目和文档的数量)的原因,nscannedObjects处的数字是扫描文档的数量。所以基于索引mongo只读取符合条件的文档(索引部分覆盖左右)。正如您所看到的,scanAndOrder为false,因此没有排序阶段。 (这意味着如果索引在内存中会很快)

与其他人链接的文章一起:http://blog.mongolab.com/wp-content/uploads/2012/06/IndexVisitation-4.png你必须首先在索引和查询键之后放置排序键,如果它们有子集匹配,你必须以相同的顺序包含子集因为它们在排序标准中(而对于查询部分无关紧要)。

答案 3 :(得分:0)

我认为最好更改find​​中字段的顺序。

db.mycollection.find({b:4, a:{$gt:10,$lt:100}}).sort({c:-1, a:-1})

然后添加索引

{b:1,a:-1,c:-1}

答案 4 :(得分:0)

我尝试了两个不同的索引,

索引的顺序为db.mycollection.ensureIndex({a:1,b:1,c:-1})

和解释计划如下所示

{
    "cursor" : "BtreeCursor a_1_b_1_c_-1",
    "nscanned" : 9542,
    "nscannedObjects" : 1,
    "n" : 1,
    "scanAndOrder" : true,
    "millis" : 36,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "isMultiKey" : false,
    "indexOnly" : false,
    "indexBounds" : {
        "a" : [
            [
                3,
                10000
            ]
        ],
        "b" : [
            [
                4,
                4
            ]
        ],
        "c" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ]
    }
}

db.mycollection.ensureIndex({b:1,c:-1,a:-1})

的其他索引
> db.mycollection.find({a:{$gt:3,$lt:10000},b:4}).sort({c:-1, a:-1}).explain()
{
    "cursor" : "BtreeCursor b_1_c_-1_a_-1",
    "nscanned" : 1,
    "nscannedObjects" : 1,
    "n" : 1,
    "millis" : 8,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "isMultiKey" : false,
    "indexOnly" : false,
    "indexBounds" : {
        "b" : [
            [
                4,
                4
            ]
        ],
        "c" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ],
        "a" : [
            [
                10000,
                3
            ]
        ]
    }
}
> 

我相信,既然您在一系列值上查询'a'而在特定值上查询'b',我猜第二种选择更合适。 nscanned对象从9542更改为1