MongoDB的索引与执行计划

索引定义

索引(Index)是帮助数据库高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。

索引的数据结构

现在的数据库(mongo,mysql等)索引多采用B-Tree数据结构,不懂BTree的同学先自行去了解下,个人觉得这篇文章比较易懂一些,http://www.cnblogs.com/coder2012/p/5309197.html

为什么使用B-Tree(B+Tree)

红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用B-/+Tree作为索引结构。为什么呢?

先从B-Tree分析,根据B-Tree的定义,可知检索一次最多需要访问h(h为数高)个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:
每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。
B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。
综上所述,用B-Tree作为索引结构效率是非常高的。

而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。
B+Tree更适合外存索引,原因和内节点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小:
dmax = floor(pagesize / (keysize + datasize + pointsize)) (pagesize – dmax >= pointsize)

dmax = floor(pagesize / (keysize + datasize + pointsize)) – 1 (pagesize – dmax < pointsize)
floor表示向下取整。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

MongoDB索引类型

众所周知,MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),_id是文档唯一的标识,为了保证能根据文档id快递查询文档,MongoDB默认会为集合创建_id字段的索引(默认索引)。

mongo-9552:PRIMARY&gt; db.person.getIndexes() // 查询集合的索引信息
[
    {
        "ns" : "test.person",  // 集合名
        "v" : 1,               // 索引版本
        "key" : {              // 索引的字段及排序方向
            "_id" : 1           // 根据_id字段升序索引
        },
        "name" : "_id_"        // 索引的名称
    }
]

除此之外,MongoDB还支持多种类型的索引,包括单值索引、复合索引、多key(多值)索引、文本索引、哈希索引、地理位置索引等,每种类型的索引有不同的使用场合。

单值索引 (Single Field Index)

    db.person.createIndex( {age: 1} ) 

上述语句针对age创建了单字段索引,其能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。

{age: 1} 代表升序索引,也可以通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。

复合索引 (Compound Index)

复合索引是Single Field Index的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推,如下针对age, name这2个字段创建一个复合索引。

    db.person.createIndex( {age: 1, name: 1} ) 

上述索引对应的数据组织类似下表,与{age: 1}索引不同的时,当age字段相同时,在根据name字段进行排序,所以pos5对应的文档排在pos3之前。

AGE,NAME位置信息
18,adampos5
18,jackpos3
19,jackpos1
20,rosepos2
21,tonypos4

复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询,比如db.person.find( {age: 18, name: “jack”} ),也能满足所有能匹配符合索引前缀的查询,这里{age: 1}即为{age: 1, name: 1}的前缀,所以类似db.person.find( {age: 18} )的查询也能通过该索引来加速;但db.person.find( {name: “jack”} )则无法使用该复合索引。如果经常需要根据『name字段』以及『name和age字段组合』来查询,则应该创建如下的复合索引

db.person.createIndex( {name: 1, age: 1} ) 

这里有个需要注意的地方:
对应上面的复合索引db.person.createIndex( {name: 1, age: 1} ) ,查询语句的索引使用顺序db.person.find({name: "adam " ,age: 18})与db.person.find({age: 18,name: "adam " })相同,都会使用到复合索引db.person.createIndex( {name: 1, age: 1} ),因为MongoDB底层会对查询语句进行优化重新排列顺序(mysql也是同样的原理)。

除了查询的需求能够影响索引的顺序,字段的值分布也是一个重要的考量因素,即使person集合所有的查询都是『name和age字段组合』(指定特定的name和age),字段的顺序也是有影响的。

age字段的取值很有限,即拥有相同age字段的文档会有很多;而name字段的取值则丰富很多,拥有相同name字段的文档很少;显然先按name字段查找,再在相同name的文档里查找age字段更为高效。

多key索引 (Multikey Index)

当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个habbit字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用habbit字段的多key索引。

