MySQL索引数据结构红黑树,Hash,B+树详解

数据结构和算法(Data Structure Visualizations):https://www.cs.usfca.edu/~galles/visualization/Algorithms.html  

一、MySQL索引底层的实现

索引是帮助MySQL高效获取数据的排好序数据结构

上图中有一张表,表名为 t ,表中有7条数据;使用 select * from t where t.clo2 = 89 查询;

1、若表中没有创建索引,则会全表扫描,一条一条的遍历查询,需要遍历 6 次,查询一行数据至少和磁盘做一次I/O操作(I/O是很耗性能的),至少要做 6 次 I/O 操作;

2、表中建立了索引:

(1)若索引底层是二叉树左边的子元素小于父元素,右边的子元素大于父元素)存储的,则如下图所示:

这样查询 4 次就找到数据了;

当然,在极端情况下,若按照大小顺序插入二叉树,则会形成单边增长的二叉树,这样使用索引的时候和全表扫描是一样的了;

(2)若索引底层是红黑树存储的,则如下图所示:

红黑树:当单边的节点大于3时候,就会自动调整,这样可以解决二叉树的弊端;红黑树也叫平衡二叉树;

 当然,红黑树也有弊端的,当数据量特别大的时候,红黑树的高度特别大;假如有500W条数据,则红黑树高度为 23,若我们要查找的刚好是红黑树的叶子节点,则需要查找 23 次才可以,即要发生 23 次的磁盘 I/O 操作,性能就太差了;

(3)若索引底层是 B-Tree 存储的

