MySQL调优之索引

文档

MySQL官网文档:

https://dev.mysql.com/doc/refman/8.0/en/mysql-indexes.html

特截取其中一段说明:
在这里插入图片描述
文档中有写明,MySQL绝大部分的索引都是存储在B-tree中(此处应该是B+tree),MEMORY类的存储引擎索引使用的是hash。下面我们来认识下tree。

Tree

这边需要简单介绍下二叉树BST,平衡树AVL,红黑树R-B Tree,B-tree,B±tree。
推荐一个可以在线画各种tree的网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

现在假设我需要依次插入1,2,3…10这10条数据:

二叉树BST

在这里插入图片描述
因每一次插入的数据都是比前面的大,都成为了前一条数据的右子树了,而且深度非常深,如果数据库采用BST tree作为索引,极端情况下,会需要进行全表扫描,一层一层的往里进行查找,显然是不合适的。

平衡树AVL

为了解决二叉树深度的问题,引入了平衡树,要求左右两个子树的高度差的绝对值最大为1:
在这里插入图片描述
最终tree的深度只有4,远小于二叉树的深度10。那MySQL为什么不用平衡树作为索引的存储结构呢?下面我们来分析下AVL数的插入过程。
当插入数据1,2时,tree结构如下:
在这里插入图片描述
此时根节点001的左子树深度为0,右子树深度为1,满足AVL树的要求,同理节点002也是满足的,下面插入003节点,如果我们继续根据树的定义,将003节点作为002节点的右子树,那么此时001节点的左子树深度还是0,但是右子树深度就变成了2,此处已经不满足平衡二叉树的要求了,需要对002节点进行左旋,最终结果如下:
在这里插入图片描述
一样的道理,当插入节点004还是需要进行选择,如果后续数据库中有几十上百条数据,插入或者删除一条数据之后,可能就需要对整个AVL做非常多的左旋和右旋。AVL tree虽然查询效率会高,但是增、删的效率会很低,也不适合用来做索引的存储结构。

红黑树 R-B Tree

在这里插入图片描述
红黑树本身也是一个平衡二叉树,对比AVL tree,红黑树明显扩展了右子树的深度,目的是为了减少平衡树中太多频繁的节点左旋和右旋,但是左旋和右旋只是减少了一部分,还是不适合做索引的存储结构。

B-Tree

B-Tree不再是二叉树,是一种多路搜索树。需要自定义非叶子节点最多有M个子节点,且M>2,
为了形象点展示,就不已1-10的数字为例了。
在这里插入图片描述
以3阶B-Tree为例,每一个节点,最多有3个子节点。同时,每一个节点,有三个指针,分别是p1,p2,p3,还有两个key。以根节点为例,两个key分别是18,36。p1,p2,p3三个指针分别指向三个子节点。
假设现在需要查找key=12的数据:

  1. 12<18,命中磁盘块1中指针p1,从而找到磁盘块2
  2. 12>10 && 12<15,命中磁盘块2中的指针p2,从而找到磁盘块6
  3. 磁盘块6中找到key=12的data

也就是说在3阶B-tree的情况下,3次查询就可以找到我们需要的数据,其性能比上面几种树结构要高很多。

相信大家都知道,一般数据库索引,使用的都是B+tree,而不是B-Tree,那B-Tree有上面问题呢?
以MySQL的InnoDB存储引擎为例。OS读取磁盘数据,是以一个磁盘块(block)为单位,一次性读取出来。InnoDB使用分页(page)的概念来管理磁盘,默认一页是16K(可以通过配置调整为4,8,16等)。假设上图中一个data数据大小1k,p1,p2,p3指针不占空间,那么从根节点开始往下的三层(到叶子节点了),最多可以容纳数据161616=4096条数据,在高并发或者DB数据量很大的情况下,需要读取很多磁盘块才能找到数据。

显然问题的关键点就是在这个data上面,data占据了很大的空间,由此引出了B+Tree

B+Tree

简单理解,就是在B-Tree的基础上,所有非叶子节点不在存储data:
在这里插入图片描述
现在非叶子节点,里面只有p1,p2,p3指针和两个key,数据量非常小,假设占10字节,那非叶子节点,每一页可以读取容量为161000/10=1600条,三层结构下来可以查找16001600*16=40960000条数据

结构不同点

还记得在上一章讲过InnoDB和MyISAM两种存储引擎吗?InnoDB是聚簇索引,只有一个.ibd文件,即数据与索引是放在一起的,而MyISAM是非聚簇索引,数据与索引时两个文件进行存储。当我们使用索引进行检索时,虽然InnoDB和MyISAM都是使用B+Tree进行索引,但是数据结构和过程是不一样的。

InnoDB:
在这里插入图片描述
因为索引与数据是存放在一起的,其叶子节点的数据,即为数据库表中记录的真实数据