{"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
db.person.createIndex( {habbit: 1} )  // 自动创建多key索引
db.person.find( {habbit: "football"} )

哈希索引(Hashed Index)

是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。

地理位置索引(Geospatial Index)

能很好的解决O2O的应用场景,比如『查找附近的美食』、『查找某个区域内的车站』等。
这样的查询也变得越来越流行:要找到离当前位置近期的N个场所,如要找到给定经纬度坐标周围近期的咖啡馆。
MongoDB为坐标平面提供了专门的索引。称作:地理空间索引
相比于createIndex创建索引同样也能够用ensureIndex来创建,仅仅只是參数不是1或-1,而是"2d"
db.map.ensureIndex({“gps”:“2d”})
"gps"必需是某种形式的一对值:一个包括两个元素的数组或是包括两个键的内嵌文档;键值名能够随意。例如以下:
{ “gps” : [ 0, 100 ] }
{ “gps” : { “x” : -30, “y” : 30 } }
{ “gps” : { “latitude” : -180, “longitude” : 180 } }

默认情况下地理空间的范围是-180~180(经纬度)。要想用其他值,能够通过ensureIndex选项指定最大最小值:

db.star.trek.ensureIndex({“light-years” : “2d”}, {“min” : -1000, “max” : 1000})

这样就创建了一个2000光年见方的索引。

地理空间查询方法

使用$near
返回离[40, -73]近期的10个文档

db.map.find({“gps” : {"$near" : [40, -73]}}).limit(10)
或是:
db.runCommand({geoNear : “map”, near : [40, -73], num : 10});

查找指定开头内的文档
即将原来的 $near 换成 $within

$ within形状參数文档(http://www.mongodb.org/display/DOCS/Geospatial+Indexing)
搜索矩形内的文档:使用"$box"

db.map.find({“gps” : {"$ within" : {"$box" : [[10, 20], [15, 30]]}}})

搜索圆形内的文档:使用"$center"

db.map.find({“gps” : {"$ within" : {"$center" : [[12, 25], 5]}}})

复合地理空间索引

应用常常要找的东西不仅仅是一个地点。比如。用户要找出周围全部的咖啡店或披萨店。将地理空间索引与普通索引组合起来就能够满足这样的需求。
比如。要查询"location"和"desc",就能够这样创建索引:

db.ensureIndex({“location” : “2d”, “desc” : 1})

然后就可能非常快找到近期的咖啡馆了

db.map.find({“location” : {"$near" : [-70, 30]}, “desc” : “coffeeshop”}).limit(1)
{
“_id” : ObjectId(“4c0d1348928a815a720a0000”),
“name” : “Mud”,
“location” : [x, y],
“desc” : [“coffee”, “coffeeshop”, “muffins”, “espresso”]
}

注意:创建一个关键词组对于用户自己定义查找非常有帮助。

文本索引(Text Index)

能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。text索引可以包括其值为字符串或字符串元素数组的任何字段。
文本索引,顾名思义就是用于搜索文本的,可以用于搜索所有的value,也可以搜索指定的field对应的value。只要field对应value是string,或者对应的value是array且array中的元素是string,那么文本索引都可以索引该field

注意:一个集合最多只能有一个文本索引。

要创建text索引,请使用该 db.collection.createIndex()方法。要索引包含字符串或字符串元素数组的字段,请包含该字段并"text"在索引文档中指定字符串文字。

如以下示例所示:

db.collection.createIndex({keys:”text”})

也可以创建多个字段text,例如

db.collection.createIndex({subject:”text”,comments:”text”})

举例,有两条记录

{_id:5908df789dfd1fd5884fd84f7df4,statement:MongoDB is the worst}

{_id:5908dfgfh587hgf15f4hf54hf418,statement:MongoDB is the best}

给statement字段创建文本索引:

db.collection.createIndex({statement:”text”})

查询

db.collection.find({$text:{$search:”MongoDB best”}})

查询出来的结果集是:

{_id:5908df789dfd1fd5884fd84f7df4,statement:MongoDB is the worst}

{_id:5908dfgfh587hgf15f4hf54hf418,statement:MongoDB is the best}

这是因为文本索引在查询时,每个单词之间的分隔是”或者”,所以上面的查询语句的意思是:查询包含MongoDB或者best的记录数。

在多个字段上创建文本索引时,还可以使用通配符说明符($**)。使用通配符文本索引,MongoDB会为包含集合中每个文档的字符串数据的每个字段编制索引。
例如:

db.collection.createIndex({“$**”,”text”})

此索引允许在具有字符串内容的所有字段上进行文本搜索。如果不清楚要包含在文本索引中的哪些字段或用于临时查询

通配符文本索引可以是复合索引的一部分。例如,以下内容在字段nane和通配符说明符上创建复合索引,例如:

db.collection.createIndex({name:1,“$**”,”text”})

排序操作无法从text索引获取排序顺序,即使是复合文本索引也是如此 ; 即排序操作不能使用文本索引中的排序。

索引额外属性

MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性。

唯一索引 (unique index):保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引
TTL索引:可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)
部分索引 (partial index): 只针对符合某个特定条件的文档建立索引,3.2版本才支持该特性
稀疏索引(sparse index): 只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况

用到的时候可以具体研究,这里只做个记录。

执行查询计划

索引已经建立了,但查询还是很慢怎么破?这时就得深入的分析下索引的使用情况了,可通过查看下详细的查询计划来决定如何优化。通过执行计划可以看出如下问题

根据某个/些字段查询,但没有建立索引
根据某个/些字段查询,但建立了多个索引,执行查询时没有使用预期的索引。

建立索引前,db.person.find( {age: 18} )语句执行COLLSCAN,即全表扫描。

mongo-9552:PRIMARY&gt; db.person.find({age: 18}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.person",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "age" : {
                "$eq" : 18
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "age" : {
                    "$eq" : 18
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "localhost",
        "port" : 9552,
        "version" : "3.2.3",
        "gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"
    },
    "ok" : 1
}

建立索引后,通过查询计划可以看出,先进行[IXSCAN]((https://docs.mongodb.org/manual/reference/explain-results/#queryplanner)(从索引中查找),然后FETCH,读取出满足条件的文档。

mongo-9552:PRIMARY&gt; db.person.find({age: 18}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.person",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "age" : {
                "$eq" : 18
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "age" : 1
                },
                "indexName" : "age_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "age" : [
                        "[18.0, 18.0]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "localhost",
        "port" : 9552,
        "version" : "3.2.3",
        "gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"
    },
    "ok" : 1
}

注意事项

既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,数据库在运行时也要消耗资源维护索引,因此索引并不是越多越好。一般两种情况下不建议建索引。

第一种情况是表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。

另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:

Index Selectivity = Cardinality / #T
常见慢查询:
1.不等于和不包含查询
2.通配符在前面的模糊查询, like ‘%xxx’
3.无索引的count 查询 和 排序(复合索引顺序不匹配)
4.多个范围查询(范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引)
5.skip跳过过多的行数(优化方案:我们第一页可以用db.article.find().limit(articles_of_each_page),并记录最后一片文章的_id(或者其他排序值),之后查询db.article.find({_id:{$lt:_id_stored}}).limit(articles_of_each_page)来查找下一页或者类似的,上一页的文章,可以避免大量计数.)

正确建立索引

在没有建立索引的情况下,对Mongodb数据表进行查询操作的时候,需要把数据都加载到内存。当数据的数量达到几十万乃至上百万的时候,这样的加载过程会对系统造成较大的冲击,并影响到其他请求的处理过程。

索引是对数据库表中一列或多列的值进行排序的一种结构,建立索引以后,对索引字段进行查询时,仅会加载索引数据,并能提高查询速度。

1、建立合适的索引

为每一个查询建立合适的索引。

组合索引是创建的索引由多个字段组成,例如:

db.test.ensureIndex({"username":1, "age":-1}) #1是按升序排列,-1是按降序排列 

交叉索引是每个字段单独建立索引,但是在查询的时候组合查找,例如:

db.test.ensureIndex({"username":1}) 
db.test.ensureIndex({"age":-1}) 
db.test.find({"username":"kaka", "age": 30}) 

交叉索引的查询效率较低,在使用时,当查询使用到多个字段的时候,尽量使用组合索引,而不是交叉索引。

2、组合索引的字段排列顺序

当我们的组合索引内容包含匹配条件以及范围条件的时候,比如包含用户名(匹配条件)以及年龄(范围条件),那么匹配条件应该放在范围条件之前。

比如需要查询:

db.test.find({"username":"kaka", "age": {$gt: 10}}) 

那么组合索引应该这样创建:

db.test.ensureIndex({"username":1, "age":-1}) 

3、查询时尽可能仅查询出索引字段

有时候仅需要查询少部分的字段内容,而且这部分内容刚好都建立了索引,那么尽可能只查询出这些索引内容,需要用到的字段显式声明(_id字段需要显式忽略!)。因为这些数据需要把原始数据文档从磁盘读入内存,造成一定的损耗。

比如说我们的表有三个字段:

username, age, mobile 

索引是这样建立的:

db.test.ensureIndex({"username":1,"age":-1}) 

我们仅需要查到某个用户的年龄(age),那可以这样写:

db.test.find({"username":"kaka"}, {"_id":0, "age":1}) 

注意到上面的语句,我们除了”age”:1外,还加了”_id”:0,因为默认情况下,_id都是会被一并查询出来的,当不需要_id的时候记得直接忽略,避免不必要的磁盘操作。

4、对现有的数据大表建立索引的时候,采用后台运行方式

在对数据集合建立索引的过程中,数据库会停止该集合的所有读写操作,因此如果建立索引的数据量大,建立过程慢的情况下,建议采用后台运行的方式,避免影响正常业务流程。

db.test.ensureIndex({"username":1,"age":-1},{"background":true})
 #默认情况下background是false。

索引优化

db profiling

MongoDB支持对DB的请求进行profiling,目前支持3种级别的profiling。

0: 不开启profiling
1: 将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合 
    (类似于mysql、redis的slowlog)
2: 将所有的请求都记录到DB下的system.profile集合(生产环境慎用)

通常,生产环境建议使用1级别的profiling,并根据自身需求配置合理的阈值,用于监测慢请求的情况,并及时的做索引优化。

如果能在集合创建的时候就能『根据业务查询需求决定应该创建哪些索引』,当然是最佳的选择;但由于业务需求多变,要根据实际情况不断的进行优化。索引并不是越多越好,集合的索引太多,会影响写入、更新的性能,每次写入都需要更新所有索引的数据;所以你system.profile里的慢请求可能是索引建立的不够导致,也可能是索引过多导致。

执行计划的概念

我们根据上边的profiling定位到了慢查询的语句,如何解决他呢?在关系型数据库中无论哪种数据库,都提供了SQL剖析工具,用来解决SQL低下的问题。在MongoDB中,也有相应的策略来实现剖析。mongoDB系统了explain()方法,用来查看其执行计划和其统计信息。

执行计划explain三种模式

1、queryPlanner

queryPlanner是explain的默认模式,queryPlanner模式下并不会去真正进行操作语句的执行,而是针对query语句进行执行计划分析并选出winning plan
  1)namespace:
   操作语句所针对的表
  2)indexFilterSet:
   布尔值,该操作是否指定indexFilter
  3)winningPlan:
   查询优化器对该query返回的最优执行计划的详细内容
  4)winningPlan.stage
   操作阶段的策略
  5)winningPlan.inputStage/winningPlan.inputStages
   子操作阶段
  6)winningPlan.inputStage.stage
   子操作阶段的策略
  7)winningPlan.inputStage.keyPattern
   扫描的index内容
  8)winningPlan.inputStage.indexName
   winning plan所选用的index
  9)winningPlan.inputStage.isMultiKey
   是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true
  10)winningPlan.inputStage.direction
   此query的查询顺序,与sort有关,sort:1为forward,sort:-1为backward
  11)winningPlan.inputStage.indexBounds:
   winningplan所扫描的索引范围
  12)rejectePlans
   其他的执行计划(不会被采用,因为不是最优的。最优的是winningPlan,采用的也是winningPlan)。

