MongoDB:即使查询的所有字段都被索引,为啥我的扫描对象值很高?

Posted

技术标签:

【中文标题】MongoDB:即使查询的所有字段都被索引,为啥我的扫描对象值很高?【英文标题】:MongoDB: Why is my scannedObjects value high even all fields of the query are indexed?MongoDB:即使查询的所有字段都被索引,为什么我的扫描对象值很高? 【发布时间】:2015-01-28 20:36:58 【问题描述】:

我正在索引一个集合中的三个字段,其中一个是数组。我正在对这三个字段运行查询,并且查询需要一秒钟以上,集合中有 300K 字段。当我在查询上调用解释时,我看到我的索引被正确使用,但扫描对象的数量非常高。我想这就是性能低下的原因。


    "_id" : ObjectId("54c8f110389a46153866d82e"),
    "mmt" : [ 
        "54944cfd90671810ccbf2552", 
        "54c64029038d8c3aff41ad6d", 
        "54c64029038d8c3aff41ad73", 
        "54c8f151038d8c3aff453669", 
        "54c8f151038d8c3aff45366d"
    ],
    "p" : 8700,
    "sui" : "3810d5cf-3032-4a77-9715-a42e010e569c"
    /* also some more fields */

有了这个索引:


    "sui" : 1,
    "p" : 1,
    "mmt" : 1

我正在尝试运行此查询:

db.my_coll.find(

    "mmt" :  "$all" :
        [
            "54944cfd90671810ccbf2552", "54ac1db0e3f494afd4ded4c8", "54ac1db1e3f494afd4ded66a", "54ac1db1e3f494afd4ded66b", "54c8b671038d8c3aff453649", "54c8f154038d8c3aff45368f", "54c8f154038d8c3aff453694"
        ]
,
    "sui" :  "$ne" : "bde0f517-b942-4823-b2c8-a41900f46641" ,
    "p":  $gt: 100, $lt: 1000 


).limit(1000).explain()

解释的结果是:


    "cursor" : "BtreeCursor sui_1_p_1_mmt_1",
    "isMultiKey" : true,
    "n" : 16,
    "nscannedObjects" : 14356,
    "nscanned" : 129223,
    "nscannedObjectsAllPlans" : 14356,
    "nscannedAllPlans" : 129223,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 1009,
    "nChunkSkips" : 0,
    "millis" : 1276,
    "indexBounds" : 
        "sui" : [ 
            [ 
                
                    "$minElement" : 1
                , 
                "bde0f517-b942-4823-b2c8-a41900f46641"
            ], 
            [ 
                "bde0f517-b942-4823-b2c8-a41900f46641", 
                
                    "$maxElement" : 1
                
            ]
        ],
        "p" : [ 
            [ 
                -Infinity, 
                1000
            ]
        ],
        "mmt" : [ 
            [ 
                "54944cfd90671810ccbf2552", 
                "54944cfd90671810ccbf2552"
            ]
        ]
    ,
    "server" : "shopkrowdMongo:27017",
    "filterSet" : false,
    "stats" : 
        "type" : "LIMIT",
        "works" : 129224,
        "yields" : 1009,
        "unyields" : 1009,
        "invalidates" : 0,
        "advanced" : 16,
        "needTime" : 129207,
        "needFetch" : 0,
        "isEOF" : 1,
        "children" : [ 
            
                "type" : "KEEP_MUTATIONS",
                "works" : 129224,
                "yields" : 1009,
                "unyields" : 1009,
                "invalidates" : 0,
                "advanced" : 16,
                "needTime" : 129207,
                "needFetch" : 0,
                "isEOF" : 1,
                "children" : [ 
                    
                        "type" : "FETCH",
                        "works" : 129224,
                        "yields" : 1009,
                        "unyields" : 1009,
                        "invalidates" : 0,
                        "advanced" : 16,
                        "needTime" : 129207,
                        "needFetch" : 0,
                        "isEOF" : 1,
                        "alreadyHasObj" : 0,
                        "forcedFetches" : 0,
                        "matchTested" : 16,
                        "children" : [ 
                            
                                "type" : "IXSCAN",
                                "works" : 129223,
                                "yields" : 1009,
                                "unyields" : 1009,
                                "invalidates" : 0,
                                "advanced" : 14356,
                                "needTime" : 114867,
                                "needFetch" : 0,
                                "isEOF" : 1,
                                "keyPattern" : " sui: 1.0, p: 1.0, mmt: 1.0 ",
                                "isMultiKey" : 1,
                                "boundsVerbose" : "field #0['sui']: [MinKey, \"bde0f517-b942-4823-b2c8-a41900f46641\"), (\"bde0f517-b942-4823-b2c8-a41900f46641\", MaxKey], field #1['p']: [-inf.0, 1000.0), field #2['mmt']: [\"54944cfd90671810ccbf2552\", \"54944cfd90671810ccbf2552\"]",
                                "yieldMovedCursor" : 0,
                                "dupsTested" : 14356,
                                "dupsDropped" : 0,
                                "seenInvalidated" : 0,
                                "matchTested" : 0,
                                "keysExamined" : 129223,
                                "children" : []
                            
                        ]
                    
                ]
            
        ]
    

找到的项目数是16,但scannedObjects的数量是14356。我不明白为什么mongodb扫描这么多文档,即使查询的所有字段都被索引了。

    mongodb 为什么要扫描这么多对象? 如何更快地获得此查询的结果?

我使用的 mmt 数组不会随着时间的推移而增长或缩小,但其中的元素数量在 5 到 15 之间变化。我需要使用 $in、$all 和 $nin 的几种组合来查询该字段。此集合中的项目数量可能会增长超过 3000 万。有没有办法可靠地快速获得这种情况下的结果?

