六、索引——B+树索引的使用

提示:文章内容来自《mysql是怎样运行的》以及部分B站宋红康老师的视频,这里仅仅是我的笔记,对重点内容的记录。强烈推荐购买这本书《mysql是怎样运行的》。

文章目录

  • 前言
  • 一、索引的代价?
  • 二、扫描区间
  • 三、索引用于排序
    • 3.1 使用联合索引进行排序时的注意事项
    • 3.2 不可以使用索引排序的几种情况
    • 3.3 回表的代价——体现limit的作用,用多少查多少
  • 四 、如何更好的创建和使用索引


前言

根据前面对索引的学习,我们总结出:
1、每个索引都对应一个B+树,B+树分为好多层,最下面的一层是叶子节点,其余是内节点,所有用户记录都存储在B+树的叶子节点,所有目录项记录都存储在内节点
2、我们可以为感兴趣的列创建二级索引,二级索引的叶子节点包含的用户记录有索引列+主键组成,如果想通过二级索引查找完整的用户记录,需要执行“回表”操作,也就是通过二级索引找到主键后,再到聚簇索引中找到完整的用户记录
3、B+树的中的每层节点都按照索引列的值从小到大的排序组成了双向链表,而且每个页内的记录,都是按照索引列的值从小到大排的单向列表,如果是联合索引的话,先按照c1排序,c1相同是按c2,c2相同是再按c3
4、通过索引查找记录时,先从B+树的根节点开始一层一层的向下搜索,找到页后,再根据二分查找找到槽,再一次遍历槽中的数据。


一、索引的代价?

1、空间上
索引即数据,因此索引不是随便就建立的。
2、时间上
索引虽然提高了查询的速度,降低增删改的速度,主要是按照B+树的结构“升序”形成的,更改一个数据,可能会造成大量的页分裂
优化器需要话更多的时间,分支各个索引的执行成本,选择成本低的,生成执行计划,索引的建立的太多,会增加这一部分的开销

二、扫描区间

在理解扫描的区间时,一定要在脑海里建立一个聚簇索引和二级索引的图:
六、索引——B+树索引的使用_第1张图片

首先创建一个表:
CREATE TABLE single_table(
id INT NOT NULL AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
KEY idx_key1 (key1),
UNIQUE KEY uk_key2 (key2),
KEY idx_key3 (key3),
KEY idx_key_part(key_part1,key_part2,key_part3)
)ENGINE =INNODB CHARSET =utf8;

1、全表扫描的扫描区间:对于InnoDB来说,全表扫描的扫描区间就是从聚簇索引的第一个叶子节点开始,沿着单向链表向后扫描,直到扫到叶子节点的最后一个记录,也就是说扫描所有的叶子节点,如果数据量很大时,这种扫描无疑是十分耗时的,扫描范围是(-∞,+∞),这也是我们在优化慢查询时,为什么要必买全表扫描的原因
2、只扫描聚簇索引的一部分:
SELECT * FROM single_table WHERE id >=2 AND id <=100
这个语句实际是想查找[2,100]区间的所有聚簇索引记录。我们可以通过聚簇索引的B+树快速定位到2的位置,向后扫描,直到扫描到id值不符合 id <=100的记录。那这条sql语句的扫描区间自然就是[2,100]

3、对于非聚簇索引
SELECT * FROM single_table WHERE key2 IN (1438,6328) OR (key2 >=38 AND key2 <=79)
这条sql的语句涉及到key2 ,我们在key2上建立了二级索引,因此判断扫描范围也是根据这个二级索引来的(你可能会问,如果查询的列没建立索引怎么办?那就直接全表扫描,扫描所有的聚簇索引的叶子节点,特别提一下,不是所有的全表扫描都比二级索引慢,别忘了通过二级索引查找数据是需要回表的,数据量很小的话,可能全表扫描更快)。
①key2 IN(1438,6382) 等同于key2 IN(1438)和key2 IN (6382):
key2 IN (1438)的扫描区间是[1438,1438],类似于[1438,1438]这种的我们称为单点扫描,单点扫描不是只扫描一条记录,如果我们的列上有唯一限制,那就是直接返回这一条记录;如果没有唯一限制,还会继续扫描下一个,直到扫描到不在区间里的为止也就是key2>1438的,我们因为对Key2建立了唯一索引,所以这个单点扫描,扫描到记录后会直接返回,否则具体扫描多少条记录,还得看具体情况。
② 同理key2 IN (6382)也是,扫描区间是[6382,6382]。
③ (key2 >=38 AND key2 <=79)的扫描区间是[38,79]
因为key2 IN (1438,6328) OR (key2 >=38 AND key2 <=79)是通过or连接的,二者取并集就是这条sql的扫描结果。
也就是[38,79]和两个单点1438、6382,6382。
因为我们查询的结果不能在二级索引中完全获取,也就是用不上覆盖索引,因此每查询条记录都要回表。