2、executionStats

mongoDB运行查询优化器对当前的查询进行评估并选择一个最佳的查询计划进行执行。
  1)executionSuccess:
   是否执行成功
  2)nReturned:
   查询的返回条数
  3)executionTimeMillis:
   整体执行时间
  4)totalKeysExamined:
   索引扫描次数
  5)totalDocsExamined:
   document扫描文档数目
  6)executionStages:
   执行阶段的全部详情
  7)executionStages.works

8)executionStages.advanced

9)executionStages.needTime

10)executionStages.needYield
   存储层产生锁的次数
  11)executionStages.isEOF
   指定执行阶段是否结束
  12)executionStages.inputStage.keysExamined
   执行过程中扫描索引数据的次数
  13)executionStages.inputStage.docsExamined
   执行阶段扫描文档数据的次数

3、allPlansExecution

该模式是前两种模式的更细化,会包括上述两种模式的所有信息。
即按照最佳的执行计划列出统计信息,还会列出一些候选的执行计划。

IndexFilter与Stage详解

1、IndexFilter

1)概念
  IndexFilter决定了查询优化器对于某一类型的查询将如何使用index,indexFilter仅影响查询优化器对于该类查询可以尝试用哪些index的执行计划分析,查询优化器还是根据分析情况选择最优计划。

