• home > DB > mongoDB >

    MongoDB查询数据优化—避免同时使用$or和sort()

    Author:[email protected] Date:

    在MongoDB中一起使用$or和sort()时,查询性能可能会很差首先看一下操作过程:mongos> db find({ "user" : "jhon"}) sort({"name" : 1

    在MongoDB中一起使用$or和sort()时,查询性能可能会很差

    首先看一下操作过程:
    复制代码
    mongos> db.find({ "user" : "jhon"}).sort({"name" : 
    1}).limit(100).explain() 
    { 
            "cursor" : "BtreeCursor user_1", 
            "nscanned" : 10100, 
            "nscannedObjects" : 10100, 
            "n" : 100, 
            "scanAndOrder" : true, 
            "millis" : 116, 
            "nYields" : 0, 
            "nChunkSkips" : 0, 
            "isMultiKey" : false, 
            "indexOnly" : false, 
            "indexBounds" : { 
                    "user" : [ 
                            [ 
                                    "jhon", 
                                    "jhon" 
                            ] 
                    ] 
            } 
    } 
    
    Second, I do $or query with sort(): 
    mongos> db.find({ "$or" : [ { "user" : "jhon"} , { "owner" : 
    "jhon"}]}).sort({"name" : 1}).limit(100).explain() 
    { 
            "cursor" : "BtreeCursor name_1", 
            "nscanned" : 1010090, 
            "nscannedObjects" : 1010090, 
            "n" : 100, 
            "millis" : 3800, 
            "nYields" : 0, 
            "nChunkSkips" : 0, 
            "isMultiKey" : false, 
            "indexOnly" : false, 
            "indexBounds" : { 
                    "name" : [ 
                            [ 
                                    { 
                                            "$minElement" : 1 
                                    }, 
                                    { 
                                            "$maxElement" : 1 
                                    } 
                            ] 
                    ] 
            } 
    } 
    
    Last, I do $or query without sort(): 
    mongos> db.find({ "$or" : [ { "user" : "jhon"} , { "owner" : 
    "jhon"}]}).limit(100).explain() 
    { 
            "cursor" : "BtreeCursor user_1", 
            "nscanned" : 100, 
            "nscannedObjects" : 100, 
            "n" : 100, 
            "millis" : 0, 
            "nYields" : 0, 
            "nChunkSkips" : 0, 
            "isMultiKey" : false, 
            "indexOnly" : false, 
            "indexBounds" : { 
                    "user" : [ 
                            [ 
                                    "jhon", 
                                    "jhon" 
                            ] 
                    ] 
            } 
    } 
    复制代码

    可以看出:

    第一次查询中, 单独使用sort()时性能很好。

    第二次查询中,联合使用了$or和sort()时,性能很差。

    第三次查询中,单独使用$or,性能很好。

    后来在官方论坛中提问,得知有一个bug:https://jira.mongodb.org/browse/SERVER-1205

    将来会修改,今天先记录一下。

    论坛回复:

    I believe the issue you are running into is expressed in this JIRA 
    ticket: https://jira.mongodb.org/browse/SERVER-1205

    I believe the query optimizer is choosing to use the name index and 
    walk it backwards. As it goes through the index it compares the user 
    and owner attributes to your parameters and collects them in sorted 
    order. As a result the nscanned objects is much larger than in the 
    other two cases. 

    I would vote up the issue to prioritize it. 

    -Tyler 

     

    原帖地址:http://groups.google.com/group/mongodb-user/browse_thread/thread/58428702d9485b8/40d6db4604a95a69?lnk=gst&q=gen+liu+%24or#40d6db4604a95a69

     

    注:如果查询中使用了$or,并且查询元素有索引的话,那么也会使用该元素的索引的。换句话说,如果查询中只有$or时(无sort时),不会出现这里的问题。另外在使用$and和$or的组合时也发现了类似的问题。

    在mongodb的计划中,2.5.w版本中可能会修改这个bug。

    我的项目中也遇到了这个问题,后来自己想了一个解决方案,暂时规避了这个问题,现在把这个方案分享出来,和大家讨论一下.

    这个解决方案是受到了mongos的源代码的启示,众所周知mongodb是分布式架构,那么在我们使用mongos查询并使用排序的时候,mongos需要把查询请求发送给各个shard,并将每个shard的查询结果

    存放在一个队列中(队列中已经排好序)。这里假定有2个shard(多个shard的原理是一样的),查询条件为{“age”:20},排序条件为:{"time":1},mongos实现示意图如下:

    1. mongos首先向两个shard发送查询排序命令。

    2.两个shard返回结果是排序后的两个队列,如图所示。

    3.客户端在取记录时,mongos取出两个队列的第一个元素,判断time值小的记录返回给客户端。

    4.客户端再取记录时,重复步骤3,从两个队列中取time值小的记录返回给客户端。

    正是受到mongos的启发,在遇到or查询并sort的情况时,把or的查询条件分解为多次查询,然后实现了一个查询类,里面保存了list<DBObject q>,然后向mongos发起多次查询排序请求,

    此时得到多个cursor,此时的cursor就类似于上面的队列,即此时得到了多个排序好的队列,然后经过简单比较后,依次把记录返回给客户端。

    例如,此时查询{"$or":[{"age":20},{"name":"li"}]},排序条件为{"Time":1},可以分解为2次查询:{"age":20},{"name":"li"},执行查询后,得到两个cursor,即两个队列,如下:、

    此时就可以重复mongos的步骤了,在客户端取记录时,对队列(cursor)中的第一个元素做比较,取出time值最小的记录返回给客户端。

    该解决方案的优点如下:

    1.可以使用索引,速度很快。

    2.封装类后,可以供多个业务使用。

    缺点如下:

    1. 每个队列中会缓存一些记录,这无形中造成了一些流量浪费和内存浪费。

     

    上面是我对这个方案的整体思路,欢迎大家讨论。

     


    转载本站文章《MongoDB查询数据优化—避免同时使用$or和sort()》,
    请注明出处:https://www.zhoulujun.cn/html/DB/mongoDB/2015_0730_225.html