Innodb事务隔离级别及锁机制(二)

在innodb中,cr和rr隔离级别对于select操作都没有共享锁,而是使用多版本控制,即一致性非锁定读,为了保证并发下的数据安全问题,可以手动对select加锁实现一致性锁定读

一. MVCC在InnoDB存储引擎中的意义

要了解MVCC的实际作用,首先必须了解InnoDB中事务的隔离性:
1. Read Uncommitted:会出现脏读的情况。事务B可以读到未提交事务A所做的修改。
2. Read Committed:避免了脏读,但是会出现幻读。事务B可以看到事务A提交之后的修改,考虑这种情况:
事务B
事务A
select name from user where id = 1 ; 
name  = "libis";


prepare;
update user set name = "fxx" where id = 1;
commit;
select name from user where id = 1;
name = "fxx";

可见事务B在不同的时间点查询到的数据不一致。
3. Repeatable Read:避免了幻读。事务B只会看到自己这个事务内的记录版本,而对其他事务更新的记录版本是不可见的。MVCC实现了多版本机制,每个事务都会有一个版本。
4. Serializatable:事务必须严格的串行执行,在一定程度上会影响数据库性能。

二. 事务隔离性实现
 事务之间的隔离性是如何实现的呢?
1. 数据结构
事务的隔离性实现基于两个基本的数据结构,一个是行记录,一个是read_view。其中前者实现了MVCC,后者在其基础上实行了记录的可见性。
(1)首先来看下innodb的行结构,innodb中每行都有两个隐藏列:DATA_TRX_ID,DATA_ROLL_PTR。其中DATA_TRX_ID表示更新此条记录的最新事务ID,DATA_ROLL_PTR指向此条记录项的undo信息,可以通过这个指针找到之前的版本。对行记录的更新操作可以通过如下示例表示:
原有记录:

id:1

namedivid

age25

DATA_TRX_ID1

DATA_ROLL_PTR

事务A执行update table set name = “fxx” where id = 1 ,行记录变更为:

InnoDB之MVCC机制与事务隔离性 - hzfanxinxin - libisthanks
 

事务B执行update table set name = “libis” where id = 1 ,行记录变更为:
InnoDB之MVCC机制与事务隔离性 - hzfanxinxin - libisthanks
 

可见MVCC是基于两个隐藏列和undo log实现的(可以重用undo log,而且不需要专门对undo log进行维护)。另一种简单地实现MVCC的方法是存放多个记录的版本,但是和实际方案相比,不仅浪费了磁盘的空间,而且多个版本记录的维护开销大,影响性能。

(2)read_view:用来实现行记录的可见性。
这里有必要解释一下什么是行记录的可见性,经过上文介绍可知,MVCC实现了多个并发事务更新同一行记录会时产生多个记录版本,那问题来了,新开始的事务如果要查询这行记录,应该获取到哪个版本呢?即哪个版本对这个事务是可见的。这个问题就是行记录的可见性问题。
下图是read_view_struct的结构体:
InnoDB之MVCC机制与事务隔离性 - hzfanxinxin - libisthanks
 
其中和可见性相关的两个变量分别low_limit_id和up_limit_id,根据注释可知,前者表示事务id大于此值的行记录都不可见,后者表示事务id小于此值的行记录都可见。具体的解释见下文。

2. 行记录的可见性实现
  (1)生成read_view:  每个事务在开始的时候都会根据当前系统的活跃事务链表创建一个read_view,具体的创建过程:
           假设当前的活跃事务链表如下图所示:
           InnoDB之MVCC机制与事务隔离性 - hzfanxinxin - libisthanks
 
           对应的read_view各个变量分别是:
           read_view->creator_trx_id = ct-trx;
           read_view->up_limit_id = trx3;
           read_view->low_limit_id = trx11;
           read_view->trx_ids = [trx11, trx9, trx6, trx5, trx3];
           read_view->m_trx_ids = 5;

  (2)现在事务A要查看记录R,同时R有三个版本R1,R2,R3,首先查看最新版本R1。
          如果R1行记录DATA_TRX_ID大于low_limit_id,则R1对事务A不具有可见性,需要继续查看之前的版本R2。针对上图中的read_view,如果R1的DATA_TRX_ID=trx12,则R1不可见。
          如果R1行记录DATA_TRX_ID小于up_limit_id,则R1对事务A具有可见性。针对上图中的read_view,如果R1的DATA_TRX_ID=trx2,则R1不可见。
          如果R1在up_limit_id和low_limit_id之间,则遍历trx_ids,如果DATA_TRX_ID不在其中,则R1对事务可见,否则不可见。如果R1的DATA_TRX_ID = trx6,则R1不可见;而如果DATA_TRX_ID = trx7,则R1可见。

   (3)why?为什么是上面的结论?
         对于第一条,假如R1行记录DATA_TRX_ID大于low_limit_id,且R1对事务A可见。那如果再来了一个新事务B更新了这条记录得到新版本R0,按照同样的逻辑,R0也会对事务A可见,这样对事务A来说,就出现了幻读,即同一个事务前后读到的数据不一致。
         而对于第二条,一方面R1行记录DATA_TRX_ID小于up_limit_id说明R1记录已经提交,不可能回滚;二是即使其他事务更新了此条记录形成了新的版本,根据第一条其也不具有可见性。这样就可以确保一个事务前后只会看到R1版本,而不会读到其他版本。
         至于第三条,本质和第二条相同,都是基于上述两点来保证同一个事务前后只能读到同一个版本的。


猜你喜欢

转载自blog.csdn.net/asdfsadfasdfsa/article/details/80044034