mysql - 索引深入浅出
数据模型:
1: 哈希表
2: 有序数组
value(id_card_n3)>value(id_card_n2)>...........>value(id_card_n4)>value(id_card_n1)
3: 搜索树
3.1 二叉树
以InnoDB的一个整数字段索引为例,这个N差不多是1200。这棵树高是4的时候,就可以存1200的3次方个值,这已经17亿了。考虑到树根的数据块总是在内存中的,一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。
3.1 N叉树(B+树)
B树分为B-,B,B+树,这里主要讲解B+树,因为Innodb索引用的就是B+树
1: B+树的生成基本步骤
- 1)若为空树,创建一个叶子节点,然后将记录插入其中,此时这个叶子节点也是根节点,插入操作结束。
- 2)针对叶子类型节点:根据key值找到叶子节点,向这个叶子节点插入记录。插入后,若当前节点key的个数小于等于m-1,则插入结束。否则将这个叶子节点分裂成左右两个叶子节点,左叶子节点包含前m/2个记录,右节点包含剩下的记录,将第m/2+1个记录的key进位到父节点中(父节点一定是索引类型节点),进位到父节点的key左孩子指针向左节点,右孩子指针向右节点。将当前节点的指针指向父节点,然后执行第3步。
- 3)针对索引类型节点:若当前节点key的个数小于等于m-1,则插入结束。否则,将这个索引类型节点分裂成两个索引节点,左索引节点包含前(m-1)/2个key,右节点包含m-(m-1)/2个key,将第m/2个key进位到父节点中,进位到父节点的key左孩子指向左节点, 进位到父节点的key右孩子指向右节点。将当前节点的指针指向父节点,然后重复第3步
举例说明: 以5阶B+树为例(5阶B+树节点最多有4个关键字,最少有2个关键字,其中根节点最少可以只有一个关键字),从初始时刻依次插入数据
1)在空树插入5

2)依次插入8,10,15

3)插入16

此时节点超过关键字的个数,所以需要进行分裂。由于该节点为叶子节点,所以可以分裂出来左节点2个记录,右边3个记录,中间key成为索引节点中的key(也可以左节点3个记录,右节点2个记录),分裂后当前节点指向了父节点(根节点)。结果如下图所示

当前节点的关键字个数满足条件,插入结束
4)插入17

5)插入18

当前节点超过关键字的个数,进行分裂。由于是叶子节点,分裂成两个节点,左节点2个记录,右节点3个记录,关键字16进位到父节点(索引类型)中,将当前节点的指针指向父节点,如下图所示

当前节点的关键字个数满足条件,插入结束
6)同理继续插入6,9,19,细节不再描述
7)继续插入7

当前节点超过关键字的个数,进行分裂。由于是叶子节点,分裂成两个节点,左节点2个记录,右节点3个记录,关键字7进位到父节点(索引类型)中,将当前节点的指针指向父节点,如下图所示

当前节点超过关键字的个数,进行分裂。由于是索引节点,左节点2个关键字,右节点2个关键字,关键字16进入到父节点中,将当前节点指向父节点,如下图所示

当前节点的关键字个数满足条件,插入结束
2: B+树的删除操作
如果叶子节点中没有相应的key,则删除失败。否则执行下面的步骤:
- 1)删除叶子节点中对应的key。删除后若节点的key的个数大于等于Math.ceil(m/2) – 1,删除操作结束,否则执行第2步。
- 2)若兄弟节点key有富余(大于Math.ceil(m/2) – 1),向兄弟节点借一个记录,同时用借到的key替换父结(指当前节点和兄弟节点共同的父节点)点中的key,删除结束。否则执行第3步。
- 3)若兄弟节点中没有富余的key,则当前节点和兄弟节点合并成一个新的叶子节点,并删除父节点中的key(父节点中的这个key两边的孩子指针就变成了一个指针,正好指向这个新的叶子节点),将当前节点指向父节点(必为索引节点),执行第4步
- 4)若索引节点的key的个数大于等于Math.ceil(m/2) – 1,则删除操作结束。否则执行第5步
- 5)若兄弟节点有富余,父节点key下移,兄弟节点key上移,删除结束。否则执行第6步
- 6)当前节点和兄弟节点及父节点下移key合并成一个新的节点。将当前节点指向父节点,重复第4步。
1)初始状态

2)删除22

删除后叶子节点中key的个数大于等于2,删除结束
3)删除15

当前节点只有一个key,不满足条件,而兄弟节点有三个key,可以从兄弟节点借一个关键字为9的记录,同时更新将父节点中的关键字由10也变为9,删除结束。

4)删除7

当前节点关键字个数小于2,(左)兄弟节点中的也没有富余的关键字(当前节点还有个右兄弟,不过选择任意一个进行分析就可以了,这里我们选择了左边的),所以当前节点和兄弟节点合并,并删除父节点中的key,当前节点指向父节点。

此时当前节点的关键字个数小于2,兄弟节点的关键字也没有富余,所以父节点中的关键字下移,和两个孩子节点合并,结果如下图所示。

