【MySQL】深入了解 InnoDB中的聚集索引和辅助索引(B+树索引)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wrs120/article/details/91126531

1. B+ 树索引分类

1.1 聚集索引

  按照每张表的主键构成一棵B+树,叶子节点中存放整张表的行记录数据,也将聚集索引的叶子节点成为数据页,每个数据页之间通过一个双向链表来进行链接。数据页存放的是每行的所有记录,非数据页存放的是键值和指向数据页的偏移量。张表只能有一个聚集索引

  1. 可以在叶子节点直接找到数据
  2. 对于主键的排序查找和范围查找速度非常快,因为聚集索引是逻辑上连续的。比如查询后10条数据,由于B+树索引是双向链表,可以很快找到随后一个数据页,然后取出最后的10条数据

1.2 辅助索引

  也称 非聚集索引,按照每张表创建的索引列创建一棵B+树,叶子节点并不包含行记录的全部数据。叶子节点包含键值 和 书签,书签用来告诉InnoDB存储引擎在哪可以找到与索引对应的行数据,通常是襄阳行数据的聚集索引键。每张表可以有多个辅助索引

  如果某个查询是通过辅助索引查找数据的,则查找过程为:先遍历辅助索引并找到叶节点找到指针获取主键索引的主键,然后通过主键索引找到对应的页从而找到一个完整的行记录。注:没执行一次查询就是一次IO,比如 辅助索引树高度为3,聚集索引树高度为2,则通过辅助索引查询数据时就要进行3+2次逻辑IO最终得到一个数据页


2. 使用

2.1 联合索引

  联合索引 也是一个B+树,只不过键值的数量为多个,比如联合索引为(a,b),b相对于a才是有序的,可以用二维数据理解一下,比如A(3,5),在键A做的的都是小于(3,5)的,右边的都是大于等于(3,5),用如下语句创建了一张表

create tablebuy_log (
	userid int Unsigned not null,
	num int,
	buy_date Date
)
//创建两个索引:userid,userid_2
Alter table buy_log add key(userid);   //userid
Alter table buy_log add (userid,num,buy_date)  //userid_2
  1. select * from buy_log where userid=2;,经过EXPLAIN分析会使用userid索引,因为该辅助索引叶子节点包含单个键值,理论上一个页能存放更多的记录
  2. select * from buy_log where userid =1 and num=2 order by buy_date Desc limit 3,经过EXPLAIN分析会使用(userid,num,buy_date)联合索引userid_2,因为联合索引已经对buy_date排好序了,不要再对buy_date做一次额外的排序了。如果强制索引索引userid,经过分析,会做一次排序操作Useing filesort,即对buy_date排序,因为userid索引中的buy_date是无序的
  3. select * from buy_log where userid =1 order by buy_date Desc limit 3,经过EXPLAIN分析会使用userid索引,不会使用联合索引,并进行一次排序操作,因为(userid,buy_date)是无序的

2.2 覆盖索引

  也称索引覆盖,即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录
使用覆盖索引有2大好处是:

  1. 辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以较少大量的IO操作
  2. 做统计时,不会通过聚集索引来统计,通过辅助索引就可以实现统计,也减少了IO

利用上面创建的表和索引,再通过举例说明覆盖索引:

  1. select count(*) from buy_log,经过EXPLAIN分析,会使用userid辅助索引,然后进行的是覆盖索引Extra为Using index
  2. select count(*) from buy_log where buy_date >=‘2019-01-01’ and buy_date < ‘2019-06-07’,经过EXPLAIN分析,会使用联合索引(userid,num,buy_date),一般情况下是不能进行进行该联合索引的,但是此SQL是统计操作,可以利用覆盖索引的信息,就可以得到想要的结果,因此优化器会选择该联合索引

2.3 优化器选择不适用索引:全表扫描

  即不使用索引去查找数据,而是通过扫描聚集索引,即直接进行全表的扫描得到数据。这种情况多发生于范围查找,join链接等情况.下面通过订单表举例说明一下:

select * from orderdetails where orderid > 10000 and orderid<102000;

  即查找订单号大于10000小于102000的订单详情,该表有1个联合索引(orderid,productid),还有1个单个索引(orderid),上面这句话显然可以通过索引(orderid)进行数据的查找,但是经过EXPLAIN分析,优化器并没有选择userid索引查数据,而是选择了primary聚集索引,也就是表扫描,为什么那???因为用户选择的数据是整行数据,辅助索引不能覆盖我们要查询的信息,因为对orderid索引查询到指定数据后,还需要进行一次书签访问来查找整行数据信息。虽然orderid索引中的数据是顺序存放的,但是再一次书签查找的数据是无序的了,因此变为了磁盘上的离散读操作。如果访问的数据量很少(20%左右),优化器还是会选择聚集索引来查找数据的。因为顺序读远远快于离散度
  不能进行索引覆盖的情况下,优化器选择辅助索引的情况为:通过辅助索引查找的数据是少量的,但是如果磁盘是固态磁盘,随机读操作很快,并且确认辅助索引可带来更好的性能,可使用关键字FORCE INDEX来强制使用某个索引

select * from orderdetails FORCE INDEX(userid) where orderid > 10000 and orderid<102000;

  之前在使用的时候就遇到这样一个问题,利用时间查询,时间字段加索引,运行时发现有的时候可以命中索引,有的时候命不中,现在明白了不命中的原因是此时数据量太大了,当时就是采用了强制走索引,数据库有30万条数据,没使用索引前查询为3秒左右,强制使用索引后只用1秒左右


更多有关mysql索引问题,参见 https://blog.csdn.net/wrs120/article/details/80711800

猜你喜欢

转载自blog.csdn.net/wrs120/article/details/91126531