更新 1:

我尝试删除 sui 字段和 $ne 查询。更新说明:


    "cursor" : "BtreeCursor p_1_mmt_1",
    "isMultiKey" : true,
    "n" : 17,
    "nscannedObjects" : 16338,
    "nscanned" : 16963,
    "nscannedObjectsAllPlans" : 16338,
    "nscannedAllPlans" : 33930,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 265,
    "nChunkSkips" : 0,
    "millis" : 230,
    "indexBounds" : 
        "p" : [ 
            [ 
                -Infinity, 
                1000
            ]
        ],
        "mmt" : [ 
            [ 
                "54944cfd90671810ccbf2552", 
                "54944cfd90671810ccbf2552"
            ]
        ]
    ,
    "server" : "shopkrowdMongo:27017",
    "filterSet" : false,
    "stats" : 
        "type" : "LIMIT",
        "works" : 16966,
        "yields" : 265,
        "unyields" : 265,
        "invalidates" : 0,
        "advanced" : 17,
        "needTime" : 16947,
        "needFetch" : 0,
        "isEOF" : 1,
        "children" : [ 
            
                "type" : "KEEP_MUTATIONS",
                "works" : 16966,
                "yields" : 265,
                "unyields" : 265,
                "invalidates" : 0,
                "advanced" : 17,
                "needTime" : 16947,
                "needFetch" : 0,
                "isEOF" : 1,
                "children" : [ 
                    
                        "type" : "FETCH",
                        "works" : 16965,
                        "yields" : 265,
                        "unyields" : 265,
                        "invalidates" : 0,
                        "advanced" : 17,
                        "needTime" : 16947,
                        "needFetch" : 0,
                        "isEOF" : 1,
                        "alreadyHasObj" : 0,
                        "forcedFetches" : 0,
                        "matchTested" : 17,
                        "children" : [ 
                            
                                "type" : "IXSCAN",
                                "works" : 16964,
                                "yields" : 265,
                                "unyields" : 265,
                                "invalidates" : 0,
                                "advanced" : 16338,
                                "needTime" : 626,
                                "needFetch" : 0,
                                "isEOF" : 1,
                                "keyPattern" : " p: 1.0, mmt: 1.0 ",
                                "isMultiKey" : 1,
                                "boundsVerbose" : "field #0['p']: [-inf.0, 1000.0), field #1['mmt']: [\"54944cfd90671810ccbf2552\", \"54944cfd90671810ccbf2552\"]",
                                "yieldMovedCursor" : 0,
                                "dupsTested" : 16338,
                                "dupsDropped" : 0,
                                "seenInvalidated" : 0,
                                "matchTested" : 0,
                                "keysExamined" : 16963,
                                "children" : []
                            
                        ]
                    
                ]
            
        ]
    

查询执行得更好,但scannedObjects仍然很高。

【问题讨论】:

记住扫描的对象只不过是一个计数器,因此如果您扫描一个子文档,它将远高于返回的数量 【参考方案1】:

我认为 marcinn 将 $ne 列为最可能的罪魁祸首是正确的,但更新 1 向我们展示了 $all 也是一个问题。该查询使用索引的mmt 部分来查找包含数组中的值之一的文档,然后必须扫描mmt 数组的其余部分以验证$all 数组中的所有值是否在可能匹配的文档的mmt 数组。这意味着必须加载和扫描可能匹配的文档,因此它被视为已扫描对象。为了非常清楚地展示这种行为,请考虑以下示例:

> db.test.drop()
> for (var i = 0; i < 100; i++) db.test.insert( "x" : [1, 2] )
> for (var i = 0; i < 100; i++) db.test.insert( "x" : [1, 3] )
> db.test.ensureIndex( "x" : 1 )
> db.test.find( "x" :  "$all" : [1, 2]  ).explain(true)

这显示了 n = 100nscanned = nscannedObjects = 200 使用值 1 作为两个索引边界的结果,而逻辑上等效的查询

> db.test.find( "x" :  "$all" : [2, 1]  ).explain(true)

显示n = nscanned = nscannedObjects = 100,两个索引边界的值都为 2。

【讨论】:

这很有帮助,谢谢。您能否提出一种以可扩展的方式解决此问题的方法? $ne 没有选择性,不能很好地扩展。您使用的是哪个版本的 MongoDB? $all 的性能故障已在 SERVER-1000 中得到解决。解决方法是通过索引交集来处理这种情况,这是 MongoDB >= 2.6 的一个特性。可能值得重新考虑架构设计,以便能够编写更具扩展性的查询。如果您愿意,您应该提出一个单独的问题,解释您的文档结构和查询背后的用例,我们将对其进行研究。【参考方案2】:

基本上这是因为 $ne 不能(有效地)使用索引。所以你的索引被使用只是因为你首先通过 mnt 字段查询然后它的阅读

某些查询操作不是选择性的。这些操作不能使用 索引有效或根本不能使用索引。

不等式运算符 $nin 和 $ne 的选择性不是很高,因为它们 经常匹配索引的很大一部分。结果,在大多数情况下, 带有索引的 $nin 或 $ne 查询可能不会比 $nin 或 $ne 必须扫描集合中所有文档的查询

http://docs.mongodb.org/manual/core/query-optimization/

【讨论】:

我删除了 sui。查询执行得更好,但scannedObjects 仍然很高。我在问题中加入了新的解释。

以上是关于MongoDB:即使查询的所有字段都被索引,为啥我的扫描对象值很高?的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB 覆盖索引查询

mongodb使用总结

为啥mongodb返回地理索引错误?

即使有索引,MongoDB 也发现查询非常慢

为啥这个 mySQL 查询非常慢?

MongoDB,即使它们形成分区,查询字段也会减慢查询速度吗?