2)IndexFilter的创建
  可以通过如下命令为某一个collection建立indexFilter

db.runCommand(
   {
      planCacheSetFilter: <collection>,
      query: <query>,
      sort: <sort>,
      projection: <projection>,
      indexes: [ <index1>, <index2>, ...]
   }
)

db.runCommand(
   {
      planCacheSetFilter: "orders",
      query: { status: "A" },
      indexes: [
         { cust_id: 1, status: 1 },
         { status: 1, order_date: -1 }
      ]
   }
)

以上针对orders表建立了一个indexFilter,indexFilter指定了对于orders表只有status条件(仅对status进行查询,无sort等)的查询的indexes,故下图的查询语句的查询优化器仅仅会从{cust_id:1,status:1}和{status:1,order_date:-1}中进行winning plan的选择

db.orders.find( { status: "D" } )
db.orders.find( { status: "P" } )

3)IndexFilter的列表
  可以通过如下命令展示某一个collecton的所有indexFilter

db.runCommand( { planCacheListFilters: <collection> } )

4)IndexFilter的删除
  可以通过如下命令对IndexFilter进行删除

db.runCommand(
   {
      planCacheClearFilters: <collection>,
      query: <query pattern>,
      sort: <sort specification>,
      projection: <projection specification>
   }
)

