一、概述
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
在数据量大时,效率差别就很明显,对于包括一个没有索引的排序操作的查询,服务器必须在返回任何结果之前将所有的文档加载到内存中来进行排序。
索引(Index)是帮助MySQL高效获取数据的数据结构。
可以得到索引的本质:索引是数据结构。
你可以简单理解为“排好序的快速查找数据结构”。
索引存储一个特定字段或一组字段的值,按该字段的值排序。索引条目的排序支持有效的相等匹配和基于范围的查询操作。
另外,MongoDB可以通过使用索引中的顺序来返回排序的结果。
二、MongoDB索引
2.1、索引创建
MongoDB使用 ensureIndex() 方法来创建索引:
2.1.1、语法:
ensureIndex()方法基本语法格式如下所示:
>db.COLLECTION_NAME.ensureIndex({KEY:1})
# 1为升序,-1为降序
2.1.2、ensureIndex() 可选参数
可选参数列表如下:
Parameter | Parameter | Parameter |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | Boolean | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | documen | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | Boolean | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | Boolean | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
在后台创建索引:
db.values.ensureIndex({open: 1, close: 1}, {background: true})
通过在创建索引时加background:true 的选项,让创建工作在后台执行。
2.1.3、索引的默认名称
索引的默认名称是使用下划线作为分隔符的索引键和索引中每个键的方向(即1或-1)的串联。 例如,在{ item : 1, quantity: -1 } 上创建的索引的名称为item_1_quantity_-1。
您可以使用自定义名称创建索引,例如比默认名称更易于阅读的名称。 例如,考虑一个经常查询产品集合以填充现有库存数据的应用程序。 以下createIndex()方法为名为库存查询的项目和数量创建索引:
db.products.createIndex(
{ item: 1, quantity: -1 } ,
{ name: "query for inventory" }
)
2.2、索引分类
2.2.1、单值索引:
#索引按title升序
>db.col.ensureIndex({"score":1})
2.2.2、复合索引:
#索引按userid升序,score降序
>db.col.ensureIndex({ userid: 1, score: -1 })
对于复合索引和排序操作,索引键的排序顺序(即升序或降序)可以确定索引是否可以支持排序操作。
2.2.3、全文本索引
MongoDB提供了一种文本索引类型,该类型支持在集合中搜索字符串内容。
注意:
在一个操作频繁的集合上创建全文本索引可能会导致MongoDB过载,所以应该在离线状态下创建全文本索引,或者是在对性能没要求时。
另外全文本索引也会导致比“普通”索引更严重的性能问题,因为所有字符串都需要被分解、分词,并且保留到一些地方。
在一个集合上最多只能有一个全文本索引,但一个全文本索引可以包含很多字段。
2.2.4、TTL索引
time to live index,具有声明周期的索引,这种索引允许为每个文档设置一个超时时间。当一个文档到达预设置的老化程序就会被删除。
>db.log_events.createIndex(
{ "createTime": 1 }, ---字段名称
{ expireAfterSeconds: 60*60 } ---过期时间(单位秒)
)
>db.log_events.getIndexes() ---查看索引
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "tt.t1"
},
{
"v" : 1,
"key" : {
"createTime" : 1
},
"name" : "createTime_1",
"ns" : "tt.t1",
"expireAfterSeconds" : 3600
}
]
2.2.5、稀疏索引
> db.scores.insertMany([
{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" },
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 },
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }])
//下面为score键创建稀疏索引
> db.scores.createIndex( { score: 1 } , { sparse: true } )
> db.scores.find( { score: { $lt: 90 } } )
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
//由于文档newbie并不包含score键,因此该文档不会出现在稀疏索引之中,也就不会被查询返回
2.2.6、多键索引(了解即可)
对于某个索引的键,如果这个键在某个文档中是一个数组,那么这个索引就会标示为多键索引(multikey index)。
#假定存在下列集合
{ _id: 1, item: "ABC", ratings: [ 2, 5, 9 ] }
#在ratings上创建索引
db.survey.createIndex( { ratings: 1 } )
#这个多键索引则包括2,5,9三个索引键,每一个分别指向相同的文档
> db.version()
3.2.10
>db.inventory.insertMany([
{ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] },
{ _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] },
{ _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] },
{ _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] },
{ _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }])
//下面基于ratings列创建一个多键索引
> db.inventory.createIndex( { ratings: 1 } )
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
//查询数组上为5,9的文档
> db.inventory.find( { ratings: [ 5, 9 ] } )
{ "_id" : 6, "type" : "food", "item" : "bbb", "ratings" : [ 5, 9 ] }
//下面查看其执行计划
> db.inventory.find( { ratings: [ 5, 9 ] } ).explain()
{
"queryPlanner" : {
........
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"ratings" : 1
},
"indexName" : "ratings_1",
"isMultiKey" : true, //这里表明查询使用到了多键索引
.......
"direction" : "forward",
"indexBounds" : {
"ratings" : [
"[5.0, 5.0]",
"[[ 5.0, 9.0 ], [ 5.0, 9.0 ]]"
..........
"ok" : 1
}
//在上面的示例中,使用了多键索引进行扫描,MongoDB寻找在ratings数组任意位置包含5的文档
//然后MongoDB检索这些文档,并过滤出那些等于[ 5, 9 ]的文档
2.2.7、散列索引
为了支持基于散列的分片,MongoDB提供了散列索引类型,该类型索引字段值的散列值。 这些索引在其范围内具有更随机的值分布,但仅支持相等匹配,并且不支持基于范围的查询。
db.collection.createIndex( { _id: "hashed" } )
2.2.8、地理空间索引(不做介绍)
2.3、覆盖查询
当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而无需扫描任何文档或将文档带入内存。 这些覆盖的查询可能非常有效。
三、索引限制
3.1、最大范围
- 集合中索引不能超过64个
- 索引名的长度不能超过125个字符
- 一个复合索引最多可以有31个字段
3.2、查询限制
索引不能被以下的查询使用:
- 正则表达式及非操作符,如 $nin, $not, 等。
- 算术运算符,如 $mod, 等。
- $where 子句
所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。
3.3、额外开销
使用索引是有代价的,对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为,当数据发生变动时,MongoDB不仅要更新文档,还要更新集合上所有的索引。所以,如果你很少对集合进行读取操作,建议不使用索引。