索引

唯一索引

唯一索引是索引具有的一种属性,让索引具备唯一性,确保这张表中,该条索引数据不会重复出现。在每一次insert和update操作时,都会进行索引的唯一性校验,保证该索引的字段组合在表中唯一。

db.containers.createIndex({name: 1},{unique:true, background: true})
db.packages.createIndex({ appId: 1, version: 1 },{unique:true, background: true})

知识点一:

创建索引时,1表示按升序存储,-1表示按降序存储。

知识点二:

Mongo提供两种建索引的方式foreground和background。
前台操作,它会阻塞用户对数据的读写操作直到index构建完毕;
后台模式,不阻塞数据读写操作,独立的后台线程异步构建索引,此时仍然允许对数据的读写操作。
创建索引时一定要写{background: true}
创建索引时一定要写{background: true}
创建索引时一定要写{background: true}

复合索引

概念:指的是将多个键组合到一起创建索引,终极目的是加速匹配多个键的查询。

db.flights.createIndex({ flight: 1, price: 1 },{background: true})

内嵌索引

可以在嵌套的文档上建立索引,方式与建立正常索引完全一致。

个人信息表结构如下,包含了省市区三级的地址信息,如果想要给城市(city)添加索引,其实就和正常添索引一样

db.personInfos.createIndex({“address.city”:1})

数组索引

MongoDB支持对数组建立索引,这样就可以高效的搜索数组中的特定元素。

知识点四:

但是!对数组建立索引的代价是非常高的,他实际上是会对数组中的每一项都单独建立索引,就相当于假设数组中有十项,那么就会在原基础上,多出十倍的索引大小。如果有一百个一千个呢?
所以在mongo中是禁止对两个数组添加复合索引的,对两个数组添加索引那么索引大小将是爆炸增长,所以谨记在心。
过期索引(TTL)
可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)

哈希索引(Hashed Index)

是指按照某个字段的hash值来建立索引,hash索引只能满足字段完全匹配的查询,不能满足范围查询等

地理位置索引(Geospatial Index)

能很好的解决一些场景,比如『查找附近的美食』、『查找附近的加油站』等

文本索引(Text Index)

能解决快速文本查找的需求,比如,日志平台,相对日志关键词查找,如果通过正则来查找的话效率极低,这时就可以通过文本索引的形式来进行查找

Explain查询计划

提到查的慢,二话不说直接看查询计划好么?具体每一个字段的含义我就不做赘述了很容易查到,我截取winningPlan的部分和大家一起看一下。WinningPlan就是在查询计划中胜出的方案,那肯定就有被淘汰的方案,是在rejectPlan里。

// 查询计划中的winningPlan部分
"winningPlan": {
	"stage": "FETCH",
	"filter": {
		"createdAt": {
			"$gte": ISODate("2019-07-22T12:00:44.000Z")
		}
	},
	"inputStage": {
		"stage": "IXSCAN",
		"keyPattern": {
			"load": 1
		},
		"indexName": "load_1",
		"isMultiKey": false,
		"multiKeyPaths": {
			"load": []
		},
		"isUnique": false,
		"isSparse": false,
		"isPartial": false,
		"indexVersion": 2,
		"direction": "backward",
		"indexBounds": {
			"load": [
				"[MaxKey, MinKey]"
			]
		}
	}
},
  • 知识点六:

explain 结果将查询计划以阶段树的形式呈现。
每个阶段将其结果(文档或索引键)传递给父节点。
中间节点操纵由子节点产生的文档或索引键。
根节点是MongoDB从中派生结果集的最后阶段。
对于新人一定要特别注意:在看查询结果的阶段树的时候一定一定是从最里层一层一层往外看的,不是直接顺着读下来的。

  • 知识点七:

在查询计划中出现了很多stage,下面列举的经常出现的stage以及他的含义:
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据前面扫描到的位置抓取完整文档
SORT:进行内存排序,最终返回结果
SORT_KEY_GENERATOR:获取每一个文档排序所用的键值
LIMIT:使用limit限制返回数
SKIP:使用skip进行跳过
IDHACK:针对_id进行查询
COUNTSCAN:count不使用用Index进行count时的stage返回
COUNT_SCAN:count使用了Index进行count时的stage返回
TEXT:使用全文索引进行查询时候的stage返回

最期望看到的查询组合

Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
最不期望看到的查询组合
COLLSCAN(全表扫)
SORT(使用sort但是无index)
COUNTSCAN(不使用索引进行count)

最左前缀原则

假定索引(a,b,c) 它可能满足的查询如下:

1. a

2. a,b

3. a,b,c

4. a,c [该组合只能用a部分]

5. a, c, b [cb在查询时会被优化换位置]

显然,最左前缀的核心是查询条件字段必须含有索引第一个字段

最左值尽可能用最精确过滤性最好的值,不要用那种可能会用于范围模糊查询,用于排序的字段

效率极低的操作符

$where和$exists:这两个操作符,完全不能使用索引。
$ne和$not:通常来说取反和不等于,可以使用索引,但是效率极低,不是很有效,往往也会退化成扫描全表。
$nin:不包含,这个操作符也总是会全表扫描
对于管道中的索引,也很容易出现意外,只有在管道最开始时的match sort可以使用到索引,一旦发生过project投射,group分组,lookup表关联,unwind打散等操作后,就完全无法使用索引。
索引设计和优化原则
最后祭出李丹老师的索引设计和优化原则

  • 1.主键的设置
    业务无关、显示指定、递增属性

  • 2.数据区分度

原则上区分度高的字段优先做索引字段,如果是组合索引优先放前面

  • 3.字段更新频率

频繁更新的字段是否做索引字段需要综合考虑对业务的影响及查询的代价

  • 4.前缀索引问题

需要注意的是因前缀索引只包含部分值因此无法通过前缀索引优化排序

  • 5.适当冗余设计

对于存储较长字符串字段可额外增加字段存储原字段计算(如hash)后的值

创建索引时只需要对额外字段创建索引即可

  • 6.避免无效索引

通常类似表已经含有主键ID就无需再创建额外唯一性的ID索引

  • 7.查询覆盖率

设计一个索引我们需要考虑尽量覆盖更多的查询场景

  • 8.控制字段数

如果你设计的索引例如含有7、8个字段通常需要考虑设计是否合理

优化原则

  • 1.减少网络带宽

按需返回所需字段、尽量避免返回大字段

  • 2.减少内存计算

减少无必要中间结果存储、减少内存计算

  • 3.减少磁盘IO

添加合适的索引、关注SQL改写