2、Stage

如queryPlanner.winningPlan.stage和queryPlanner.winningPlan.inputStage等。
  COLLSCAN:全表扫描
  IXSCAN:索引扫描
  FETCH:根据索引去检索指定document
  SHARD_MERGE:将各个分片返回数据进行merge
  SORT:表明在内存中进行了排序(与老版本的scanAndOrder:true一致)
  LIMIT:使用limit限制返回数
  SKIP:使用skip进行跳过
  IDHACK:针对_id进行查询
  SHARDING_FILTER:通过mongos对分片数据进行查询
  COUNT:利用db.coll.explain().count()之类进行count运算
  COUNTSCAN:count不使用用Index进行count时的stage返回
  COUNT_SCAN:count使用了Index进行count时的stage返回
  SUBPLA:未使用到索引的$or查询的stage返回
  TEXT:使用全文索引进行查询时候的stage返回
  PROJECTION:限定返回字段时候stage的返回

结果分析

1、executionTimeMillis分析

executionStats模式中,有3个执行时间:
   executionTimeMillis:该query的整体查询时间
   executionStages.executionTimeMillis:该查询根据index去检索document获取数据的时间
   executionStages.inputStage.executionTimeMillis:该查询扫描index行所用时间
  这三个值越小,该查询的性能最好

2、nReturned、totalKeysExamined与totalDocsExamined分析

这三个返回值分别代表该条查询返回的条目、索引扫描条目和根据索引去检索文档的扫描条目。这些都直观的影响到executionTimeMillis值的大小。
 对于一个查询, 我们最理想的状态是:
  1)索引覆盖
   nReturned=totalKeysExamined & totalDocsExamined=0
   即仅仅使用到了index,无需文档扫描,这是最理想状态。
  2)正常使用索引
   nReturned=totalKeysExamined=totalDocsExamined
   即正常index利用,无多余index扫描与文档扫描。
 如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned=totalDocsExamined的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。

3、Stage状态分析

