十三:索引
索引一般用来提高查询效率,避免全集合搜索,那么在mongodb中,支持索引么?如果支持,如何定义索引,如何使用索引,如何确定一个sql是否走索引?
1. 创建索引
语法定义:
db.collection.createIndex(keys, options)
请注意,在3.0之前的版本中,也可以使用ensureIndex
来创建索引
参数说明:
- keys:kv结构,key为fieldName, value为1 表示升序创建索引;-1 表示降序创建索引;支持多字段索引
- options:可选参数
常见参数说明如下表:
参数名 | 说明 |
---|---|
background | true,则后台方式创建索引,不阻塞其他操作;默认为false |
unique | true,则表示唯一约束索引,比如_id 就有唯一约束;默认为false |
name | 索引名,不指定时,根据field + 方向生成索引名 |
sparse | true, 则不包含这个字段的不创建索引,且索引查询时查不到不包含这个字段的文档;默认false |
expireAfterSeconds | 设置文档在集合的生存时间,s为单位 |
v | 版本号 |
weight | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重 |
default_language | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language |
实例如下:
db.doc_demo.createIndex({'name': 1}, {'background': true})
2. 索引查询
查看一个集合定义了哪些索引,借助getIndexes()
方法即可,如
db.doc_demo.getIndexes()
3. 索引分析
虽然我们创建了索引,但是我们的查询语句却并不一定会走索引,在mysql中我们知道有一个explain
语句来分析索引情况,在mongodb中也存在类似的方法
集合数据如下
{ "_id" : ObjectId("5e7b5ac10172dc950171c488"), "name" : "一灰灰blog", "age" : 19, "skill" : [ "java", "python", "sql" ], "tag" : 2 }
{ "_id" : ObjectId("5e7b5ac40172dc950171c489"), "name" : "一灰灰blog", "age" : 20, "skill" : [ "web", "shell", "js" ], "tag" : 1 }
{ "_id" : ObjectId("5e7b5bb085a742842d2e23fc"), "name" : "一灰灰", "age" : 18, "sex" : "man", "tag" : 2 }
{ "_id" : ObjectId("5e7b5c2e0172dc950171c48a"), "name" : "一灰灰", "age" : 18, "hobby" : [ "play game" ] }
{ "_id" : ObjectId("5e7c5627f020f58f5323e52d"), "name" : "一灰灰2", "age" : 22, "skill" : [ "android", "ios" ] }
{ "_id" : ObjectId("5e7c5a61f020f58f5323e52e"), "name" : "一灰灰", "doc" : { "title" : "简单的标题", "content" : "简单的内容", "tag" : [ "java", "后端" ] } }
{ "_id" : ObjectId("5e7c5a8af020f58f5323e52f"), "name" : "一灰灰", "doc" : { "title" : "哈哈", "content" : "嘻嘻哈哈", "tag" : [ "随笔" ], "draft" : true } }
{ "_id" : ObjectId("5e7c5ae7f020f58f5323e530"), "name" : "一灰灰", "doc" : { "title" : "22", "content" : "3333", "tag" : [ "随笔" ], "draft" : false, "visit" : 10 } }
当前集合上除了默认的_id
索引之外,针对name
也创建了升序索引
如需要判断一个查询语句的情况,可以在后面加上explain()
方法,如下
db.doc_demo.find({'name': '一灰灰'}).explain()
输出如下
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "basic.doc_demo",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "一灰灰"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"一灰灰\", \"一灰灰\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "0f51c424211c",
"port" : 27017,
"version" : "4.0.4",
"gitVersion" : "f288a3bdf201007f3693c58e140056adf8b04839"
},
"ok" : 1
}
关于是否走索引,主要看stage,通常会有以下几种状态
stage | 描述 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 扫描索引 |
FETCH | 根据索引去检索指定document |
SHARD_MERGE | 将各个分片返回数据进行merge |
SORT | 表明在内存中进行了排序 |
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的返回 |
上面的具体查询,对应的stage组合是Fetch+ixscan
,也就是说会根据索引查询
虽然mongodb会根据查询来选择索引,但并不能保证都能选到最优的索引;这种时候我们可以通过hint
来强制指定索引,举例如下
db.doc_demo.find({'age': 18, 'name':'一灰灰'}).hint({'name': 1}).explain()
4. 删除索引
一般有下面两种删除方式,全量删除和指定索引删除
# 全量删除
db.collection.dropIndexes()
# 指定删除
db.collection.dropIndex(索引名)
请注意,指定索引名删除时,如果不确定索引名是啥,可以通过getIndexes()
来查看
5. 文档自动删除
在创建索引的时候,其中有一个参数比较有意思,有必要单独拿出来说明一下,expireAfterSeconds
设置文档的生存时间
使用它有几个潜规则:
- 索引字段为Date类型
- 单字段索引,不支持混合索引
- 非立即执行
# 插入一条文档,请注意这个时间,因为时区原因相对于北京时间,少8小时
db.doc_demo.insert({'name': 'yihui', 'log': '操作了啥啥啥', 'createDate': new Date('Mar27, 2020 2:54:00')})
# 创建索引
db.doc_demo.createIndex({'createDate': 1}, {expireAfterSeconds: 60})
然后过一段时间(并不一定10:55分的时候会删除)再去查询,会发现插入的文档被删除了
利用这种特性,在mongodb中存一些需要定时删除的数据,相比较我们常用的mysql而言,还是有很大优势的
6. 覆盖索引
覆盖索引的概念有些类似mysql中的不回表查询的case,直接查询索引,就可以返回所需要的字段了
比如在前面的case中,我只查询name字段,可以走覆盖索引;但是返回除了name,还有_id
,那么就不能了
# 覆盖索引
db.doc_demo.find({'name': '一灰灰'}, {'name': 1, '_id': 0})
# 非覆盖索引
db.doc_demo.find({'name': '一灰灰'}, {'name': 1})
注意:所有索引字段是一个数组时,不能使用覆盖索引