在一个键上创建的索引就是单键索引,单键索引是最常见的索引,如MongoDB默认创建的_id的索引就是单键索引。
在多个键上建立的索引就是复合索引
如果在一个值为数组的字段上面创建索引, MongoDB会自己决定,是否要把这个索引建成多键索引
MongoDB支持几种类型的地理空间索引。其中最常用的是 2dsphere 索引(用于地球表面类型的地图)和 2d 索引(用于平面地图和时间连续的数据)
全文索引用于在文档中搜索文本,我们也可以使用正则表达式来查询字符串,但是当文本块比较大的时候,正则表达式搜索会非常慢,而且无法处理语言理解的问题(如 entry 和 entries 应该算是匹配的)。使用全文索引可以非常快地进行文本搜索,就如同内置了多种语言分词机制的支持一样。创建索引的开销都比较大,全文索引的开销更大。创建索引时,需后台或离线创建
哈希 索引可以支持相等查询,但是 哈希 索引不支持范围查询。您可能无法创建一个带有 哈希 索引键的复合索引或者对 哈希 索引施加唯一性的限制。但是,您可以在同一个键上同时创建一个 哈希 索引和一个递增/递减(例如,非哈希)的索引,这样MongoDB对于范围查询就会自动使用非哈希的索引
- 唯一索引可以拒绝保存那些被索引键的值已经重复的文档。
- 默认情况下,MongoDB索引的 unique 属性是 false 。如果对复合索引施加唯一性的限制,那么MongoDB就会强制要求 复合 值的唯一性,而不是分别对每个单独的值要求唯一。
- 唯一性的限制是针对一个集合中不同文档的。也即,唯一索引可以防止 不同 文档的被索引键上存储相同值,但是它不禁止同一篇文档在被索引键存储的数组里存储的元素或者内嵌文档是相同的值。
- 在同一篇文档存储重复数据的情况下,重复的值只会被存入索引一次。
- 如果一篇文档不包含唯一索引的被索引键,那么索引默认会为该文档存储一个null值。由于唯一性的限制,MongoDB将只允许有一篇可以不包含被索引键。如果超过一篇文档不包含被索引键或没有值,那么会抛出键重复(duplicate key)错误导致索引创建失败。可以组合使用唯一性和稀疏索引的特性来过滤那些包含null值的文档以避免这个错误。
- 稀疏索引会跳过所有不包含被索引键的文档。这个索引之所以称为 “稀疏” 是因为它并不包括集合中的所有文档。与之相反,非稀疏的索引会索引每一篇文档,如果一篇文档不含被索引键则为它存储一个null值。
- 如果一个索引会导致查询或者排序的结果集是不完整的,那么MongoDB将不会使用这个索引,除非用户使用 hint() 方法来显示指定索引。例如,查询 { x: { $exists: false } } 将不会使用 x 键上的稀疏索引,除非显示的hint。
- 2dsphere (version 2), 2d 和 text 这些索引总是稀疏的。
- 只要一个文档里有至少一个被索引键,稀疏且只包含有递增/递减索引键的复合索引就会索引这篇文档。
- 至于稀疏且包含有地理索引键(例如 2dsphere, 2d)以及递增/递减索引键的复合索引,只有地理索引键的存在与否能决定一篇文档是否被索引。
- 至于稀疏且包含了全文索引键和其他递增/递减索引键的复合索引,只有全文索引键的存在与否能决定是否索引该文档。
- 一个稀疏且唯一的索引,可以防止集合中的文档被索引键中出现重复值,同时也允许多个文档里不包含被索引键
备注:3.2.0 版本后推荐使用Partial Indexes
- 这是3.2.0版本以后新增的
- 只会对Collections满足条件partialFilterExpression的文档进行索引
- 使用该索引,会在一定程度上减少存储空间和创建索引和维护性能降低的成本
- TTL 集合支持失效时间设置,当超过指定时间后,集合自动清除超时的文档,这用来保存一些诸如session会话信息的时候非常有用,或者存储缓存数据使用。但删除会有延时
- 索引的字段必须是一个日期的 bson 类型
- 你不能创建 TTL 索引,如果要索引的字段已经在其他索引中使用。否则超时后文档不会被自动清除。
- 索引不能包含多个字段
参数名 | 类型 | 描述 |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。不能超过128字符 |
partialFilterExpression | Document | 设定索引只对满足条件的文档起作用 具体见官网对Partial Indexes |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
storageEngine | Document | 允许用户在创建索引时指定每一个索引的配置 |
参数名 | 类型 | 描述 |
---|---|---|
weights | document | 设置文本索引字段的权重,权重值1- 99,999 |
default_language | 设置文本分词的语言,默认为english,其他支持语言 | |
language_override | string | 使用文档中的一个字段的值作为设置文本分词的语言,默认为language,例子 |
textIndexVersion | integer | 版本号,可以是1或2 |
备注:其他索引属性见官网
语法:db.collection.getIndexes()
示例:
db.index_test.getIndexes()
输出:
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "usercenter_test.index_test"
}
]
可以看出_id字段是默认建立了索引的
从3.0版本后使用 db.collection.createIndex()代替db.collection.ensureIndex()
语法:db.collection.createIndex(keys, options)
参数说明:
1. keys: {字段名1:ascending,… 字段名n:ascending}: ascending 设为1 标识索引升序,-1降序
2. options : 设置索引选项,如设置名称、设置成为唯一索引
准备数据
db.index_test.insert({"name":"2","age":53,"sex":1})
db.index_test.insert({"name":"3","age":19,"sex":0})
db.index_test.insert({"name":"4","age":20,"sex":2})
db.index_test.insert({"name":"5","age":63,"sex":5})
db.index_test.insert({"name":"6","age":18,"sex":0})
db.index_test.insert({"name":"7","age":98,"sex":1})
db.index_test.insert({"name":"8","age":76,"sex":1})
db.index_test.insert({"name":"9","age":7,"sex":2})
db.index_test.insert({"name":"l0","age":15,"sex":1})
db.index_test.insert({"name":"l","age":23,"sex":1})
语法: db.collections.createIndex({“字段名”:1或-1},{options})
示例:
db.index_test.createIndex({"age":1})
执行成功后可以看到返回的numIndexesAfter比numIndexesBefore大
示例1:查询字段不包含索引字段
db.index_test.find({"sex":1}).explain("executionStats")
可以看到其 winningPlan.stage=COLLSCAN是全表扫描
示例2:查询字段同时包含索引字段和非索引字段
db.index_test.find({"age":{"$gte":10},"sex":1}).explain("executionStats")
虽然 winningPlan.stage=FETCH以及winningPlan.inputStage.stage =IXSCAN,但是其totalKeysExamined和totalDocsExamined都比nReturned大,说明在查询的时候进行了一些没有必要的扫描。
示例3:查询字段同时只包含索引字段
db.index_test.find({"age":{"$gte":10}}).explain("executionStats")
可以看到返回中的
winningPlan.stage=FETCH(根据索引去检索指定document )
winningPlan.inputStage.stage =IXSCAN(索引扫描)
executionStats.nReturned=totalKeysExamined=totalDocsExamined=9表示该查询使用的根据索引去查询指定文档
(nReturned:查询返回的条目,totalKeysExamined:索引扫描条目,totalDocsExamined:文档扫描条目)
总结:在设置查询字段时应尽量只设置建立了索引的字段
示例1:排序字段不包含索引字段
db.index_test.find({"age":20}).sort({"sex":-1}).explain()
返回中的winningPlan.stage=SORT 即查询后需要在内存中排序再返回
示例2:排序字段同时包含索引字段和非索引字段
db.index_test.find({"age":20}).sort({"age":1,"sex":1}).explain()
结果与上面一样
示例3:排序字段只包含一个单个索引字段
db.index_test.find({"age":20}).sort({"age":1}).explain()
可以看到winningPlan.stage变为了FETCH(使用索引)
示例4:排序字段包含多个单个索引字段
db.index_test.find({}).sort({"sex":1,"age":1}).explain("executionStats")
db.index_test.find({}).sort({"age":1,"sex":1).explain("executionStats")
可以看到这种情况winningPlan.stage为sort即索引在排序中没有起到作用,这说明单键索引在多个字段排序时没有作用。
总结:
- 排序操作可以通过从索引中按照索引顺序获取文档的方式来保证结果的有序性。如果查询计划器(planner)无法从索引中得到排序顺序,那么它将需要在内存中排序(winningPlan.stage=SORT)结果。
- 在多个字段上做排序时需要使用复合索引
语法: db.collections.createIndex({“字段名1”:1或-1,…,”字段名n”:1或-1},{options})
示例:
db.index_test.dropIndexes() //先删除原来创建的索引
db.index_test.createIndex({"age":1,"sex":1}) //在age和sex上创建复合索引
创建成功后,通过db.index_test.getIndexes() 可看到创建的复合索引其实只是一个索引,其key是{“age” : 1,”sex” : 1};而不是多个。
示例1:查询字段只包含创建复合索引字段中的部分字段
db.index_test.dropIndexes() //先删除原来创建的索引
db.index_test.createIndex({"age":1,"sex":1}) //在age和sex上创建复合索引
然后分别执行
db.index_test.find({"age":1}).explain()
db.index_test.find({"sex":1}).explain()
可以看到第一句执行返回的winningPlan.stage=FETCH;且winningPlan.inputStage.stage=IXSCAN;indexName=age_-1_sex_1
而第二句执行返回的wininigPlan.stage=COLLSCAN
这好像说明查询条件只是复合索引key中部分字段时,索引只对创建时指定的第一个字段有作用。接下来我们先删除原有的索引,然后在age,name,sex三个字段上创建一个复合索引再来看看。
db.index_test.dropIndexes()
db.index_test.createIndex({"age":-1,"name":1,"sex":1})
db.index_test.find({"age":"1"}).explain() //第1条查询
db.index_test.find({"sex":"1"}).explain() //第2条查询
db.index_test.find({"age":"1","sex":1}).explain()//第3条查询
db.index_test.find({"name":"1","sex":1}).explain()//第4条查询
可以看到第1条以及第3条查询返回的都是winningPlan.stage=FETCH;而第2条和第4条查询返回的都是winningPlan.stage=COLLSCAN
总结:
查询条件只是复合索引key中部分字段时,如果想要复合索引起到优化的作用则必须包含创建复合索引时指定的第1个字段;如上面的示列中的字段”age”
示例1:排序字段只包含创建复合索引字段中的部分字段
db.index_test.find().sort({"age":-1}).explain()
db.index_test.find().sort({"age":1}).explain()
db.index_test.find().sort({"name":1}).explain()
db.index_test.find().sort({"sex":1}).explain()
db.index_test.find().sort({"age":-1,"name":1}).explain()
db.index_test.find().sort({"name":1,"sex":1}).explain()
db.index_test.find().sort({"age":-1,"sex":1}).explain()
上面的只有第1,2,5 返回的winningPlan.stage= FETCH ;其他都是=sort
这说明排序字段只包含创建复合索引字段中的部分字段时排序键的顺序必须和它们在索引中的排列顺序 一致,且不能跳跃(即第一个字段必须有,且不能跳过中间的字段)接下来再看下面的查询情况
db.index_test.find().sort({"age":1,"name":-1}).explain()
db.index_test.find().sort({"age":-1,"name":-1}).explain()
第1条返回的winningPlan.stage= FETCH;而第2条winningPlan.stage= SORT;这说明sort中指定的所有键的排序顺序(例如递增/递减)必须和索引中的对应键的排序顺序 完全相同, 或者 完全相反
总结
可以指定在索引的所有键或者部分键上排序。但是,排序键的顺序必须和它们在索引中的排列顺序 一致
sort中指定的所有键的排序顺序(例如递增/递减)必须和索引中的对应键的排序顺序 完全相同, 或者 完全相反
语法:db.collections.createIndex({“字段名”: 1或-1},{“unique”:true})
示例1:
db.index_test.createIndex({"name":1},{"unique":true})
db.index_test.createIndex({"sex":1},{"unique":true})
可以看到第1条执行成功,而第2条则失败因为已存在的数据在sex字段上有重复的数据。
示例2:
db.index_test.insert({"name":"2","age":123,"sex":9})
db.index_test.insert({"name":"22","age":123,"sex":9})
可以看到第1条执行失败,第2条成功。因为唯一索引会阻止写入在唯一索引字段上重复的数据
总结1:
唯一索引会阻止应用插入被索引键上的值是重复值的 documents
不能再已经有重复数据的字段上建立唯一索引
语法:db.collections.createIndex({“字段名”: 1或-1,…”字段名n”: 1或-1},{“unique”:true})
db.index_test.createIndex({"name":1,"age":1},{"unique":true}
db.index_test.insert({"name":"22","age":123,"sex":1})
db.index_test.insert({"name":"23","age":123,"sex":1})
db.index_test.insert({"name":"22","age":123,"sex":2})
可以看到第2条和第3条都能成功,但最后1条却是失败的。这说明在多字段上建唯一索引会强制要求 复合 键值的唯一性,而 不是 每个键的唯一性。
总结:
- 唯一索引会阻止应用插入被索引键上的值是重复值的 documents
- 不能再已经有重复数据的字段上建立唯一索引
- 在多字段上建唯一索引会强制要求 复合 键值的唯一性,而 不是 每个键的唯一性
语法: db.collection.createIndex({“字段名”:1},{“sparse”:true})
在一个字段上创建稀疏索引,索引会跳过所有不包含被索引键(字段)的文档
在执行查询 { 被索引键:{$exists:false}},不会使用该稀疏索引,除非显示指定hint({被索引键:1})
示例:
db.scores.insert({"userid":"lxh"})
db.scores.insert({"userid":"pen","score":89})
db.scores.insert({"userid":"xiao","score":90})
//在字段score上创建稀疏索引
db.scores.createIndex( { score: 1 } , {sparse:true} )
db.scores.find( { score:{$exists:false}}).explain()
db.scores.find( { score:{$exists:false}}).hint({score:1}).explain()
db.scores.find( { score:{$exists:true}}).explain()
从例子可以看出第5条在执行$exists:false 查询时,其返回的winningPlan.stage=COLLSCAN(表示扫描全表);
第5条执行exists:false 查询时,显示指定了hint,其返回的winningPlan.stage=FETCH(使用索引扫描);
最后一条执行 exists:true 查询,没有显示指定hint,其返回的winningPlan.stage=FETCH(使用索引扫描);
总结:
在某一个被创建了稀疏索引字段上执行exists:false查询时,需要显示指定hint,其索引才会起作用;而执行 exists:true查询时,则不需要。
在字段上创建普通索引 ,如果文档不含该字段这其索引值会被设为null,而稀疏索引会跳过该文档;这就是说使用该索引扫描集合时稀疏索引会比普通索引少。
语法: db.collection.createIndex({“字段名”: 1或-1,…”字段名n”: 1或-1},{“partialFilterExpression”:{partialFilterExpression }})
partialFilterExpression表达式如下
equality expressions (i.e. field: value or using the $eq operator)
$exists: true expression,
$gt, $gte, $lt, $lte expressions,
$type expressions,
$and operator at the top-level only
示例:
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)
(备注:地理空间索引,全文索引、TTL索引、哈希索引本人工作中很少使用到,这里就没有描述了)
db.collections.dropIndexes()
db.collections.dropIndex({"被创建索引的字段名":1})
如果希望修改一条索引,您需要删除然后重建它
如果您需要重建集合中的索引,您可以使用 db.collection.reIndex() ,一条操作就可以重建这个集合上的所有索引。它会删除所有索引,包括 _id 索引 ,然后重建所有索引。
(备注:其他索引在本人工作中很少使用过,这里就不描述了)
索引策略
索引创建教程