索引的底层实现原理
数据库索引是存储在磁盘上的; 当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘块 (对应索引树的节点),索引树越低;
越“矮胖”——>磁盘IO次数就少 xxx有索引==》 存储引擎==》kernel==》磁盘IO(读索引文件)==》内存上
MySQL支持两种索引:
大家知道,B-树和哈希表在数据查询时的效率是非常高的。
这里我们主要讨论一下MySQL InnoDB存储引擎,基于B-树(但实际上MySQL采用的是B+树结构 )的索引结构。
B-树是一种m阶平衡树,叶子节点都在同一层 ,由于每一个节点存储的数据量比较大,索引整个B-树的层数是非常低的,基本上不超过三层。
- 题外话:内存是按
page 页面(4k为单位)操作的,当我们向操作系统申请4byte时,实际上内核按照页面给我们分配,在这之后,假设返回了两个页面即2x 4k = 8k,应用程序只需要4个,剩下的就被c库(libc.so 或者libc++.so )的malloc实现ptmalloc 或者tcmalloc 来管理。这两个函数(或者说缓存用来管理多余的空间),这样做的好处就是该应用程序下一次malloc 或者new 时,就不要陷入kernal内核空间去申请内存,下次直接在用户空间中使用,效率更高。 - 由于磁盘的读取也是按block块操作(一般是16k,也是内存页面的整数倍) ,因此
B-树的节点大小一般设置为和磁盘块大小一致 ,这样一个B-树节点,就可以通过一次磁盘I/O把一个磁盘块的数据全部存储下来,所以当使用B-树存储索引的时候,磁盘I/O的操作次数是最少的(MySQL的读写效率,主要集中在磁盘I/O上)。
上面的内容可能不好理解,我们通过一个示例理解一下:假设我们有2000w的数据:
- 使用AVL树存储,构建下来有log220000000 ≈ 25层,最坏情况下读取一个索引要花费25次磁盘IO
- 使用B树存储,m阶平衡树(m一般取300-500),假设m= 500,log1020000000 / log10500≈ 3层,这种情况下最多花费三次磁盘IO
建立索引的过程如下:
- 当执行select语句时发现,where条件列uid是已经建立好索引的;
- 于是请求存储引擎,请求内核;
- 磁盘IO读取索引文件,读取到内存上;
- 用读取到的数据构建B树,从而加速搜索。
注意 :这里的data是存储的是数据本身的内容,还是数据在磁盘上的地址呢? ——这与存储引擎相关,
- 如果是
MYISAM ,索引和数据是分开存储的(想想之前学过的内容),构建的索引树,一定是数据的地址; - 要是用
InnoDB ,数据跟索引一起存放,索引树上放的是数据
注意 :如果在InnoDB存储引擎下,where后的字段名没有建立索引,但是由于InnoDB存储引擎会自动建立索引,但是搜索条件并未和索引列建立关系,那么我们就会遍历整个索引树,也就相当于整表搜索。
根根据B树的结构特点(排序关系),最终采取的搜索策略是二分搜索,时间复杂度是(O(log2n) ),那么如果我们使用AVL树,不同样也是(O(log2n) )吗?
搜索的过程分为两步:
- 花费磁盘IO把数据构建到内存的索引树上
- 再在索引树上搜索数据
即便搜索这个过程的时间是一样的,但B树的好处就是非常少的磁盘IO,在这方面提升了效率,这在之前我们已经提及了。
从上图可以看到B-树存在的缺点:
- 每个节点中有key,也有data,但是每一个节点的存储空间是有限的,如果data数据较大时会导致每个节点能存储的key的数据很小
- 当存储的数据量很大时同样会导致B-树的高度较大,磁盘IO次数花费增大,效率降低
|