索引的底层实现原理

数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘块(对应索引树的节点),索引树越低,越“矮胖”,磁盘IO次数就少

MySQL支持两种索引,一种的B-树索引,一种是哈希索引
大家知道,B-树和哈希表在数据查询时的效率是非常高的。
这里我们主要讨论一下MySQL InnoDB存储引擎,基于B-树(但实际上MySQL采用的是B+树结构)的索引结构。
B-树是一种m阶平衡树,叶子节点都在同一层,由于每一个节点存储的数据量比较大,所以整个B-树的层数是非常低的,基本上不超过三层。

由于磁盘的读取也是按block块操作的(内存是按page页面操作的,一般是16k,是内存页面的整数倍,读1块,刚好放到4个内存页面上),因此B-树的节点大小一般设置为和磁盘块大小一致,这样一个B-树节点,就可以通过一次磁盘I/O把一个磁盘块的数据全部存储下来,所以当使用B-树存储索引的时候,磁盘I/O的操作次数是最少的(MySQL的读写效率,主要集中在磁盘I/O上)
数据和索引都是放在磁盘上的,MySQL server不可能直接从磁盘上读取数据,得通过操作系统把磁盘上的数据加载到内存中,也就是说,运行起来的进程要访问索引,需要花费磁盘I/O,先把数据,索引读到内存当中,这个磁盘I/O当然是少的好,磁盘I/O影响效率。

在我们的C/C++语言中,如果我们new/malloc向内存申请4个字节,实际上不可能只拿4个字节,内存管理是按页面4K为大小单位的,操作系统相当于批发站,它批发内存是以页面为单位的,我们申请4个字节,实际上我们向内核kernel申请,内核是按页面给我们分配的。按页面分配以后,但是我们的应用程序只需要4个字节,还剩下的字节就被libc.so或者 libc++.so库的ptmalloc(缓存),tcmalloc来管理,相当于2个函数,管理剩下的空闲的字节,等你下次还malloc申请字节的时候,就不用向内核空间申请,直接在用户空间,从c库分配出来就可以了。等用光了,才向内核申请。

假设有2千万的数据,要进行读取,如果是平衡的二叉树,一个节点1个数据,2个指针,二叉平衡树构建2千万的数据,树有多少层?log2000w,

mysql B树索引 唯一索引 mysql索引b+树原理_mysql


25层。

mysql B树索引 唯一索引 mysql索引b+树原理_数据库_02

在最坏的情况下,读取一个索引,要花费25次磁盘I/O

B-树

mysql B树索引 唯一索引 mysql索引b+树原理_mysql B树索引 唯一索引_03


涉及到磁盘到内存的一些读取,我们一般都采用B-树结构。

首先,B-树是平衡树,所有叶子节点都在一层,AVL树是平衡二叉树(左右子树高度差不会超过1),搜索的时间复杂度是O(logn),二分搜索也是。

我们称B-树是m阶平衡树,一般取值300-500。
如果我们用B-树构建数据结构来存储2千万的索引,假如m取500,

mysql B树索引 唯一索引 mysql索引b+树原理_数据库_04


最多3层,最多3次磁盘I/O就可以了

最好的情况是一次磁盘I/O读取的磁盘块的内容,刚好存储在B树的第1个节点中。我们以下面这张表格为例:

mysql B树索引 唯一索引 mysql索引b+树原理_mysql_05

我们把uid设置成了主键,主键会自动创建索引,

当我们进行一些查询操作的时候,

mysql B树索引 唯一索引 mysql索引b+树原理_mysql B树索引 唯一索引_06


一看uid有索引,请求存储引擎,请求kernel,花费磁盘I/O从磁盘上读索引文件到内存上,然后拿读取的索引的数据构建B-树来加速搜索。

关于操作系统从磁盘读取索引文件到内存中的疑问解答

在这里进行一些解释:
1、索引文件在磁盘上存储,磁盘的索引文件中的索引就是已经按B+树构建好的了?
那肯定不是哦,磁盘上只是存储的二进制文件,读取索引文件的时候,在内存上构建一颗B+树存放磁盘上读来的索引数据
数据结构都是在内存上表示的,没有说磁盘上构建个数据结构。

2、那操作系统把磁盘的索引文件读到内存上构建B+树,如果磁盘的索引文件太大,内存读不下怎么办?那磁盘IO怎么算次数,现在不是都在内存上的B+树搜索读取数据了吗?
肯定不是读整个文件的,平衡树怎么搜索的?
先读索引文件的前几个字节,里面有第一个要读取的根节点数据在索引文件中的偏移量,读一个根节点后,根据你要搜索的数据进行搜索,看是接着加载他的哪个孩子节点。包括根节点的每一个节点,都存储了索引key值和它的孩子节点在磁盘上的位置偏移量信息的。
这样每一次搜索,最多只从根节点沿着某个路径加载到叶子节点上,怎么可能整个索引文件都加载呢???

总结:索引文件在磁盘上是二进制的,但是文件中存储了根节点的key值和这个节点的整个的偏移量,还存储的它的左右孩子的key值和整个节点的偏移量。操作系统从磁盘的索引文件中,一次读取一个块的大小,也就是一个节点到内存中构建B+树,然后在节点中二分搜索元素,如果发现值大于根节点的所有数据值,那么就继续从磁盘的索引文件中把该节点的右孩子节点加载到内存上,然后进行二分搜索查找,以此类推下去。

