mysql覆盖索引之看山还是山

497 阅读4分钟

前置知识 ReadView/MVCC 请看另一篇博客:juejin.cn/post/684490…

覆盖索引定义

如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。

看山是山

覆盖索引,快不快,当然快,只用查询二级索引就得到了所需数据,少了一步回表操作,当然快。

看山不是山

这几天学了mvcc的知识,知道了对记录更改需要生成undo日志,构建版本链之类的,那么问题来了,版本链是在聚簇索引,怎么在二级索引查看/更改版本链呢?那不就是要回表?

矛盾:

1:覆盖索引不回表,那么覆盖索引查询无法维护版本链
2:覆盖索引回表,那么覆盖索引还快吗?就不快了呀,但是博客、面试,大家不都是异口同声的说覆盖索引快?

我的解决思路

既要覆盖索引快,又要能通过二级索引维护版本链,那么可能就是查询的过程可能可以细分一下,具体有两个思路:
1:二级索引查询到数据,返回数据给客户端,然后去聚簇索引维护版本链
2:维护版本链是个异步的过程(其实想想就知道很不靠谱)

看山还是山

咨询大佬之后学到了mysql的做法,在二级索引的页中,维护这一个记录了增删改操作的事务的ID,SELECT生成ReadView时,判断最小活跃事务ID是否大于页面的增删改最大事务ID,如果大于,说明页面没有更改过,不用回表,否则就得回表。
回表的时候,就跟之前分析过的mvcc流程一致了,不赘述了。

对比一下聚簇索引中的ReadView判断流程和二级索引中的ReadView判断流程

聚簇索引:

1:如果被访问记录的trx_id与creator_trx_id相同,即当前事务在访问它自己修改过的记录,可见。
2:记录的trx_id小于min_trx_id值,表示生成该版本的事务已经提交,可见。
3:记录的trx_id大于max_trx_id值,代表生成该版本的事务在当前事务之后才开启,不可见。
4:min_trx_id < trx_id < max_trx_id,判断trx_id是否在m_ids中,如果在,说明创建ReadView时(注意这里是ReadView,而不是trx_id)该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经提交,该版本可以被访问。

二级索引:

在二级索引的页中,维护这一个记录了增删改操作最大事务的ID,SELECT生成ReadView时,判断最小活跃事务ID是否大于页面的增删改最大事务ID,如果大于,说明页面没有更改过,可见,否则不可见。

区别

聚簇索引中,如果大于min_trx_id,则还要判断是否在m_ids中,而二级索引不判断了,回表判断
为什么?
因为二级索引的页中只保存了一个值,假如它
1:> min_trx_id,在m_ids中,说明该事务还是活跃的,不可访问。要回表去聚簇索引拿版本链。 2:> min_trx_id,不在m_ids中,说明该事务已经提交。那么可以说该版本可以被访问吗? 按我的推算是可以的,虽然mysql没有这么做。

总结

所以,相对我的两个写死流程的解决办法,mysql其实是做了一种平均值上缩减访问时间的做法。 从mysql的很多设计看出,mysql很多地方都用了优化,往往不是异步,因为异步会带来更多问题,是同步,但是对能优化的部分的流程进行了优化,不能优化的部分就不优化。
所以,覆盖索引快吗?确实快,但是不是查每一行记录都快,而是平均值意义上的快,具体来看,有些行记录查询变快了,有些没有,但是整体上来说,就是变快了。
面试的时候,问覆盖索引为什么快?因为不用回表,这个答案没毛病,节省的不就是那部分不用回表的记录的回表时间吗?没毛病。

PS:至于二级索引页的“记录了增删改操作的事务的ID”,二级索引页和buffer pool的矛盾,我已经不想再深究了。。。。。。