14 MVCC的可见性

来源:互联网 发布:2016年网络经典口头禅 编辑:程序博客网 时间:2024/06/08 14:06

转自http://hedengcheng.com/?p=148 何登成

一、准备

create table test (id int primary key, comment char(50)) engine=innodb;

create index test_idx on test(comment);

insert into test values (1, ‘aaa’);

insert into test values (2, ‘bbb’);


更新主键update test set id = 9 where id = 1;

更新非主键update test set comment = ‘ccc’ where id = 9;


总结:
a、无论是聚簇索引,还是二级索引,只要其键值更新,就会产生新版本。将老版本数据deleted bti设置为1;同时插入新版本。
b、对于聚簇索引,如果更新操作没有更新primary key,那么更新不会产生新版本,而是在原有版本上进行更新,老版本进入undo表空间,通过记录上的undo指针进行回滚。
c、对于二级索引,如果更新操作没有更新其键值,那么二级索引记录保持不变。
d、对于二级索引,更新操作无论更新primary key,或者是二级索引键值,都会导致二级索引产生新版本数据。
e、聚簇索引设置记录deleted bit时,会同时更新DATA_TRX_ID列。老版本DATA_TRX_ID进入undo表空间;二级索引设置deleted bit时,不写入undo。


二、可见性判断

A、主键查找
1、select * from test where id = 1;
针对测试1,如果1811(DATA_TRX_ID) < read_view.up_limit_id,证明被标记为删除的记录1可见。删除可见 -> 无记录返回。
针对测试1,如果1811(DATA_TRX_ID) >= read_view.low_limit_id,证明被标记为删除的记录1不可见,通过DATA_ROLL_PTR回滚记录,得到DATA_TRX_ID = 1809。如果1809可见,则返回记录(1,aaa);否则无记录返回。
针对测试1,如果up_limit_id,low_limit_id都无法判断可见性,那么遍历read_view中的trx_ids,依次对比事务id,如果在DATA_TRX_ID在trx_ids数组中,则不可见(更新未提交)。


2、select * from test where id = 9;
针对测试2,如果1816可见,返回(9,ccc)。
针对测试2,如果1816不可见,通过DATA_ROLL_PTR回滚到1811,如果1811可见,返回(9, aaa)。
针对测试2,如果1811不可见,无结果返回。

3、select * from test where id > 0;
针对测试1,索引中,满足条件的同一记录,有两个版本(版本1,delete bit =1)。那么是否会一条记录返回两次呢?必定不会,这是因为pk = 1的可见性与pk = 9的可见性是一致的,同时pk = 1是标记了deleted bit的版本。如果事务ID = 1811可见。那么pk = 1 delete可见,无记录返回,pk = 9返回记录;如果1811不可见,回滚到1809可见,那么pk = 1返回记录,pk = 9回滚后无记录。
 
总结:
a、通过主键查找记录,需要配合read_view,记录DATA_TRX_ID,记录DATA_ROLL_PTR指针共同判断。
b、read_view用于判断当前记录是否可见(判断DATA_TRX_ID)。DATA_ROLL_PTR用于将当前记录回滚到前一版本。

B、非主键查找
1、select comment from test where comment > ‘ ‘;
针对测试2,二级索引,当前页面的最大更新事务MAX_TRX_ID = 1816。如果MAX_TRX_ID < read_view.up_limit_id,当前页面所有数据均可见,本页面可以进行索引覆盖性扫描。丢弃所有deleted bit = 1的记录,返回deleted bit = 0 的记录;此时返回 (ccc)。(row_select_for_mysql -> lock_sec_rec_cons_read_sees)
针对测试2,二级索引,如果当前页面不能满足MAX_TRX_ID < read_view.up_limit_id,说明当前页面无法进行索引覆盖性扫描,此时需要针对每一项,到聚簇索引中判断可见性。回到测试2,二级索引中有两项pk = 9 (一项deleted bit = 1,另一个为0),对应的聚簇索引中只有一项pk= 9。如何保证通过二级索引过来的同一记录的多个版本,在聚簇索引中最多只能被返回一次?如果当前事务id 1811可见。二级索引pk = 9的记录(两项),通过聚簇索引的undo,都定位到了同一记录项。此时,InnoDB通过以下的一个表达式,来保证来自二级索引,指向同一聚簇索引记录的多个版本项,有且最多仅有一个版本将会返回数据:
if (clust_rec
&& (old_vers || rec_get_deleted_flag(
                      rec,dict_table_is_comp(sec_index->table)))
         && !row_sel_sec_rec_is_for_clust_rec(rec, sec_index, clust_rec, clust_index))

满足if判断的所有聚簇索引记录,都直接丢弃,以上判断的逻辑如下:
a、需要回聚簇索引扫描,并且获得记录
b、聚簇索引记录为回滚版本,或者二级索引中的记录为删除版本
c、聚簇索引项,与二级索引项,其键值并不相等
为什么满足if判断,就可以直接丢弃数据?用白话来说,就是我们通过二级索引记录,定位聚簇索引记录,定位之后,还需要再次检查聚簇索引记录是否仍旧是我在二级索引中看到的记录。如果不是,则直接丢弃;如果是,则返回。

根据此条件,结合查询与测试2中的索引结构。可见版本为事务1811.二级索引中的两项pk = 9都能通过聚簇索引回滚到1811版本。但是,二级索引记录(ccc,9)与聚簇索引回滚后的版本(aaa,9)不一致,直接丢弃。只有二级索引记录(aaa,9)保持一致,直接返回。

总结:
a、二级索引的多版本可见性判断,需要通过聚簇索引完成。
b、二级索引页面中保存了MAX_TRX_ID,可以快速判断当前页面中,是否所有项均可见,可以实现二级索引页面级别的索引覆盖扫描。一般而言,此判断是满足条件的,保证了索引覆盖扫描 (index only scan)的高效性。
c、二级索引中的项,需要与聚簇索引中的可见性进行比较,保证聚簇索引中的可见项,与二级索引中的项数据一致。


备注:相关结构


关于readview:

low_limit_no
• 提交时间早于此值(trx->no < low_limit_no)的事务,可以被purge线程回收

low_limit_id
• >= 此值(trx->id >= low_limit_id)的事务,当前ReadView均不可见

up_limit_id
• < 此值(trx->id < up_limit_id)的事务,当前ReadView一定可见
• up_limit_id = ReadView创建时系统中最小活跃事务ID(一般的,low_limit_id>up_limit_id

trx_ids[]
• 系统中所有活跃事务id组成的数组

0 0
原创粉丝点击