Stage类型会影响到totalKeysExamined和totalDocsExamined,从而影响executionTimeMillis值的大小。
  对于普通查询,我们最希望看到的组合有这些:
   Fetch+IDHACK
   Fetch+ixscan
   Limit+(Fetch+ixscan)
   PROJECTION+ixscan
   SHARDING_FILTER+ixscan
  不希望看到包含如下的stage:
   COLLSCAN(全表扫)
   SORT(使用sort但是无index)
   不合理的SKIP
   SUBPLA(未用到index的$or)
  对于count查询,希望看到的有:
   COUNT_SCAN,
  不希望看到的有:
   COUNTSCAN。
  当查询覆盖精确匹配,范围查询与排序的时候,{精确匹配字段,排序字段,范围查询字段}这样的索引排序会更为高效。

执行计划说明(截图)

当我们查询条件为两个时,如:

db.commits.find({"eId":"5913cd0ee727e70007a109f2",

   "sId":"5913c700434a8b00077256fe"

 }).explain("executionStats")

在这里插入图片描述因前面已经给eId创建了索引,所以本次查询范围从18248条记录,降到了80条记录,而sId没有创建索引,所有我们从80条记录里得到了符合eId,sId这两个条件的38条记录,那么我们现在给sId也创建索引

db.commits.createIndex({sId:1})

当查询的两个条件sId,eId都创建了索引后,再执行

db.commits.find({"eId":"5913cd0ee727e70007a109f2",

  "sId":"5913c700434a8b00077256fe"  

 }).explain("executionStats")

命令

1、普通查询

db.collection.explain()

2、游标查询

cursor.explain()

3、聚合查询

db.collection.aggregate([{pipelines}],{explain:true})

示例

1、查看不同顺序的混合索引的最优解

1)数据

{ "_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 }

2)根据查询条件建立索引

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

3)分别使用不同的索引查看执行计划

db.inventory.find({ quantity: { $gte: 100, $lte: 300 }, type: "food" }).hint({ quantity: 1, type: 1 }).explain("executionStats")
{
   "queryPlanner" : {
      ...
      "winningPlan" : {
         "stage" : "FETCH",
         "inputStage" : {
            "stage" : "IXSCAN",
            "keyPattern" : {
               "quantity" : 1,
               "type" : 1
            },
            ...
            }
         }
      },
      "rejectedPlans" : [ ]
   },
   "executionStats" : {
      "executionSuccess" : true,
      "nReturned" : 2,
      "executionTimeMillis" : 0,
      "totalKeysExamined" : 5,
      "totalDocsExamined" : 2,
      "executionStages" : {
      ...
      }
   },
   ...
}

MongoDB扫描5个索引条目并返回2个符合条件的Document

db.inventory.find({ quantity: { $gte: 100, $lte: 300 }, type: "food" }).hint({ type: 1, quantity: 1 }).explain("executionStats")
{
   "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个索引条目并返回2个符合条件的Document
 比较两次查询的结果,索引2比索引1更有效。

创建索引方法的可选参数

createIndex()接收可选參数。可选參数列表例如以下:

ParameterTypeDescription
backgroundBoolean建索引过程会堵塞其他数据库操作,background可指定以后台方式创建索引,即添加 “background” 可选參数。 “background” 默认值为false。
uniqueBoolean建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
namestring索引的名称。假设未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
dropDupsBoolean在建立唯一索引时是否删除反复记录,指定 true 创建唯一索引。默认值为 false.
sparseBoolean对文档中不存在的字段数据不启用索引。这个參数须要特别注意。假设设置为true的话,在索引字段中不会查询出不包括相应字段的文档.。默认值为 false.
expireAfterSecondsinteger指定一个以秒为单位的数值,完毕 TTL设定,设定集合的生存时间。
vindex version索引的版本号号。默认的索引版本号取决于mongod创建索引时执行的版本号。
weightsdocument索引权重值。数值在 1 到 99,999 之间,表示该索引相对于其它索引字段的得分权重。
default_languagestring对于文本索引。该參数决定了停用词及词干和词器的规则的列表。 默觉得英语
language_overridestring对于文本索引。该參数指定了包括在文档中的字段名。语言覆盖默认的language,默认值为 language.

参数举列:创建唯一索引

我们也可以定义唯一索引,方法就是指定unique键为true,如:

db.users.createIndex({name:1},{“unique”:true})

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值