十三:索引

一灰灰blogDBMongoMongoDB约 1524 字大约 5 分钟

索引一般用来提高查询效率,避免全集合搜索,那么在mongodb中,支持索引么?如果支持,如何定义索引,如何使用索引,如何确定一个sql是否走索引?

1. 创建索引

语法定义:

db.collection.createIndex(keys, options)

请注意,在3.0之前的版本中,也可以使用ensureIndex来创建索引

参数说明:

  • keys:kv结构,key为fieldName, value为1 表示升序创建索引;-1 表示降序创建索引;支持多字段索引
  • options:可选参数

常见参数说明如下表:

参数名说明
backgroundtrue,则后台方式创建索引,不阻塞其他操作;默认为false
uniquetrue,则表示唯一约束索引,比如_id就有唯一约束;默认为false
name索引名,不指定时,根据field + 方向生成索引名
sparsetrue, 则不包含这个字段的不创建索引,且索引查询时查不到不包含这个字段的文档;默认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运算
COUNTSCANcount不使用Index进行count时的stage返回
COUNT_SCANcount使用了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})

注意:所有索引字段是一个数组时,不能使用覆盖索引

Loading...