以上,我们将B+树基本讲解完成了,那么我们一起进入相对偏实战的内容吧
索引:
InnoDB 的索引模型
从图中不难看出,根据叶子节点的内容,索引类型分为主键索引和非主键索引。主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clusteredindex)非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引
索引维护
联合索引
首先,
如果执行的语句是select ID fromTwhere k between 3 and 5,这时只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引k已经“覆盖了”我们的查询需求,我们称为覆盖索引。
最左前缀原则:
顺序问题: 联合索引存在一个顺序问题,那就是首先以最左的索引对应的值进行排序,在第一个索引相同值的情况下,在对第二个索引排序,一次往下。。 举例:上图中name属于第一索引,所以第一索引是按照name排序的,在name相同的情况下(比如张三),在对第二个索引对应的值进行排序,就如上图的三个张三下age是按照10,10,20排序的.
可以看到,索引项是按照索引定义里面出现的字段顺序排序的。当你的逻辑需求是查到所有名字是“张三”的人,,可以快速定位到ID4,然后向后遍历得到所有需要的结果。如果你要查的是所有名字名为占山并且年龄为10的人,这时,你也能够用上这个索引,查找到第一个符合条件的记录是ID4,然后向后遍历,直到不满足条件为止,这样可以筛选出ID-4道ID-6的值,然后在遍历第二个条件,ID4满足条件,一次向后便利,直到便利道ID-6大于10,遍历结束(并不是因为ID-6在图中是最后一个值才不继续便利的,因为在第一个索引对应的值相等的情况,第二个索引的值是按照顺序排列的,因为ID-6中age已经>10了,如果后面还存在值肯定也是>=20的,那么肯定满足!=10的条件,所以就没必要向后遍历了)
索引下推:
联合索引的B+树
首先我们来看一个问题,我们有一个表user,有几个字段id、c1、c2、c3、c4,其中id是主键,c1、c2、c3字段建立联合索引,那么执行下面几个SQL,来看一下索引执行情况:
select * from user where c1= 12 and c2= 14 and c3 = 3 // 索引全匹配
select * from user where c1= 12 and c2= 14 // 索引部分匹配
select * from user where c1= 12 and c3 = 3 // 索引部分匹配
select * from user where c2= 12 and c3 = 3 // 索引无法匹配
可以看到,对于联合索引,全部命中索引字段可以执行索引;部分命中并符合最左匹配原则,也可能会执行索引;不满足最左匹配原则,则无法命中索引,那么这是为什么呢?
我们来分析一下对于联合索引的B+树的数据结构

上图就是一个联合索引的B+树示意图,InnoDB会使用聚簇索引在B+树维护索引和数据文件,然后我们创建了一个联合索引name、age、point也会生成一个索引树,同样是B+树的结构,只不过它的data部分存储的是联合索引所在行的主键值。
对于联合索引来说只不过比单值索引多了几列,而这些索引列全都出现在索引树上。对于联合索引,存储引擎会首先根据第一个索引列排序,如上图我们可以单看第一个索引列,横着看,如,1 1 5 12 13…他是单调递增的;如果第一列相等则再根据第二列排序,依次类推就构成了上图的索引树,上图中的c1列都等于1时,则根据c2排序,此时c2列也相等则按c3列排序,如:1 1 4 ,1 1 5,c=4在c=5前面,以及13 12 4,13 16 1,13 16 5就可以说明这种情况。
联合索引的查找方式
当我们的SQL可以应用到联合索引的时候,比如select * from user where c1= 12 and c2= 14 and c3 = 3 。
存储引擎首先从根节点(一般常驻内存)开始查找:
第一个索引的第一个索引列为1,12大于1;
第二个索引的第一个索引列为56,12小于56;
于是从这俩索引的中间读到下一个节点的磁盘文件地址,从磁盘上Load这个节点,通常伴随一次磁盘IO,然后在内存里去查找。
当Load叶子节点的第二个节点时又是一次磁盘IO,比较第一个元素,b=12,c=14,d=3完全符合,于是找到该索引下的data元素即ID值,再从主键索引树上找到最终数据。

最左匹配原则
好了,上面我们了解了联合索引的B+树检索过程,那么再来考虑另一个问题,为什么联合索引会有最左匹配原则?
之所以会有最左前缀匹配原则和联合索引的索引构建方式及存储结构是有关系的。
首先我们创建的c1、c2、c3索引,相当于创建了(c1)、(c1、c2)(c1、c2、c3)三个索引,看完下面你就知道为什么相当于创建了三个索引。
我们看,联合索引是首先使用多列索引的第一列构建的索引树,用上面的例子就是优先使用c1列构建,当c1列值相等时再以c2列排序,若c2列的值也相等则以c3列排序。我们可以取出索引树的叶子节点看一下。

索引的第一列也就是c1列可以说是从左到右单调递增的,但我们看c2列和c3列并没有这个特性,它们只能在c1列值相等的情况下这个小范围内递增,如第一叶子节点的第1、2个元素和第二个叶子节点的后三个元素。
由于联合索引是上述那样的索引构建方式及存储结构,所以联合索引只能从多列索引的第一列开始查找。所以如果你的查找条件不包含c1列如(c2, c3)、(c2)、(c3) 是无法应用缓存的,以及跨列也是无法完全用到索引如(c1, c3),只会用到c1列索引

浙公网安备 33010602011771号