如何优化mongoDB查询?

时间:2014-06-13 12:16:12

标签: mongodb mongodb-query aggregation-framework

我在mongoDB中有以下示例文档。

  {
    "location" : {
                "language" : null,
                "country" : "null",
                "city" : "null",
                "state" : null,
                "continent" : "null",
                "latitude" : "null",
                "longitude" : "null"
         },
    "request" : [
                 {
                  "referrer" : "direct",
                  "url" : "http://www.google.com/"
                  "title" : "index page"
                  "currentVisit" : "1401282897"
                  "visitedTime" : "1401282905"
                 },

                 {
                 "referrer" : "direct",
                 "url" : "http://www.stackoverflow.com/",
                 "title" : "index page"
                 "currentVisit" : "1401282900"
                 "visitedTime" : "1401282905"
                 },
           ......
               ]
    "uuid" : "109eeee0-e66a-11e3"
}

注意:

  1. 数据库包含多个10845文档
  2. 每个文档包含近100个请求(请求数组中有100个对象)。
  3. 技术/语言 - node.js

  4. 我有setProfiling来检查执行时间

    First Query - 13899ms
    Second Query - 9024ms 
    Third Query - 8310ms
    Fourth Query - 6858ms
    
  5. 使用索引

  6. 没有太大区别

    查询:

    我要执行以下aggregation queries来获取数据。

     var match = {"request.currentVisit":{$gte:core.getTime()[1].toString(),$lte:core.getTime()[0].toString()}};
    

    For Example: var match = {“request.currentVisit”:{$ gte:“1401282905”,$ lte:“1401282935”}};

    对于第三和第四个查询request.visitedTime而不是request.currentVisit

    1. 第一

      [
          { "$project":{
              "request.currentVisit":1,
              "request.url":1
          }},
         { "$match":{
             "request.1": {$exists:true}
         }},
         { "$unwind": "$request" },
         { "$match": match },
         { "$group": { 
             "_id": {
                 "url":"$request.url"
             },
             "count": { "$sum": 1 }
         }},
         { "$sort":{ "count": -1 } }
      ]
      
    2. 第二

      [
          { "$project": {
              "request.currentVisit":1,
              "request.url":1
          }},
          { "$match": {  
              "request":{ "$size": 1 }
          }},
          { "$unwind": "$request" },
          { "$match": match },
          { "$group": {
              "_id":{ 
                  "url":"$request.url"
              },
              "count":{ "$sum": 1 }
          }},
          { "$sort": { "count": -1} }
      ]
      
    3. 第三

      [
          { "$project": {
               "request.visitedTime":1,
               "uuid":1
          }},
          { "$match":{
              "request.1": { "$exists": true } 
          }},
          { "$match": match },
          { "$group": {
               "_id": "$uuid",
               "count":{ "$sum": 1 }
          }},
          { "$group": {
              "_id": null,
              "total": { "$sum":"$count" }}
          }}
      ]
      
    4. 第四

      [
          { "$project": {
              "request.visitedTime":1,
              "uuid":1
          }},
          { "$match":{
              "request":{ "$size": 1 }
          }},
          { "$match": match },
          { "$group": {
             "_id":"$uuid",
             "count":{ "$sum": 1 }
         }},
         { "$group": {
             "_id":null,
             "total": { "$sum": "$count" }
         }}
      ]
      
    5. 问题:

      获取数据的时间超过38091 ms

      有没有办法优化查询?

      任何建议都将不胜感激。

1 个答案:

答案 0 :(得分:8)

那么有一些问题,你肯定需要索引,但你不能有复合索引。它是您要在索引的数组中查询的“时间戳”值。还建议您将这些转换为数值而不是当前字符串,或者实际转换为BSON日期类型。后一种形式实际上是内部存储为数字时间戳值,因此通常会减小存储大小,这也会降低索引大小,并且可以更有效地匹配数值。

每个查询的一个大问题是,您在处理$unwind之后总是潜入“数组”内容,然后使用匹配“过滤”。虽然这是您想要对结果执行的操作,但由于您尚未在早期阶段应用相同的过滤器,因此当您$unwind时,管道中有许多文档与这些条件不匹配。结果是在此阶段您不需要处理的“大量”文档。在这里你不能使用索引。

您需要此匹配位于管道阶段的开始。在过滤实际数组之前,这会将文档缩小为“可能的”匹配。

所以以第一个为例:

[
   { "$match":{
       { "request.currentVisit":{ 
           "$gte":"1401282905", "$lte": "1401282935"
       }
   }},
   { "$unwind": "$request" },
   { "$match":{
       { "request.currentVisit":{ 
           "$gte":"1401282905", "$lte": "1401282935"
       }
   }},
   { "$group": { 
       "_id": {
           "url":"$request.url"
       },
       "count": { "$sum": 1 }
   }},
   { "$sort":{ "count": -1 } }
]

所以有一些变化。管道头部有一个$match。这会缩小文档范围并能够使用索引。这是最重要的性能考虑因素。黄金法则,始终首先“匹配”。

你在那里的$project是多余的,因为你不能只“投射”尚未解开的数组的字段。还有一种误解,即人们认为他们{{}}首先要减少管道。如果事实上有一个后来的$project$project语句实际上限制了字段,那么效果是非常小的,那么这将是“前向优化”所以事情确实会从管道处理中取出。上面的$group声明仍然可以进行更优化。

不需要查看数组是否实际存在于其他$match阶段,因为您现在在管道的开头“隐式地”执行此操作。如果更多条件让您更舒服,那么将它们添加到初始管道阶段。

其余部分保持不变,因为您然后$match数组和$unwind来过滤您实际需要的项目,然后再继续进行剩余处理。到目前为止,输入文档已经大大减少或减少了。

您可以使用MongoDB 2.6及更高版本执行的另一个替代方法是在您甚至$match之前“过滤”数组内容。这将产生这样的列表:

[
   { "$match":{
       { "request.currentVisit":{ 
           "$gte":"1401282905", "$lte": "1401282935"
       }
   }},
   { "$project": {
       "request": {
           "$setDifference": [
               { 
                   "$map": {
                       "input": "$request",
                       "as": "el",
                       "in": {
                           "$cond"": [
                               {
                                   "$and":[
                                       { "$gte": [ "1401282905", "$$el.currentVisit" ] },
                                       { "$lt": [ "1401282935", "$$el.currentVisit" ] }
                                   ]
                               }
                               "$el",
                               false
                           ]
                       }
                   }
               }
               [false]
           ]
       }
   }}
   { "$unwind": "$request" },
   { "$group": { 
       "_id": {
           "url":"$request.url"
       },
       "count": { "$sum": 1 }
   }},
   { "$sort":{ "count": -1 } }
]

这可以通过在**$unwind之前“过滤”数组来节省一些,这可能比之后执行$unwind更好。

但这是所有陈述的一般规则。您需要可用的索引,并且需要$match 首先

您可能会在单个查询中获得您真正想要的实际结果,但就目前而言,您的问题并非如此。尝试按照概述更改处理,您应该会看到显着的改进。

如果您仍然试图接受这可能是单数的方式,那么您可以随时提出另一个问题。