第五章 索引与算法 (中)B+树索引的使用

5.6 B+树索引的使用
5.6.1 不同应用中B+树索引的使用
在数据库中存在两种类型的应用,OLTP和OLAP应用。在OLTP应用中,查询操作只从数据库中取得一小部分数据,一般可能都在10条记录以下,甚至在很多时候只取1条记录,如根据主键值来取得用户信息,根据订单号取得订单的详细信息,这都是典型OLTP应用的查询语句。在这种情况下,B+树索引建立后,对该索引的使用应用只是通过该索引取得表中少部分数据。这是建立B+树索引才是有意义的。否则即使建立了,优化器也可能选择不使用索引。在OLAP应用中,都需要访问表中大量的数据,根据这些数据来产生查询的结果,对于OLAP中复杂的查询,要涉及到多张表之间的联接操作,因此索引的添加仍然是有意义的。

5.6.2 联合索引
联合索引是指对表上多个列进行索引。前面讨论的情况都是只对表上的一个列进行索引。联合索引的创建方法与单个索引的创建方法一样,不同之处仅在于有多个索引列。
创建如下表t

image.png

从本质上来看,联合索引也是一颗B+树,不同的是联合索引的键值的数量大于等于2,不是1
第五章 索引与算法 (中)B+树索引的使用_第1张图片
多个键值的B+树.png

对于查询 select * from t where a=xxx and b=xxx 显然是可以使用(a,b)这个联合索引的。对于单个的a列查询 select * from t where a=xxx 也可以使用这个(a,b)索引。但对于 select * from t where b=xxx,则不可以使用这颗B+树索引。
联合索引的第二个好处是已经对第二个键值进行了排序处理。可以避免一次排序操作。
创建如下表 buy_log
bug.png

插入如下数据
image.png

创建如下索引


image.png
image.png

对于 select * from buy_date where userid = 2,优化器的选择为如图5-23所示:

image.png

possible_key表示有两个索引可供使用,分别是单个的userid索引和(userid,buy_date)的联合索引。但是优化器最终选择的索引userid,因为该索引的叶子节点包含单个键值,所以理论上一个页能存放的记录应该更多。

对于 select * from buy_log where userid = 1 order by buy_date desc limit 3
执行计划如下:

第五章 索引与算法 (中)B+树索引的使用_第2张图片
image.png

可以使用userid索引,也可以使用(userid,buy_date)索引。但是这次优化器使用了(user_id,buy_date)的联合索引userid_2,因为在这个联合索引中buy_date已经排序好了。根据该联合索引取出数据,无需再对buy_date做一次额外的排序操作。
若强制使用userid索引,则执行计划如下所示:


第五章 索引与算法 (中)B+树索引的使用_第3张图片
image.png

在Extra选项中可以看到Using filesort,即需要额外的一次排序操作才能完成查询。

对于联合索引(a,b,c)来说,
select * from table where a = xxx order by b
select * from table where a = xxx and b = xxx order by c
可以通过联合索引来得到结果,但是对于
select * from table where a =xxx order by c 来说,联合索引不能直接得到结果,还需要执行一次filesort排序操作,因为索引(a,c)未排序

5.6.3 覆盖索引
InnoDB存储引擎支持覆盖索引(covering index,或称覆盖索引),即从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。使用覆盖索引的一个好处是,辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。

覆盖索引的另一个好处是对某些统计问题而言的。要进行如下查询:
select count(*) from buy_log
InnoDB存储引擎并不会选择通过查询聚集索引来进行统计查询。由于buy_log表上还有辅助索引,而辅助索引远小于聚集索引,选择辅助索引可以减少IO操作,故优化器的选择为如下图所示:

image.png

possible_keys列为NULL,但是实际执行时优化器却选择了userid索引,而列Extra的Using index就是代表了优化器进行了覆盖操作操作。
在通常情况下,诸如(a,b)的联合索引,一般是不可以选择列b中所谓的查询条件。但是如果是统计操作,并且是覆盖索引的,则优化器会进行选择,如下述语句:
select count(*) from buy_log where buy_date >= '2009-01-01' and buy_date < '2009-02-01'

表buy_log中有(userid,buy_date)的联合索引,这里只根据列b进行条件查询,一般情况下是不能进行该联合索引的,但是这句SQL查询是统计操作,并且可以利用到覆盖索引的信息,因此优化器会选择改联合索引,其执行计划如下图:


第五章 索引与算法 (中)B+树索引的使用_第4张图片
image.png

