MVCC 与 事务的隔离级别

一、为什么要隔离事务

  在只有一个事务对数据库进行操作的时候,不需要用到事务隔离,但在多个事务并发的时候,问题就来了,如果不采取任何措施,可能产生的结果如下:

我们这里假设有事务 T1 和 事务 T2 ,以及一个数据段 x = 0,y = 0; T1 和 T2 事务并行执行; 我们规定一致性要求 x 始终等于 y

1、脏写:

  当 T1 执行了 x = 2 ,并且写入内存,此时事务 T1 还没有提交,这时事务 T2 执行了 x = 1, y = 1, 而此时 T1 又执行了 y = 2;当两个线程都提交的时候之后,x = 1, y=2,显然破坏了我们本身的一致性。

而导致这一情况的原因就是,在事务 T1 修改了 x 但没有提交的时候, 事务 T2 就又一次修改了 x ,这就称为脏写

 

2、脏读

  事务 T1 执行了 x = 1,事务 T2 此时进行读取,读取的结果为 x = 1,y = 0,然后 T1 才执行了 y = 1;T2 事务读取的值违背了我们规定的一致性。

在事务 T1 修改了数据,但没有进行提交的时候,T2 就开始了读取工作,这样的读取就称为脏读

 

3、不可重复读 

  当事务 T1 读取了 x = 0, y = 0, 事务 T2 进行了 x = 1,y = 1 的写工作,并且 T2 提交了事务。此时  T1 又一次读了 x = 1,y = 1,发现和第一次读取的值不一样,就发生了不可重复读

 

4、幻读

  当 T1 事务根据 P 约束查询出了 k 条数据,而 T2 此时 插入 或者 删除了 一条符合 P 约束的数据, T2 事务第二次根据 P 约束进行查询的时候,会查出 K+1 或者 K-1 条符合的数据,这些减少或者增加的数据就像虚幻出现一样,称为幻读

 

幻读似乎和不可重复读有些相似之处,都是在一个两次读取中间被另一个事务修改了数据,造成两次读取的结果不一样。

它们的区别就在于 幻读强调 删除 和 插入,不可重复读强调的是 修改

 

针对在并发事务中产生的几种问题,数据库设计了四种隔离级别

 

二、SQL 标准中的四种隔离级别

  如果我们给上述几种错误的严重性排个序:脏写 > 脏读 > 不可重复读 > 幻读,针对这个排序,设定了四种隔离级别:

 

隔离级别脏读不可重复读幻影读
READ-UNCOMMITTED
READ-COMMITTED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

 如上表所示:四种级别分别是

READ UNCOMMITTED(未提交读):不可能出现脏写,可能出现脏读、不可重复读、幻读。

READ COMMITTED(提交读):不可能出现脏写、脏读,可能出现不可重复读、幻读

REPEATABLE READ(可重复读):不可能出现脏写、脏读、不可重复读,可能出现幻读(可以极大的避免幻读)

SERIALIZABLE(可串型化):以上现象都不可能发生

 

那这几种隔离级别的实现原理是什么?

 

三、MVCC

   在我之前的博客中提到过,在聚簇索引的数据段中其实有两个隐藏属性(见《从根上理解MySQL》388页):

trx_id: 一个事务对该条数据进行修改后,就会记录该 事务 ID

roll_pointer: 每次对该条数据进行修改的时候,就会把旧的版本保存下来,类似链表的形式,链表头保存的是最新的版本,这就是 undo 日志,而这也是MVCC的关键

 

  每次事务进行修改的时候,会将事务的 ID 存储在 trx_id 并且将旧的版本连接在新版本的 roll_pointer 后面,这样形成的一条版本链就称为 MVCC (多版本并发控制)

 

回到我们上面所介绍的四种隔离方式:

1、READ UNCOMMITTED :由于它的意思是允许读未提交事务修改的数据,所以直接读取数据的最新版本就行

2、READ COMMITTED 和 REPEATABLE READ 都必须保证读到已经提交事务的值(没有提交事务的不能读)

3、SERIALIZABLE:这个需要加锁来实现(之后我会单独写)

 

那前面两个隔离方式实现的重点就是 :需要读取数据的版本不同

 

为了实现这个功能,InnoDB 提出了 ReadView(一致性视图),其主要包含四个内容:

 

每一个事物被创建都有创建这个 ReadView ,来判断该访问的版本

 

1、m_ids: 在生成 ReadView 时,当前系统中活跃的事务的 id 列表

2、min_trx_id: 在生成 ReadView 时,当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中最小 id 值

3、max_trx_id: 在生成 ReadView 时,当前系统应该分配给下一个事务的事务 id 值

4、creator_trx_id: 生成该 ReadView 的事务的事务 id

 

有了这个 ReadView 就可以进行访问策略,有以下几种情况:

1、如果被访问版本的 trx_id(即事务 id ) 属性值与 ReadView 中的 creator_trx_id值相同,意味着当前事务在访问自己修改的记录,可以访问

2、如果被访问版本的 trx_id 属性值小于 ReadView 中 min _trx_id,说明该事务修改已经在本事务创建的时候就已经提交,可以访问

3、如果被访问版本的 trx_id 属性值大于等于 ReadView 中 max_trx_id,说明该事务在本事务之后才开启,不能访问

4、如果被访问版本的 trx_id 属性值max_trx_id min _trx_id之间,这时需要判断 trx_id 是否还在 m_ids 列表里,如果在说明还在活跃状态,不能访问此条记录,如果不在说明已经提交,可以访问。

具体举例见:《从根上理解MySql》392 - 396

 

posted @ 2022-03-20 19:11  空心小木头  阅读(335)  评论(0编辑  收藏  举报