高性能MySQL索引(Innodb)

前置问题

  • where条件的顺序会影响索引的使用?
  • 联合索引(idx_a_b ) 条件:“where a > 1 and b < 2”和“where a in (1,3) and b < 2” 有什么区别?
  • 使用uid作为主键有什么问题?
  • 一个表有两个索引,idx_a, idx_b,查询语句"where a > 1 and b < 2",会使用到几个索引
  • 查询条件肯定是越多,查询效率越快?
  • select * 和select field 差别其实没有多大,主要体现在网络Io上?
  • 一行SQL除了where条件,order by操作也是可以利用索引的?
  • 关于limit offset,SQL效率跟limit大小有关系,跟offset也有很大关系?
  • 如何优化uid字段索引?
  • select 1 > null 结果是什么?
  • Django ORM的弊端?

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以后,有了索引合并这么的机制,一定程度上可以利用多条索引。

  • Using union 索引取并集
  • Using sort_union 索引排序取并集
  • Using intersect 索引取交集
    索引合并策略是一种优化结果,但实际上更多时候说明了表上的索引建的很糟糕,才会不得不使用索引合并进行优化。
    当在explain的执行计划里面出现上述索引合并时候,我们要知道以下问题:
  • 可以通过建立联合索引的方式解决Using intersect
  • 服务器对多个索引索引做联合操作时候,通常会耗费大量的CPU和内存资源在缓存、排序合并操作上,尤其是在索引的利用率不高,返回大量数据的情况下
  • 优化器不会把这些服务器的排序合并计算到“查询成本”中,优化器只关心随即页面读取。所以这些成本是被“低估”的成本
  • 一行SQL上利用多个索引做索引合并update时候,会提高死锁的概率。

关于NULL

  • NULL在MySQL中的意思是“未知”而不是False或者""
  • SELECT NULL, 1+NULL, CONCAT('Invisible',NULL);; 结果都是NULL
  • 当使用DISTINCT, GROUP BY, or ORDER BY, 所有的 NULL字段会被当成是相等的
  • 针对UNIQUE index,两个NULL值会被看到是相等的
  • 带有NULL值的唯一索引,无法被当做
  • order by的时候NULL会放在第一个(asc)或者最后一个(desc)
  • 当使用Aggregate (summary) 如COUNT() MIN() SUM()会忽略NULL
  • NULL会有些特殊的处理,如timestamp、AUTO-INCREMENT字段
  • 针对NULL字段建立索引,需要使用额外的空间

未来期望MySQL可以做到的

  • where条件可以传到存储引擎层index condition pushdown
  • 松散索引扫描

你可能感兴趣的:(高性能MySQL索引(Innodb))