** 5.6.4 优化器选择不使用索引的情况 **
在某些情况下,当执行EXPLAIN命令进行SQL语句分析时,会发现优化器并没有选择索引去查找数据,而是通过扫描聚集索引,也就是直接进行全部扫描来得到数据。这种情况多发生于范围查找、JOIN链接操作等情况下。例如:
select * from orderdetails where orderid > 10000 and orderid < 102000;
表orderdetails有(orderid,productid)的联合索引,此外还有对于列orderid的单个索引。上述SQL显然是可以通过扫描orderid上的索引进行数据的查找。然而通过EXPLAIN命令,用户会发现优化器并没有按照orderid上的索引来查找数据。优化器选择了primary聚集索引,也就是表扫描,而非orderid辅助索引扫描。
原因在于要选取的数据是整行信息,而orderid索引不能覆盖到我们要查询的信息,因此在对orderid索引查询到指定数据后,还需要一次书签访问来查找整行数据的信息。虽然orderid索引中数据是顺序存放的,但是再一次进行书签查找的数据则是无序的,因此变为了磁盘上的离散读操作。如果要求访问的数据量很小,则优化器还是会选择辅助索引,但是当访问的数据占整个表中数据的蛮大一部分时(一般20%左右),优化器会选择通过聚集索引来查找数据。因为顺序读要远远快于离散读。

** 索引提示 **
MySQL数据库支持索引提示(INDEX HINT),显示地告诉优化器使用哪个索引。以下两种情况可能需要用到index hint

  • MySQL数据库的优化器错误地选择了某个索引,导致SQL语句运行的很慢。这种情况在最新版的MySQL数据库中很少见
  • 某个SQL语句可以选择的索引非常多,这是优化器选择执行计划时间的开销可能会大于SQL语句本身。例如,优化器分析range查询本身就是比较耗时的操作。这是人为的分析最优索引的选择,通过index hint来强制使用优化器不进行各个执行路径的成本分析,直接选择指定的索引来完成查询。

使用use index只是告诉优化器可以选择该索引,实际上优化器还是会再根据自己的判断进行选择,只有使用force index才能是优化器的最终选择和用户指定的索引一致。

** Multi-Range Read优化 **
MySQL5.6 版本开始支持Multi-Range Read(MRR)优化。MRR优化的目的就是为了减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问,这对于IO-bound类型的SQL查询语句可带来极大的性能提升。MRR优化可适用于range,ref,eq_ref类型的查询。
MRR优化有以下几个好处:

  • MRR使数据访问变得较为顺序。在查询辅助索引时,首先根据得到的查询结果,按照主键排序的顺序进行书签查找。
  • 减少缓冲池中页被替换的次数
  • 批量处理对键值的查询操作

对于InnoDB和MyISAM存储引擎的范围查询和JOIN查询,MRR的工作方式如下:
1、将查询得到的辅助索引键值放于一个缓冲中,这是缓冲中的数据是根据辅助索引键来排序的
2、将缓存中的键值根据RowID进行排序
3、根据RowID的排序顺序来访问实际的数据文件

此外,若InnoDB存储引擎或者MyISAM存储引擎的缓冲池不是足够大,即不能存放下一张表中的所有数据,此外频繁的离散读操作还会导致缓冲中的页被替换出缓冲池,然后又不断地读入缓冲池。若是按照主键顺序进行访问,则可以将此重复行为降为最低。
MRR还可以将某些将某些范围查询,拆分为键值对,以此来进行批量的数据查询。这样做的好处是可以在拆分过程中,直接过滤一些不符合查询条件的数据,例如:
select * from t where key_part1 >= 1000 and key_part1 < 2000 and key_part2 = 1000 ,表t有(key_part1,key_part2)的联合索引,因此索引根据key_part1,key_part2的位置关系进行排序。如没有MRR,此时查询类型为Range,SQL优化器会先将key_part1大于等于1000且小于2000的数据都取出,即使key_part2不等于1000,待取出行数据后再根据key_part2的条件进行过滤。这会导致无用数据被取出。如果有大量的数据且其key_part2不等于1000,则启用MRR优化使性能有巨大提升,若是启用了MRR优化,优化器会先将查询条件进行拆分,然后再进行数据查询。就上述查询而言,优化器会将查询条件进行拆分为(1000,1000),(1001,1000),...,(1999,1000),最后根据这些拆分出来的条件进行数据的查询。

是否启动MRR优化可以通过参数optimizer_switch中的标记来控制。当mrr为on时,表示启动MRR优化,mrr_cost_based标记表示是否通过cost based的方式来选择是否启动mrr。若将mrr设为on,mrr_cost_based设为off,则总是启用MRR优化。

通过参数read_rnd_buffer_size来控制键值的缓冲区大小,当大于该值时,则执行器对已经缓存的数据根据RowID进行排序,并通过RowID来取得行数据。该值默认为256K

** Index Condition Pushdown(ICP)优化 **
在进行索引查询时,首先根据索引来查找记录,然后再根据where来过滤记录。在支持ICP后,MySQL数据库会在取出索引的同时,判断是否可以进行where条件过滤,也就是将where的部分过滤操作放在了存储引擎层。在某些查询下,可以大大减少上层SQL层对记录的索引,从而提高数据库的整体性能

你可能感兴趣的:(第五章 索引与算法 (中)B+树索引的使用)