查询计划不使用带$ exists的索引

时间:2017-01-19 18:11:55

标签: mongodb database-performance

我正在运行MongoDB 3.0,我有一个包含(以及其他)以下索引的集合:

{
    "v" : 1,
    "key" : {
        "run_state" : 1
    },
    "name" : "run_state_1",
    "ns" : "xxx.yyyy"
},
{
    "v" : 1,
    "key" : {
        "visibility_state" : 1
    },
    "name" : "visibility_state",
    "ns" : "xxx.yyyy"
},
{
    "v" : 1,
    "key" : {
        "affiliate_id" : "hashed"
    },
    "name" : "affiliate_id_hashed",
    "ns" : "xxx.yyyy"
}

我需要获得符合某些条件的所有文件 其中之一是affiliate_id字段的存在。

如果我的查询包含$exists运算符和其他条件,例如:

db.getCollection('campaigns')
  .find({
    affiliate_id : { $exists : true }, 
    visibility_state : 'showing', 
    run_state : 'running'
  })
  .explain()

我得到以下执行计划:

{
  "winningPlan" : {
    "stage" : "KEEP_MUTATIONS",
      "inputStage" : {
        "stage" : "FETCH",
          "filter" : {
            "$and" : [ 
              {
                "visibility_state" : {
                  "$eq" : "showing"
                }
              }, 
              {
                "affiliate_id" : {
                  "$exists" : true
                }
              }
            ]
          },
          "inputStage" : {
            "stage" : "IXSCAN", // GOOD, it uses the index (:
              "keyPattern" : {
                "run_state" : 1
              },
              "indexName" : "run_state_1",
              "isMultiKey" : false,
              "direction" : "forward",
              "indexBounds" : {
                "run_state" : [ 
                  "[\"running\", \"running\"]"
                ]
              }
          }
      }
  }
}

因此查询将使用索引,并且执行速度非常快。

但是,如果查询仅包含$exists运算符,我会得到:

{
  "winningPlan" : {
    "stage" : "COLLSCAN", // Full collection scan D:
      "filter" : {
        "affiliate_id" : {
          "$exists" : true
        }
      },
      "direction" : "forward"
  },
}

因此,此查询将忽略索引并执行完整的集合扫描,并且执行效果非常差。

如果我改用$ne : null,结果与上面相同。

据我所知,在第一种情况下,索引查找只会在run_state字段中进行,而下一阶段只会过滤掉所提取的数据。

我只是想知道为什么Mongo不使用$exists$ne : null的索引。

修改

正如@lascort在评论中建议的那样,我已经将索引的类型更改为常规排序索引。现在使用索引,但它仍然有这个过滤阶段。

{
  "winningPlan" : {
    "stage" : "KEEP_MUTATIONS",
      "inputStage" : {
        "stage" : "FETCH",
          "filter" : {
            "affiliate_id" : {
              "$exists" : true
            }
          },
          "inputStage" : {
            "stage" : "IXSCAN",
              "keyPattern" : {
                "affiliate_id" : 1.0
              },
              "indexName" : "affiliate_id_1",
              "isMultiKey" : false,
              "direction" : "forward",
              "indexBounds" : {
                "affiliate_id" : [ 
                  "[MinKey, MaxKey]"
                ]
              }
          }
      }
  },
}

我还创建了一个包含run_statevisibility_stateaffiliate_id键的复合索引,同时保留了单个键。如果我运行包含3个字段的查询,我得到的结果与创建复合索引之前相同,FETCH阶段包含FILTER操作:

{
  "winningPlan" : {
    "stage" : "KEEP_MUTATIONS",
      "inputStage" : {
        "stage" : "FETCH",
          "filter" : {
            "$and" : [ 
              {
                "visibility_state" : {
                  "$eq" : "showing"
                }
              }, 
              {
                "affiliate_id" : {
                  "$exists" : true
                }
              }
            ]
          },
          "inputStage" : {
            "stage" : "IXSCAN",
              "keyPattern" : {
                "run_state" : 1.0
              },
              "indexName" : "run_state_1",
              "isMultiKey" : false,
              "direction" : "forward",
              "indexBounds" : {
                "run_state" : [ 
                  "[\"running\", \"running\"]"
                ]
              }
          }
      }
  }
},

如果我删除3个单个索引,那么我得到这个:

{
  "winningPlan" : {
    "stage" : "KEEP_MUTATIONS",
      "inputStage" : {
        "stage" : "FETCH",
          "filter" : {
            "affiliate_id" : {
              "$exists" : true
            }
          },
          "inputStage" : {
            "stage" : "IXSCAN",
              "keyPattern" : {
                "visibility_state" : 1.0,
                  "run_state" : 1.0,
                  "affiliate_id" : 1.0
              },
              "indexName" : "visibility_state_1_run_state_1_affiliate_id_1",
              "isMultiKey" : false,
              "direction" : "forward",
              "indexBounds" : {
                "visibility_state" : [ 
                  "[\"showing\", \"showing\"]"
                ],
                  "run_state" : [ 
                    "[\"running\", \"running\"]"
                  ],
                  "affiliate_id" : [ 
                    "[MinKey, MaxKey]"
                  ]
              }
          }
      }
  }
}

这似乎更有效率。

1 个答案:

答案 0 :(得分:1)

我会在这里回答您的评论,因为它更容易阅读,可能需要超过600个字符。

至于你是否应该创建一个新的复合索引并丢弃其他索引或保留它们实际上取决于一堆变量,比如你有多少其他索引,你的收藏有多大,你的收藏是什么?硬件配置等

考虑到这一点,如果您的计划是为所有查询创建单个复合索引,那么您确实需要提前考虑并牢记复合索引能够做什么。例如..

db.collection.createIndex({field1: 1, field2: 1})不会产生与db.collection.createIndex({field2: 1, field1: 1})相同的结果。如果您稍后创建的查询仅按field1过滤,则不会使用索引。

但要更多地关注您的方案..如果您按顺序创建包含字段run_stateaffiliate_idvisibility_state的索引。您只能使用条件{$exists: false}的查询也不会使用索引。我将引用mongo文档来进一步解释。

  

<强>前缀

     

索引前缀是索引字段的起始子集。对于   例如,考虑以下复合索引:

{ "item": 1, "location": 1, "stock": 1 }
     

索引具有以下索引前缀:

{ item: 1 }
{ item: 1, location: 1 }
     

对于复合索引,MongoDB可以   使用索引来支持对索引前缀的查​​询。因此,   MongoDB可以在以下字段中使用索引进行查询:

     
      
  • 项目字段
  •   
  • 项目字段和位置字段
  •   
  • 项目字段和位置字段以及库存字段
  •   
     

MongoDB也可以使用   自项目字段以来支持对项目和库存字段的查询的索引   对应一个前缀。但是,该指数效率不高   支持查询,因为它只是项目和股票的索引。

     

但是,MongoDB无法使用索引来支持包含的查询   以下字段,因为没有项目字段,没有列出   fields对应于前缀索引:

     
      
  • 位置字段,
  •   
  • 库存字段,或
  •   
  • 位置和库存字段。
  •   

这是指向文档https://docs.mongodb.com/v3.0/core/index-compound/

部分的链接