(叶子节点具有相同的深度,叶节点的指针为空;所有索引元素不重复;一个节点可以存储多个元素,节点中的数据索引从左到右递增排列

若 Max. Degree = 4,则如下图所示:

 这样只查询 2 次就找到了;

当然 B-Tree 也是有弊端的;以下是 B-Tree 的存储,数字为key,data为对应的数据;

若一个节点我们申请的空间为16KB,若data中的数据过大,则一个节点能放的数据量越小,这样就会造成树的高度比较大了(比红黑树高度小点);

 (4)MySQL的索引底层使用的 B+Tree 存储的(数据存储在叶子节点)

B+Tree:

  非叶子节点不存储data,只存储索引(冗余),可以放更多索引;

  叶子节点包含所有索引字段,即所有的data元素存储在叶子节点上;

  叶子节点使用指针连接,提高区间访问的性能;

  从左到右一次递增;

B+Tree 相对于 B-Tree的优化点:

  优化点1:  B-Tree的所有节点都存储了 data 元素, B+Tree的非叶子节点不存储 data元素,则 B+Tree 的一个非叶子节点可以存储更多的索引;

  优化点2: B+Tree在叶子节点之间增加了指针连接;对 select * from t where col2 > 20 的范围查找有很好的支持;

MySQL 对 B+Tree 做了优化,叶子节点使用的是双向指针;

以上图中查找 49 的数据:

 I. 先将根节点的数据(15, 56, 77) 做一次磁盘 I/O 操作取出加载到内存中,然后再在内存中做比对,找到对应的指针,查找到其对应的节点;

 II. 将指针指向节点的数据(15, 20, 49) 做一次磁盘 I/O 操作取出加载到内存中,然后再在内存中做比对,找到对应的指针,接着去叶子节点获取数据;

 <1> 查看MySQL文件页大小(一个节点的大小):

SHOW GLOBAL STATUS like 'Innodb_page_size';

<2> MySQL页文件默认为16KB,树的高度为3,能够存储多少数据?

  我们先看非叶子节点,假设主键ID为 bigint 类型,那么长度为8B,指针大小在Innodb源码中6B,一共14B,那么一页(即一个节点)可以存储  16KB/14B=1170 个索引元素和 1170个指针;根节点有1170个索引和1170个指针,树高度为2的节点就有1170个,那么叶子节点的数量为 1170x1170;每个叶子节点可以存储16KB,若每条数据比较大为1KB,那么每个叶子节点可以存储16条数据;那么,高度为3的 B+Tree 的叶子节点可以存储的数据量为 1170x1170x16=2000W;

 在实际的MySQL中表的索引存储可以选择 Hash 或 BTree

(5)若索引使用的 Hash 存储的,存储的时候先做一次hash运算,根据 hash 的值就可以快速的定位数据的磁盘指针,这样就不管表里面有多少数据,我们的查询效率都非常的快;

在实际中为什么使用 B-Tree 或 B+Tree 来存储索引的方式更多,而不太使用 hash 呢?

原因1:若使用 select * from t where clo2 > 6,这种查找范围的SQL,那Hash就不能搞定了,就不会走索引了;而且对排序hash也没有办法;

原因2:hash会产生 hash 碰撞,MySQL的底层对hash做了处理,很少会发生hash碰撞的;

二、MySQL的存储引擎的实现

同一个数据库中,不同的表可以设置不同的存储引擎;

MySQL的数据存储在 data 目录下, data 目录下的 文件夹是以 数据库为单位的,数据库文件夹下面存放的表数据;  data / {数据库名} /表文件

 1、MyISAM存储引擎索引实现

MyISAM存储引擎的索引文件和数据文件是分离的(非聚集);

MyISAM 存储引擎的一个表有3个文件:  *.frm 文件存储的表的结构; *.MYD 文件存储表的数据; *.MYI 文件存储表中的索引数据;

MYISAM 存储引擎的索引的叶子节点的data中存储的是索引所在行的磁盘指针; ---- 非聚集索引

MYISAM 存储引擎的主键索引 和 非主键索引的存储是差不多的,InnoDB 存储引擎的 主键索引 和 非主键索引存储是不一样的;

 2、InnoDB 存储引擎-索引实现

InnoDB存储引擎索引文件和数据文件是合一的(聚集);

InnoDB 存储引擎的1个表有2个文件:  *.frm 文件存储表的结构; *.ibd 文件存储的是索引和数据;

InnoDB表的数据文件本身就是按 B+Tree 组织的一个索引结构文件;聚集索引叶子节点包含了完整的数据记录

(1)InnoDB的主键索引

InnoDB 存储引擎的索引的叶子节点的data中存储的是索引对应的所有数据;----聚集

问题1:为什么InnoDB表必须有主键,并且推荐使用整型的自增主键?

a. 因为 MySQL对于 InnoDB 表设计的就是按照 B+Tree 组织存储数据的,若没有主键就没有办法去存储数据了;但是在平常我们建表的时候没有指定主键也是可以建成功的,这是因为 MySQL 会生成一个 rowid 作为数据的唯一标识;

b. 若使用的 UUID 作为主键,在查找的时候需要去比较大小,字符串UUID比较的效率肯定低于数据的比较;在进行比较的时候会把数据拿到内存空间中做比较,UUID为字符串占用的内存空间就会较多;

c. 若是递增的,则插入的数据直接向后排,这个节点满了,直接新增一个节点就好了;若不是递增的,有个节点存储满了(5, 9),但是新插入了一个数据(7)在这个节数据的中间,则需要将这个节点先分裂,再平衡去满足 B+Tree 的结构;

(2)InnoDB 的非主键索引

 在使用非主键索引查找的时候,先从非主键索引的树中查询到对应的主键值,然后使用主键值去到主键索引的树中去查找;

 对于非主键单值索引,若索引字段的值为 null,则它的数据不会放到非叶子节点上,是放在叶子节点的链表的最前面的;(强烈不建议字段设置为null)

问题2:为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省存储空间)

因为在插入数据之前先要维护一下索引,然后再将数据插入进去;若 主键索引 和 非主键索引 的叶子节点都存储具体的数据,则一个 insert 语句插入成功的判断就是 向主键索引中插入成功 且 向非主键索引中也插入成功,这样就造成了事务的问题,事务是很耗性能的;当然,主键索引和非主键索引的叶子节点都存储具体数据,会造成数据的同样的数据存储了几份,就造成了空间的浪费;

 

联合索引的底层存储结构

以上的联合索引从左到右由字段 a,b,c 组成;

联合索引在存数据或比较的时候,先比较联合索引最前面的字段,若最前面的字段值一样,则再比较第二个字段的值;

联合索引的索引字段中有一个值为null,则将其放在叶子节点的最前面;可以认为null值是最小的。

 

 

posted @ 2020-03-11 21:42  风止雨歇  阅读(8211)  评论(0编辑  收藏  举报