教给大家一个方法,对于一般的sql直接可以按照这个思路分析扫描范围:
以and 、or划分where后的过滤条件为各个节点,判断各个节点中是否用了索引列,没有就是全表扫描,用了的话,在分析扫描范围,如果时是and就取交集,or就取并集。
多表的话比较复杂,在成本分析中再说

三、索引用于排序

在编写查询语句时候,经常会使用到order by对查询出的语句进行排序,在一般情况下,我们只能把记录加载到内存中,然后再用一些排序算法,对这些记录进行排序,有时查询的结果集可能太大以至于无法在内存中进行排序,此时就需要暂时借助磁盘的空间来存放中间结果,在排序操作完成后再把排序好的结果集返回客户端。在mysql中,这种在内存或者磁盘中进行排序的方式统称为文件排序(fileSort),但是如果在order by后面使用了索引列,就可能省去在内存或磁盘中排序的步骤。
比如:
SELECT * FROM single_table ORDER BY key_part1,key_part2,key_part3 LIMIT 10;
这个查询的结果会先按照key_part1从小到大排序,如果key_part1相同的时,再按照key_part2从小到大排序,如果key_part2也相同,就按照key_part3排序(与索引一样),LIMIT可以减少回表的次数

3.1 使用联合索引进行排序时的注意事项

就是order by子句后面的列也要必须按照索引的列顺序
特别的:SELECT * FROM single_table WHERE key_part1=1 AND key_part2=2 ORDER BY key_part3 LIMIT 10;
这个也是可以使用联合索引进行排序的,原因是key_part1=1 AND key_part2=2 下 key_part3 也是从小到大排的

3.2 不可以使用索引排序的几种情况

1)ASC DESC混用:其实这个在8.0后,出现了降序索引,就是我们可以指定索引的排序,但是除此之外是不能的
SELECT * FROM single_table ORDER BY key_part1,key_part2 desc LIMIT 10;

2)排序列包含非同一个索引的
SELECT * FROM single_table ORDER BY key1,key2 LIMIT 10;

3)排序列是某个联合索引的索引列,但是这些排序列在联合索引中不连续
SELECT * FROM single_table ORDER BY key_part1,key_part3 LIMIT 10;

4)用于生成扫描区间的索引列和排序列不一样
SELECT * FROM single_table ORDER BY key1 =’a’ order by key2 LIMIT 10;

5)排序列不是以单独列名的形式出现在order by子句中
SELECT * FROM single_table ORDER BY key1+1 LIMIT 10;

综上,想要排序利用上索引还是非常严格的:
首先,我排序的列,必须是索引列,而且还必须符合我的索引结构,比如说我的索引是先按照什么排序的,再按照什么排序的,是升序还是降序等等,其实想利用上索引进行排序,就必须符合我索引的设计,否则你就去filesort吧。

索引用于分组:与排序差不多

3.3 回表的代价——体现limit的作用,用多少查多少

对于下面:
SELECT * FROM single_table WHERE key1>’a’ and key1<’c’

1、优化器首先会看看我们使用了哪些索引
2、分析全表扫描的成本代价
3、分析各个索引的成本代价
4、选择最适合的

那么这个语句使用的索引是key1,那么它可能存在两种执行计划:1、直接扫描全部的聚簇索引,针对每一条聚簇索引的记录,判断条件是否成立;2、利用key1的二级索引+回表
如果优化器发现(‘a’,’c’)的扫描区间太多了,甚至比全表扫描成本还高,那么它就不会使用二级索引,但是如果加了”LIMIT”,就可以限制回表的次数,进而优化器就会考虑使用二级扫描+回表。

四 、如何更好的创建和使用索引

1)只为用于搜索、排序、分组的列创建索引
Where 、 order by 、 group by
2)当列中不重复的值的个数在总记录条数中的占比很大时,才为列建立索引
3)索引列的类型尽量小
4)为列的前缀创建索引,主要是字符串,减少索引占用的空间
5)尽量使用覆盖索引进行查询,避免回表操作带来的性能损耗
覆盖索引,二级索引中也存在个别数据,索引列和主键,如果我们查询的数据在二级索引中能够获取到,那就不会发生回表,这一方式就叫做覆盖索引
6)让索引以列名的形式在搜索条件中单独出现
7)为了尽可能少的让聚簇索引发生页分裂的情况,建议让主键自增长(自增长,新的数据插入只影响最后一页)
8)定位并删除表中冗余和重复的索引

你可能感兴趣的:(mysql高级,mysql)