前置知识 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的矛盾,我已经不想再深究了。。。。。。