如何优化mongoDB查询?

5

我在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. The database contains more than 10845 document
  2. Each document contains nearly 100 request(100 object in the request array).
  3. Technology/Language - node.js

  4. I had setProfiling to check the execution time

    First Query - 13899ms
    Second Query - 9024ms 
    Third Query - 8310ms
    Fourth Query - 6858ms
    
  5. There is no much difference using indexing

查询:

我需要执行以下聚合查询以获取数据。

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

例如:var match = {"request.currentVisit":{$gte:"1401282905",$lte:"1401282935"}}; 对于第三和第四个查询,请使用"request.visitedTime"代替"request.currentVisit"。
  1. First

    [
        { "$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. Second

    [
        { "$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. Third

    [
        { "$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. Forth

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

问题:

获取数据需要 38091 ms 的时间。

是否有优化查询的方法?

欢迎提出建议。


您尝试在“request”子文档上创建索引了吗?如果适用于您的应用程序,那当然可以尝试。 - Lukas Hajdu
2
能展示并解释一下吗? - Sammaye
@NeilLunn 条件是 { "$match": {"request.currentVisit":{$gte:"1401282905",$lte:"1401282935"}} }。我已将其添加到常见的查询第二个位置。 - karthick
是的。现在我们可以看到这些都是字符串,你真的应该修复它们。而且我假设这些根本没有被索引。 - Neil Lunn
@NeilLunn需要使用int而不是string来代替request.currentVisit字段吗?request.currentVisit有一个索引。 - karthick
显示剩余3条评论
1个回答

11
有一些问题需要解决,你肯定需要索引,但不能使用复合索引。你要查询数组中的“时间戳”值进行索引。建议将其转换为数值类型而不是当前的字符串,或者转换为BSON日期类型。后者实际上以数字时间戳值存储,因此可以减少存储空间和索引大小,并且在匹配数字值时更有效率。
每个查询的大问题是,在处理$unwind并使用match“过滤”之后,您总是要深入“数组”内容。虽然这正是您想要的结果,但由于您没有在较早阶段应用相同的过滤器,因此当您$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 数组并使用 $match 过滤出您实际想要的项目,然后继续进行剩余的处理。到目前为止,输入文档已经显着减少,或者说它们已经被减少到了最大程度。
您可以在 MongoDB 2.6 及以上版本中使用另一种选择,即在甚至 **$unwind 数组之前“过滤”数组内容。这将产生以下列表:
[
   { "$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之前对数组进行“筛选”,这可能比之后进行$match更好。

但是,这适用于您所有的语句。您需要可用的索引,并且需要先进行$match

可能可以通过单个查询获得实际想要的结果,但按照目前的方式提出问题并非如此。尝试按照上述方式更改处理方式,您应该会看到显着的改进。

如果您仍然试图理解这可能是如何成为单数的,那么您可以随时提出另一个问题。


1
谢谢您的解释。它正在运行。性能有很大改善。 - karthick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接