高性能MySQL索引(Innodb)

2018-11-20  本文已影响0人  OldRumble

前置问题

Innodb索引结构

索引我们都很熟悉,可以通过把要索引的key建立一个平衡二叉树,进行二分查找,使时间复杂度来到O(log2n),定位到key再通过内存指针找到自己的data,整个过程在内存中很快,但是对于数据库来说,这样的数据结构却不行,因为数据库是建立在硬盘上的。

图片来源网络侵删.png
我们先看一下硬盘读取数据的工作方式,磁盘可以转动,磁头是固定的不能转,但是可以伸缩,磁盘的同心圆称为磁道,而这个磁头伸缩,就是在寻找磁道,圆心和两个半径组成了一个扇区,操作系统发出电信号到磁盘,可能是一个逻辑地址,磁盘的电路会解析这个信息,变成磁道和扇区的物理地址,然后就开始磁盘转动和磁头伸缩。从内存电路到物理磁盘,性能下降是可想而知的,所以就有磁盘的预读特性,这也是依据计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。和共享内存的数据读取做法相似,它会往后再读一页或者几页(一页一般4k)。
图片来源网络侵删.png
那我们重新看二叉树的数据结构就会明白,每一个节点往下寻址一次,就等于一次物理转动,那这时候就需要不影响索引效率的情况下,尽可能小的减少磁盘转动。这样的数据结构就是B+Tree如下图,你会发现数据都尽可能的平铺在叶子节点,以减少磁盘io,前面有提到磁盘预读的设计,使用B+Tree结构就可以一次物理消耗读取一个叶子页的数据。一般情况下索引远远小于实际的数据,查询速度很慢,但是如果这个表超级大大到连索引也很大,这时候查询依然会很慢,这时候需要做的就是分库分表、数据归档等操作了。
图片来自高性能MySQL.png
这里不得不提到聚簇索引和非聚簇索引的区别,因为他们在物理结果上有一些不同,首先,我们先看一下聚簇索引。

聚簇索引

聚簇索引就是咱们经常说的主键索引、pk。如果没有主键呢,Innodb会选择第一个没空的唯一索引作为聚簇索引,如果这个唯一索引也没有,这个也是Innodb有而MyIsam所没有的设计。聚簇索引也是索引,也是前面的B+Tree的结构,但是聚簇索引和非聚簇索引不同的地方在于,非聚簇索引叶子节点保存的数据是聚簇索引id的地址,而聚簇索引叶子节点保存的是实际行的值,也就是说,实际行的值是按照聚簇索引排列的方式进行存储的。而myisam的结构则是数据和索引分开的,结构可以参照下图。


图片来自高性能MySQL.png

一条查询语句是怎么工作的?

一条查询语句会经过分析器进行词法分析、语法分析,经过优化器生成执行计划、索引选择,最后会操作引擎,返回结果,所以前面的问题,where条件的顺序会影响索引的使用吗?答案是不会,因为优化器已经帮你优化了。

image.png
如果查询条件有聚簇索引,优先选择聚簇索引,如果查询条件是非聚簇索引,会先查非聚簇索引,找到主键id,再去查找聚簇索引,找到自己想要的值,这个动作成为“回表”。

索引覆盖

上一节提到了“回表”,如果有回表动作,那么一行sql就要走两遍索引,只查询索引就可以把数据取出来的做法就叫做“索引覆盖”。这也是为什么大厂都禁止select *的写法,因为select *一定会回表。

前缀索引和索引选择性

有时候需要索引很长的字符串,这会让索引变得又大又慢,一种策略是使用哈希索引,把很长的字符串弄成一个hash值,但是这样做还不够,通常可以只索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率,但是这样会降低索引的选择性,选择性越高,代表使用索引后筛选到的值越少,所以怎么在选择性和索引长度之间做权衡。

可以比较:
count(distinct filed) / count(field) 与count(distinct left(field, n)) / count(field) 的比率
以uuid为例子,uuid有32位,在单表700w的场景下:
count(distinct uid) / count(uid)  = 1 VS count(distinct left(uid, 10)) / count(uid)  = 1
这时候就可以alter table xx add index idx_uid (uid(10)) 

前缀索引使索引更小、更快,但是另一方面前缀索引也有其缺点:MySQL无法使用前缀索引做order by和group by,也无法使用前缀索引做索引覆盖。

判断一个索引是否适合某一条查询?

索引可以既满足查找又满足排序

前提是:索引的顺序与order by的顺序一致,并且所有列的排序方向一致(不能是一个升序一个倒序),这样就可以使用索引进行排序了,而不是按索引顺序去数据库里把数据拉到服务器里(随机io了),再在一个临时文件里进行排序,这时候explain大多会出现filesort,这时候的效率是非常慢的。例子如下:

idx_a_b_c select a,b,c from table order by a,b,c  全部利用到索引 Using index
idx_a_b_c select * from table order by a,b,c 无法利用到索引 Using filesort
idx_a_b_c select a,b,c,d from table order by a,b,c 无法利用到索引 Using filesort
idx_a_b_c select a,b,c from table order by a,b desc,c  利用到一部分索引 Using index; Using filesort
idx_a_b_c select a, b, c from table where a=100 order by b,c  可以利用到索引排序 Using where; Using index
idx_a_b_c select a, b, c from table where a>100 order by b,c  利用到一部分索引 Using where; Using index; Using filesort

这里要说下 a in (1,2,3) 和 a >= 1 and a <= 3的区别;在联合索引的场景下如idx_a_b,如果是a in (1,2,3),不影响后面的b使用索引,如果是a >= 1 and a <= 3那么后面的b无法使用到索引。

索引合并

当在一张表建立多个字段的索引时候,一行SQL一般是只能使用到一个索引,直到MySQL5.0以后,有了索引合并这么的机制,一定程度上可以利用多条索引。

关于NULL

未来期望MySQL可以做到的

上一篇 下一篇

猜你喜欢

热点阅读