在前面的几篇文章中,我们对MySQL InnoDB引擎进行了详尽的介绍,请参见:
闲聊MySQL:(六)深入分析InnoDB之锁类型
闲聊MySQL:(五)深入分析InnoDB之硬盘存储架构
闲聊MySQL:(四)深入分析InnoDB之内存架构
本篇,我们继续聊InnoDB,深入了解一下InnoDB中非常重要的索引的背后实现。
如果您对InnoDB的索引机制已经非常的了解,那么请您思考这样一个问题,InnoDB一棵B+树可以存放多少行数据?看完本篇,将会为您揭晓答案。
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
就像我们想在新华字典去查找一个字,我们会在目录页寻找这个字的拼音首字母或者偏旁部首,以此来定位这个字在字典中的所在位置,这就是典型的索引。
MySQL的索引,也是用来做这件事的,只不过是来定位一行数据所在的具体位置。
数据库查询是数据库的最主要功能之一。但每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织),所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。
这种数据结构,就是索引。
在之前的文章中,我们介绍过InnoDB的数据物理存储结构,我们再来复习一下:
在InnoDB中,表数据组织方式是主键聚簇索引。二级索引通过索引键值加主键值组合来唯一确定一条记录。一个表只能有一个主键,所以只能有一个聚簇索引,如果表没有定义主键,则选择第一个非NULL唯一索引作为聚簇索引,如果还没有则生成一个隐藏ID列作为聚簇索引。
所以,我们也可以换句话来讲,InnoDB中的数据是面向主键索引进行数据存储的。
索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。
InnoDB引擎支持以下几种常见的索引:
在前面的文章介绍InnoDB的内部结构时,我们介绍过hash索引,InnoDB支持的hash索引是自适应的,InnoDB引擎会根据表的使用情况自动为表生成hash索引,不能人为创建hash索引。
B+Tree索引就是我们一般意义上来讲的索引,是目前关系型数据库中查找最为常用和最高效的索引之一,下面,我们着重来看一下B+Tree的实现原理。
B+Tree是一种非常经典的数据结构,网上也有大量关于B+Tree数据结构的介绍,但是在这里,我不会对B+Tree的数据结构进行详细介绍,因为其定义非常的繁琐复杂,我们用简单些的方式,来理解一下B+Tree:
我们来看一棵典型的B+Tree结构:
在上面我们简单介绍了B+Tree的数据结构,在InnoDB中,索引的数据结构,也是基于如此。但是B+Tree索引在数据库中有一个特点是高扇出性,因此在数据库中,B+Tree的高度一般都在2-4层,这也就是说查找某一键值的行记录时最多只需要2到4次的磁盘I/O,这是非常不错的。因为当前一般的机械磁盘每秒可以做到至少100次I/O,2-4次的I/O意味着查询时间只需要0.02~0.04秒。
而在InnoDB中,B+Tree索引又可以分为聚簇索引与辅助索引,我们来分别看一下它们的区别。
聚簇索引
聚簇索引其实就是主键索引,上面我们讲过,InnoDB中的数据是面向主键索引进行数据存储的。而聚簇索引就是按照每张表的主键来构造一棵B+Tree,同时叶子节点中存储的是整张表的行记录信息,也可以将聚簇索引的叶子节点称为数据页。
因此,聚簇索引的这个特性,决定了索引组织表中的数据也是索引的一部分。和B+Tree的数据结构一样,每个数据页都通过一个双向的链表来进行链接。
我们来看一下聚簇索引的模型图:
由于实际的数据页只可以按照一棵B+Tree进行排序,因此每张表只能拥有一个聚簇索引。在大多数的情况下,查询优化器倾向于采用聚簇索引,因为聚簇索引能够在B+Tree的叶子节点上直接找到数据。除此之外,由于定义了数据的逻辑顺序,聚簇索引能够特别快的访问针对范围值的查询。查询优化器能够快速发现某一段范围的数据页需要扫描。
需要着重注意的是,在InnoDB的B+Tree索引数据结构中,只有在数据页(即叶子节点)上存放的是完整的每行记录,而在非数据页的索引页中,存放的仅仅是键值及指向数据页的偏移量,而不是一个完整的行记录。
同时,从物理存储结构上来看,聚簇索引的存储并不是连续的,而是逻辑上连续的。
这其中有两点:
1、叶子节点是通过双向链表进行链接的,页按照主键的顺序排序;
2、每个页中的记录也是通过双向链表进行维护的,物理存储上可以同样不按照主键存储。
辅助索引
对于辅助索引,指的是除主键索引之外的索引类型,也可以叫非聚簇索引(或非主键索引),它与聚簇不同的是,其叶子节点不包含记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含了一个书签。该书签用来告诉InnoDB引擎哪里可以找到与索引对应的行数据。
由于InnoDB引擎表是索引组织表,因此InnoDB引擎的辅助索引的书签就是相应行数据的聚簇索引键。
在同一张表中,辅助索引的存在与聚簇索引并不冲突,因此同一张表可以有多个辅助索引。当通过辅助索引来寻找数据时,InnoDB引擎会遍历索引并通过叶级别的指针获得指向聚簇索引的主键,然后再通过聚簇索引来找到具体的记录行。
举个栗子:
如果在一棵高度为3的辅助索引树中查找数据,那么需要对这棵辅助索引树遍历3次,找到指定主键,如果聚簇索引的高度同样为3,那么还需要对聚簇索引树进行3次查找,找到具体的行数据所在的记录页。因此一共需要6次逻辑I/O访问得到最终的一个数据页。
在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的(例如树结构的选型和查找算法的实现),上面我们介绍了InnoDB的索引实现,下面我们来对比一下InnoDB的索引与MyISAM的索引的实现的区别。
1、数据存储方式不同
MyISAM的索引文件, 叶节点的data域存放的是数据记录的地址。
而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是聚簇索引。
2、聚簇索引与辅助索引不同
MyISAM聚簇索引和辅助索引在结构上没有任何区别,只是聚簇索引要求key是唯一的,而辅助索引的key可以重复。
InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。
3、聚簇索引是否必须
在MyISAM中,聚簇索引并非必须的,表中可以没有聚簇索引。
而InnoDB中,因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键。
上面我们对MySQL的索引机制进行了介绍和了解,明白了MySQL的索引是基于B+Tree的数据结构进行数据存储的,那么回到文章开头的问题,InnoDB一棵B+Tree可以存放多少行数据呢?
您可能第一反应到,应该是千万级别吧?因为MySQL单表达到千万级别后,性能会极具下降。是的,没有问题,是千万级,下面我们就来一起算一下,这个千万的数,是怎么得来的。
我们在之前的文章中介绍InnoDB的硬盘存储架构的章节,提到过,InnoDB中,每一行数据是存储在数据页(Page)中的,而页存储在区(Extent)中,层层存储。
默认的话,一个数据页的大小是16K。
数据表中的数据都是存储在页中的,所以一个页中能存储多少行数据呢?假设一行数据的大小是1k,那么一个页可以存放16行这样的数据。
如果数据库只按这样的方式存储,那么如何查找数据就成为一个问题,因为我们不知道要查找的数据存在哪个页中,也不可能把所有的页遍历一遍,那样太慢了。所以人们想了一个办法,用B+Tree的方式组织这些数据。也就是我们上面讲过的索引的数据结构。(前人还是蛮聪明的哈!)
在InnoDB中,每个叶子节点,存储了具体的数据页,那么这里我们先假设B+树高为2,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数*单个叶子节点记录行数。
那么单个叶子节点(数据页)中的记录数=16K/1K=16。(这里假设一行记录的数据大小为1k,实际上现在很多互联网业务数据记录大小通常就是1K左右)。
那么现在我们需要计算出非叶子节点能存放多少指针,其实这也很好算,我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针,即(16 * 1024)/ 14 = 1170。那么可以算出一棵高度为2的B+Tree,能存放1170 * 16=18720条这样的数据记录。
根据同样的原理我们可以算出一个高度为3的B+树可以存放:1170 * 1170 * 16=21902400条这样的记录。所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次I/O操作即可查找到数据。
由此,我们得到结论,一棵B+Tree可以存放千万级别的数据。
通篇我们都在讲索引的数据结构是B+Tree,那么你肯定也有疑问,为什么是B+Tree?而不是别的数据结构,或者其他的树型数据结构呢?
关于使用树型的数据结构,想必您如果对数据结构稍微有了解的话,就可以知道树型结构的好处,由于采用了二分法查找方式,而且通过算法保证树的平衡性,因此使用树型的数据结构可以保证查询的高效性。
那为什么是B+Tree?而不是B-Tree呢?
我们用简单的一句话来回答一下,因为B-Tree不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致I/O操作变多,查询性能变低。
本篇,我们介绍了MySQL的索引的实现机制,主要围绕着InnoDB引擎的索引实现进行了介绍,可以用简单几点进行总结一下:
1、InnoDB中的数据是面向主键索引进行数据存储的,InnoDB的索引是使用B+Tree的数据结构实现的;
2、聚簇索引的非叶子节点,只存储指针,数据页存储在叶子节点,非聚簇索引的叶子节点只存储指向主键的指针,具体数据的查找需要二次遍历聚簇索引树;
3、InnoDB中一棵索引B+Tree可以存放千万级别的数据行。
关于InnoDB的索引介绍,关于InnoDB的相关介绍,就先到这里,下一篇,我们聊一聊MySQL的查询优化,敬请期待!
更多精彩文章, 请关注我的个人公众号:老宣说
让我们一起共同学习成长!
感谢您的支持!