回到刚才的主题上

key是建索引的列

mysql B树索引 唯一索引 mysql索引b+树原理_mysql B树索引 唯一索引_03


17, 35 , 8,12等紫色的这些数字是key值,左孩子的值比父节点的值小,父节点的值比右孩子的值小,这是平衡树的概念

黄色的data表示key索引所在的这一行的数据,data存储的是数据本身内容,还是数据在磁盘上的地址

这得看我们使用哪种存储引擎如果我们用的是MyISAM存储引擎,索引和数据是在不同的文件存储,上图这构建的是索引树,存储的自然是在磁盘的地址了,通过索引值在磁盘上找到包含这个索引值的记录,然后通过地址在磁盘上读取数据文件中相应的数据。

如果我们用的是InnoDB存储引擎,数据和索引是在一块放着的,索引树上放的就是数据,没有索引树,数据没地方放。即使没有加主键,也会自动创建主键和索引。这样一来,在搜索的时候,比如我们要搜索28,但是一个B树节点里面的数据很多,不止在图中画的17和35两个节点,m取值是300-500。如果m是取500的B树,相当于一个节点有500个指针域,指向500个孩子,有499个数据域。在平衡树中,在一个节点里,里面的数据也是从小到大排序的。在一个节点里,有很多数据域,当我们想要去找28时,在一个节点里面怎么搜?肯定是二分搜索。 时间复杂度:O(logn)

选择B树结构的好处是:非常少的磁盘I/O啊

mysql B树索引 唯一索引 mysql索引b+树原理_mysql B树索引 唯一索引_06


如果uid没有索引的话,搜索的时候,是整张表一行一行的搜索下去,不在索引树上搜索了。再举个例子:

mysql B树索引 唯一索引 mysql索引b+树原理_mysql B树索引 唯一索引_09


如果name没有索引的话,如果在MyISAM中,就是把整张表过1遍,效率非常低。加个limit 1,效率到是提高一些。

如果我们给name建1个索引的话,当我们再去执行这个select语句的时候,MySQL server的分析器就知道name有索引,直接加载name的索引文件,花费磁盘I/O,把磁盘上的数据加载到内存中来,构建成B-树。name就是相当于上图中的紫色数据。然后在加载到内存的节点里进行二分搜索。

从上图可以看到B-树存在的缺点:
每个节点中有key,也有data域,但是每一个节点的存储空间是有限的,如果data数据较大时会导致每个节点能存储的key的数据很小。
当存储的数据量很大时同样会导致B-树的高度较大,磁盘IO次数花费增大,效率降低!!!

B+树

mysql B树索引 唯一索引 mysql索引b+树原理_数据结构_10


1、每一个非节点只存放key,不存放data,这样的好处是一个节点存放的key值更多,这样B+树在理论上来说,层数更低一些,磁盘I/O次数少,搜索的效率更好一些。

2、数据都存储在叶子节点上。我们发现,叶子节点上存储了所有的索引值和其对应的data。直接的好处就是搜索每一个索引对应的值data都需要跑到叶子节点上,这样,每一行记录搜索的时间是非常均匀的。

3、叶子节点被串在一个链表当中,形成了一个有序的链表。如果要进行索引树的搜索或者是整表搜索,直接遍历叶子节点的有序链表即可。或者做范围查询的时候,直接遍历叶子节点的有序链表即可。

mysql B树索引 唯一索引 mysql索引b+树原理_数据库_11


都是从小到大的,直接遍历有序链表是很方便的。

MySQL最终为什么要采用B+树存储索引结构呢?

(MyISAM和InnoDB)
那么看看B-树和B+树在存储结构上有什么不同?

  1. B-树的每一个节点,都存了关键字和对应的数据地址,而B+树的非叶子节点只存关键字,不存数据地址。节点的大小是一个块的大小,因此B+树的每一个非叶子节点存储的关键字是远远多于B-树的,B+树的叶子节点存放关键字和数据,因此,从树的高度上来说,B+树的高度要小于B-树,使用的磁盘I/O次数少,因此查询会更快一些。
  2. B-树由于每个节点都存储关键字和数据,关键字是不重复的,因此离根节点进的数据,查询的就快,离根节点远的数据,查询的就慢,花费的磁盘I/O次数不均匀,每一行数据搜索花费的时间也不均匀;B+树所有的数据都存在叶子节点上,因此在B+树上搜索关键字,找到对应数据的时间是比较平均的,没有快慢之分。
  3. 在B-树上如果做区间查找,遍历的节点是非常多的,非常麻烦,遍历左右子树;B+树所有叶子节点被连接成了有序链表结构,因此做整表遍历和区间查找是非常容易的。

从一张表进行搜索的话,如果我加了过滤条件,

mysql B树索引 唯一索引 mysql索引b+树原理_数据结构_12


MySQL先检查有没有索引,如果没有索引的话,就做整表搜索,效率是非常低的,如果有索引的话,操作系统会给我们从磁盘上的索引文件加载到内存上,用B+树来构建,一个节点刚好对应1个磁盘I/O,非叶子节点存的都是key,所有的key和data都是存储在叶子节点上,花费最少的磁盘I/O次数,以logn的时间复杂度,来找见索引对应的数据。B-树的特点:

mysql B树索引 唯一索引 mysql索引b+树原理_数据结构_13