MyISAM:
在这里插入图片描述
因为索引与数据文件是分开来存储的,其叶子节点,当前key对于data,在数据表中的位置,根据这个位置,去数据表中就可以找到实际存储的数据。

B+Tree索引种类

索引有这么几种:

  • 主键索引
  • 唯一索引
  • 普通索引
  • 全文索引
  • 组合索引

MySQL默认会对唯一约束添加索引,即当我们没有指定索引时,数据库会默认对 主键/唯一约束建立索引。如果找不到唯一约束,会默认使用6位的rowid进行索引。

回表:
当我们对非唯一约束添加索引后,其生成的B+Tree叶子节点,保存的并不是表里面的真实数据,而是其对应的唯一约束key,通过这个唯一约束key,去查找唯一索引(或者主键索引)的B+Tree,它的叶子节点保存的才是真实数据,这个过程叫做回表。
回表,只针对普通索引。

索引覆盖:
通过上面回表的过程,可以看到需要查询两个索引表,会比较耗时。假设我们对非唯一键username添加索引,
select * from xxx where username =‘张三’,这种查询是需要进行回表的。
select orderid from xxx where username=‘张三’,我们通过username找到唯一约束orderid时,在查找username的索引表时,其叶子节点保存的就已经是唯一约束orderid,此时就不需要再去关联唯一索引表了,已经可以直接读取出数据了,这种叫索引覆盖

最左匹配:
当我们以a+b 建立组合索引之后,B+Tree的结构是什么样的呢?先以a项进行比较,确定下一步的查找方向,当a值一样时,再以b的值进行比较。
如:where a=1 and b>1;这种情况是可以使用索引的,先用a=1查找到使用某一个节点,再根据b>1查找更细的节点
where b=1,这种情况无法使用索引,因缺失a的信息,数据库不清楚应该从哪个节点开始查找,索引失效

从上面的介绍可以看出,当a一样时,b才是有序排列,可查找的,如:
where a >1 and b =1;这种情况下,a >1是可以命中索引的,但是b无法命中索引,因为a>1是个范围,在这种情况下,b是无序的。
where a=1 and b = 1;这种情况下,a=1时,b的排列是有序的,可以命中索引
在这里插入图片描述
组合索引:
现在对字段a,b,c建立组合索引(a,b,c),

SQL 索引情况
where a =1 使用索引,只使用a
where a =1 and b =5 使用索引,使用了a,b
where a=1 and b=5 and c=1 使用索引,使用了a,b,c
where b=5 根据最左匹配原则,索引不生效
where a =1 and c =1 使用索引,使用了a,c根据最左匹配原则,不生效
where a=1 and b>10 and c=1 使用了索引a,b
where a=1 and b like ‘1%’ and c=1 使用了索引a,b

hash索引

在MySQL中,只有Memory存储引擎支持hash索引,但是hash索引有下面一些限制:

  1. hash索引只包含hash值和行指针,不是存储字段值,不能使用hash中的值来避免读取行
  2. hash索引不是按照索引顺序进行排序的,无法进行排序
  3. hash索引不支持部分列匹配查找,使用的是列的全部内容来计算hash值,只能进行精确匹配
  4. 只支持等值比较,不支持任何范围查询
  5. 容易出现hash冲突
  6. 存储在内存中,易丢失

优化

  1. 当使用索引列进行查询的时候,尽量不要使用表达式,把计算逻辑放到自己代码中去,而不是放在数据库中执行
    在这里插入图片描述
  2. 尽量使用主键查询,而不是其他索引,其他索引会触发回表
  3. 使用索引扫描来排序,B+tree索引本身就是有序的。
    mySQL有两种方式进行排序,通过排序操作或者按索引顺序扫描,如果explain出来的type列的值为index,则说明mysql使用了索引扫描来排序。
    在这里插入图片描述
    联合主键(a,b,c)
    select xx,xx from xxx where a =‘xxx’ order by b,c;
    select xx,xx from xxx where a =‘xxx’ order by b desc;

使用a进行过滤,b、c/(b)进行排序,将他们组合在一起,正好可以可以满足索引最左匹配原则

select xx,xx from xxx where a =‘xxx’ order by b desc , c asc;
这种无法使用索引排序,b,c一个是desc,一个是asc,使用了两种不同的排序方向

select xx,xx from xxx where a > ‘xxx’ order by b,c;
这种也是无法使用索引排序,a>‘xxx’,是个范围,不满足最左匹配原则

由此我们可以得出结论,只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方式都一致时,mysql才能用索引来对结果进行排序。如果查询需要关联多张表,则只有当order by子句引用的字段全部为第一张表时,才能使用索引做排序,需要满足最左匹配要求。

  1. union all,in,or都能够使用索引,但是推荐使用in
  2. 待完成…
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章