MongoDB 查询性能分析

Nov 2, 2018 阅读(557)

标签: MongoDB

cursor.explain("executionStats")db.collection.explain("executionStats") 方法提供关于查询性能的相关信息。这些信息可用于衡量查询是否使用了索引以及如何使用索引。

db.collection.explain() 还提供有关其他操作的执行信息。例如 db.collection.update()。 有关详情信息,请参见 db.collection.explain()

评价查询性能

考虑采用以下的 inventory 集合文档:

db.inventory.insert([
    { "_id" : 1, "item" : "f1", type: "food", quantity: 500 },    
    { "_id" : 2, "item" : "f2", type: "food", quantity: 100 },    
    { "_id" : 3, "item" : "p1", type: "paper", quantity: 200 },    
    { "_id" : 4, "item" : "p2", type: "paper", quantity: 150 },    
    { "_id" : 5, "item" : "f3", type: "food", quantity: 300 },    
    { "_id" : 6, "item" : "t1", type: "toys", quantity: 500 },    
    { "_id" : 7, "item" : "a1", type: "apparel", quantity: 250 },    
    { "_id" : 8, "item" : "a2", type: "apparel", quantity: 400 },    
    { "_id" : 9, "item" : "t2", type: "toys", quantity: 50 },    
    { "_id" : 10, "item" : "f4", type: "food", quantity: 75 }
]);

不使用索引查询

以下查询返回 quantity 值在 100 到 200 之间(含)的文档:

db.inventory.find( { quantity: { $gte: 100, $lte: 200 } } )

cursor.explain("executionStats")游标方法拼接到find 命令的结尾,显示查询选择的计划:


db.inventory.find(
   { quantity: { $gte: 100, $lte: 200 } }
).explain("executionStats")


explain() 方法返回如下结果:

{
   "queryPlanner" : {
         "plannerVersion" : 1,
         ...
         "winningPlan" : {
            "stage" : "COLLSCAN",
            ...
         }
   },
   "executionStats" : {
      "executionSuccess" : true,
      "nReturned" : 3,
      "executionTimeMillis" : 0,
      "totalKeysExamined" : 0,
      "totalDocsExamined" : 10,
      "executionStages" : {
         "stage" : "COLLSCAN",
         ...
      },
      ...
   },
   ...
}

匹配文档的数量与检查文档的数量之间的差异可能意味着,查询可以使用索引提高的查询效率。

基于索引查询

为了查询支持 quantity 字段,在 quantity 字段上新增索引:

db.inventory.createIndex( { quantity: 1 } )

使用 explain("executionStats") 方法,显示查询计划信息:

db.inventory.find(   { quantity: { $gte: 100, $lte: 200 } }).explain("executionStats")

这个 explain() 方法返回如下结果信息:

{
   "queryPlanner" : {
         "plannerVersion" : 1,
         ...
         "winningPlan" : {
               "stage" : "FETCH",
               "inputStage" : {
                  "stage" : "IXSCAN",
                  "keyPattern" : {
                     "quantity" : 1
                  },
                  ...
               }
         },
         "rejectedPlans" : [ ]
   },
   "executionStats" : {
         "executionSuccess" : true,
         "nReturned" : 3,
         "executionTimeMillis" : 0,
         "totalKeysExamined" : 3,
         "totalDocsExamined" : 3,
         "executionStages" : {
            ...
         },
         ...
   },
   ...
}

没有使用索引时查询将扫描整个集合中的10个文档返回匹配到的3个文档。查询时会将它们拉入内存并扫描每个文档的整体。这个结果非常耗性能并且潜在的会导致查询变慢。

当使用索引运行时,查询扫描3个索引条目然后3个文档中返回匹配到的3个文档,这个查询结果非常高效。

比较索引的性能

查询时不止一个索引时手动的比较索引性能,可以使用 hint() 方法再结合 explain() 方法。

考虑下面的查询:

db.inventory.find( {
   quantity: {
      $gte: 100, $lte: 300
   },
   type: "food"
} )

查询结果如下:

{ "_id" : 2, "item" : "f2", "type" : "food", "quantity" : 100 }
{ "_id" : 5, "item" : "f3", "type" : "food", "quantity" : 300 }

为了支持这个查询,添加复合索引。复合索引中字段的顺序很重要。

例如,添加如下的2个复合索引。第一个索引先使用 quantity ,再使用 type 字段创建索引。第二个索引先使用 type ,再使用 quantity 字段创建索引。

db.inventory.createIndex( { quantity: 1, type: 1 } )
db.inventory.createIndex( { type: 1, quantity: 1 } )

查询使用第一个索引来评估性能:

db.inventory.find(
   { quantity: { $gte: 100, $lte: 300 }, type: "food" }
).hint({ quantity: 1, type: 1 }).explain("executionStats")

这个 explain() 方法返回如下输出信息:

{
   "queryPlanner" : {
      ...
      "winningPlan" : {
         "stage" : "FETCH",
         "inputStage" : {
            "stage" : "IXSCAN",
            "keyPattern" : {
               "quantity" : 1,
               "type" : 1
            },
            ...
            }
         }
      },
      "rejectedPlans" : [ ]
   },
   "executionStats" : {
      "executionSuccess" : true,
      "nReturned" : 2,
      "executionTimeMillis" : 0,
      "totalKeysExamined" : 6,
      "totalDocsExamined" : 2,
      "executionStages" : {
      ...
      }
   },
   ...
}

MongoDB 扫描了6条索引键 (executionStats.totalKeysExamined) 并返回了2条匹配到的文档(executionStats.nReturned)。

查询使用第二个索引来评估性能:

db.inventory.find(
   { quantity: { $gte: 100, $lte: 300 }, type: "food" }
).hint({ type: 1, quantity: 1 }).explain("executionStats")

这个 explain() 方法返回如下输出信息:

{
   "queryPlanner" : {
      ...
      "winningPlan" : {
         "stage" : "FETCH",
         "inputStage" : {
            "stage" : "IXSCAN",
            "keyPattern" : {
               "type" : 1,
               "quantity" : 1
            },
            ...
         }
      },
      "rejectedPlans" : [ ]
   },
   "executionStats" : {
      "executionSuccess" : true,
      "nReturned" : 2,
      "executionTimeMillis" : 0,
      "totalKeysExamined" : 2,
      "totalDocsExamined" : 2,
      "executionStages" : {
         ...
      }
   },
   ...
}

MongoDB 扫描了2条索引键 (executionStats.totalKeysExamined) 并返回了2条匹配到的文档(executionStats.nReturned)。

这个查询例子中,复合索引 {type:1,quantity:1} 比复合索引 {quantity:1,type:1} 更高效。


建复合索引事字段顺序不能性能不同,联想到复合查询事查询字段顺序不同是不是对查询性能也有影响?在此还是沿用以上的数据测试:

测试场景一:仅保留一个复合索引,复合查询时顺序不同测试

db.inventory.dropIndex("type_1_quantity_1");
db.inventory.find(
   { quantity: { $gte: 100, $lte: 300 }, type: "food" }
).explain("executionStats")

db.inventory.find(
   { type: "food", quantity: { $gte: 100, $lte: 300 } }
).explain("executionStats")


测试场景二:使用两个不同顺序的复合索引来进行复合查询不同顺序测试

db.inventory.createIndex({ type: 1, quantity: 1 });
db.inventory.find(
   { quantity: { $gte: 100, $lte: 300 }, type: "food" }
).explain("executionStats")

db.inventory.find(
   { type: "food", quantity: { $gte: 100, $lte: 300 } }
).explain("executionStats")


总结: 通过以上两个场景测试,不同查询顺序查询事查询结果都是一样的,因此总结复合查询查询字段顺序对查询性能没有影响

MongoDB学习园