说到索引大家都不陌生,到底什么是索引,索引又是如何工作的?
索引是应用程序设计和开发的一个重要方面。若索引太多,应用程序的性能可能会受到影响。而索引太少,对查询性能又会产生影响。要找到一个合适的平衡点,这对应用程序的性能至关重要。
B+索引是最为常见,也是在数据库中使用最为频繁的一种索引。
什么是索引
一句话简单来说,索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。对于数据库的表而言,索引其实就是它的“目录”。
在MySQL中,索引是在存储引擎层实现的,所以并没有统一的索引标准,即不同存储引擎的索引的工作方式并不一样。而即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。
在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。InnoDB使用了B+树索引模型,所以数据都是存储在B+树中的。
每一个索引在InnoDB里面对应一棵B+树。
B+树索引
B+树索引的本质就是B+树在数据库中的实现。
但是B+索引在数据库中有一个特点是高扇出性,因此在数据库中,B+树的高度一般都在2-4层,这也就是说查找某一键值的行记录时最多只需要2到4次IO,这倒不错。因为当前一般的机械磁盘每秒至少可以做100次IO,2-4次的IO意味着查询时间只需要0.02-0.04秒。
数据库中的B+树索引可以分为聚集索引和辅助索引,但是不管是聚集还是辅助索引,其内部都是B+树的,即高度平衡的,叶子节点存放着所有的数据。
聚集索引与辅助索引不同的是,叶子节点存放的是否是一整行的信息。
B+树的插入操作
B+树是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行连接。
先来看一个B+树,其高度为2,每页可存放4条记录,扇出(fan out)为5,从图5-6中可以看出,所有记录都在叶子节点上,并且是顺序存放的,如果用户从最左边的叶子节点开始顺序遍历,可以得到所有的键值的顺序排序:5、10、15、20、25、30、50、55、60、65、75、80、85、90。
B+树的插入必须保证插入后叶子节点中的记录依然排序,同时需要考虑插入到B+树的三种情况,每种情况都可能会导致不同的插入算法。
如上图5-6的B+树,若插入28这个键值,发现当前Leaf Page和Index Page都没有满,直接插入即可,如下图
接着再插入70这个键值,这时原先的Leaf Page已经满了,但是Index Page还没有满,符合第二种情况,这时插入Leaf Page后的情况为55、55、60、65、70,并根据中间的值60来拆分叶子节点,可得图5-8。
最后插入键值95,这时符合第三种情况,即Leaf Page和Index Page都满了,这时需要做两次拆分,如图5-9
可以看到,不管怎么变化,B+树总数会保持平衡。
但是为了保持平衡对于新插入的键值可能需要做大量的拆分页(split)操作。
因为B+树结构主要用于磁盘,页的拆分意味着磁盘的操作,所以应该在可能的情况下尽量减少页的拆分操作。
因此,B+树同样提供了类似于平衡二叉树的旋转(Rotation)功能。
旋转发生在Leaf Page已经满,但是其的左右兄弟节点没有满的情况下。这时B+树并不会急于去做拆分页的操作,而是将记录移到所在页的兄弟节点上。
在通常情况下,左兄弟会被首先检查用来做旋转操作,因此再来看图,若插入键值70,其实B+树并不会急于去拆分叶子节点,而是去做旋转操作,得到如图5-10所示的操作。
采用旋转操作使B+树减少了一次页的拆分操作,同时这棵B+树的高度依然还是2。
B+树的删除操作
B+树使用填充因子(fill factor)来控制树的删除变化,50%是填充因子可设的最小值。
B+树的删除操作同样必须保证删除后叶子节点中的记录依然排序。
同插入一样,B+树的删除操作同样需要考虑以下的三种情况,与插入不同的是,删除根据填充因子的变化来衡量。
根据图5-9的B+树来进行删除操作。首先删除键值为70的这条记录,该记录符合第一种情况,删除后可得到图5-11。
接着删除键值为25的记录,这也是第二种情况,但是该值还是Index Page中的值,因此只删除Leaf Page中的25后,还应将25的右兄弟节点的28
更新Page Index中,最后可得图5-12。
最后看删除键值为60的情况。删除Leaf Page中键值为60的记录后,Fill Factor小于50%,这时需要做合并操作,同样,在删除Index Page中相关
记录后需要做Index Page的合并操作,最后得到图5-13。
聚集索引
聚集索引就是按照每张表的主键构造一棵B+树,同时叶子节点中存放的即为整张表的行记录数据,也将聚集索引的叶子节点称为数据页。
聚集索引的这个特性决定了索引组织表中数据也是索引的一部分。
同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。
由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。
在多数情况下,查询优化器倾向于采用聚集索引。
因为聚集索引能够在B+树索引的叶子节点上直接找到数据。
此外,由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值的查询。
查询优化器能够快速发现某一段范围的数据页需要扫描。
许多数据库的文档会这样告诉读者:聚集索引按照顺序物理地存储数据。
试想一下,如果聚集索引必须按照特定顺序存放物理记录,则维护成本显得非常之高。
所以,聚集索引的存储并不是物理上连续的,而是逻辑上连续的。
这其中有两点:一是前面说过的页通过双向链表链接,页按照主键的顺序排序;另一点是每个页中的记录也是通过双向链表进行维护的,物理存储上可以同样不按照主键存储。
聚集索引的另一个好处是:他对主键的排序查找和范围查找速度非常快。叶子节点的数据就是用户所要查询的数据。
辅助索引
对于辅助索引(Secondary Index,也称非聚集索引),叶子节点不是包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含了一个书签(bookmark)。
该书签用来告诉InnoDB存储引擎哪里可以找到与索引相对应的行数据。
由于InnoDB存储引擎表是索引组织表,因此InnoDB存储引擎的辅助索引的书签就是相对应行数据的聚集索引键。
辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引。
当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。
回到主键索引树搜索的过程,我们称为回表。
举例来说:如果在一棵高度为3的辅助索引中查找数据,那需要对这棵辅助索引树遍历3次找到指定主键,如果聚集索引树的高度同样为3,那么还需要对
聚集索引树进行3次查找,最终找到一个完整的行数据所在的页,因此一共需要6次逻辑IO访问以得到最终的一个数据页。
覆盖索引
由于查询结果所需要的数据只在主键索引上有,所以不得不回表。那么,有没有可能经过索引优化,避免回表过程呢?
InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。
使用覆盖索引的一个好处是辅助索引不包含整行记录的所有的信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。
比如我们创建一个name列的辅助索引,而我们只需要查询name。
如:select name from t where name="张三";
这时只需要查name的值,而name的值已经在name索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引name已经“覆盖了”我们的查询需求,我们称为覆盖索引。
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
Index Condition Pushdown(ICP)优化
Index Condition Pushdown是MySQL 5.6开始支持的一种根据索引进行查询的优化方式。
之前的MySQL数据库版本不支持Index Condition Pushdown,当进行索引查询时,首先根据索引来查找记录,然后再根据WHERE条件来过滤记录。
在支持Index Condition Pushdown后,MySQL数据库会取出索引的同时,判断是否可以进行WHERE条件过滤,也就是将WHERE的部分过滤操作放在了存储引擎层。
在某些查询下,可以大大减少上层SQL层对记录的索取(fetch),从而提高数据库的整体性能。
Index Condition Pushdown优化支持range、ref、eq_ref、ref_or_null类型的查询,当前支持MyISAM和InnoDB存储引擎。
当优化器旋转Index Condition Pushdown优化时,可在执行计划的列Extra看到Using index condition提示。
千锋重庆Java培训机构的课程采用100%全程面授教学,拒绝视频同步授课,拒绝双元视频班教学,拒绝直播授课,教师一对一指导学员做项目,全新打造“主流技术+前沿技术+企业级联动”教学课程,重新优化和定义JavaEE,采用最新版本技术开展教学,致力于为学员打造最牛的、最新的技术,助力学员拿下BAT级企业Offer。
选择千锋重庆Java培训,将带领你成功入门,走上Java开发工程师之路。