数据库的事务隔离级别怎么理解

63次阅读
没有评论

共计 2464 个字符,预计需要花费 7 分钟才能阅读完成。

本篇内容主要讲解“数据库的事务隔离级别怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“数据库的事务隔离级别怎么理解”吧!

在 MVCC 并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,某一时刻的一致性读,不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

MySQL 在 RR 隔离级别下,快照读和当前读如果在一个会话中先后出现,可能会出现幻读。因为快照读不加锁,会允许新的插入,当前读需要读到块的最新版本,因此快照读和当前读两次操作间,就可能会出现幻读。 

MySQL 为实现 RR 隔离级别,带来了很大的代价,引入了 Next-Key Locking 解决当前读模式下的幻读问题。Next-Key Locking 可能会导致大量的 DML 失败。Oracle 没有 RR 隔离级别,在 read only 级别下,Oracle 没有幻读的问题(是快照读的模式),在 read committed 级别下,快照读、当前读以及混合的【快照读和当前读】下都存在幻读的问题。 

RR 隔离级别,MySQL 是依靠 MVCC 实现的可重复读(read view),同时依靠 MVCC 实现快照读下的幻读问题,依靠 Next-Key Locking 实现当前读下的幻读问题,所以 MySQL InnoDB 的可重复读并不保证避免幻读,需要应用显式的使用加锁读来保证,而这个加锁读使用到的机制就是 next-key locks。

脏读、不可重复读、幻读,是一种缺陷,越往后,解决缺陷的成本越高

隔离级别越高,能解决的“缺陷”越多,为什么不直接使用最高的事务隔离级别,那不就没有缺陷了?因为隔离级别越高,并发性可能会越低。

脏读,解决 写不阻塞读的问题,提高并发性,牺牲读一致性。现在绝大多数的主流数据库,都是通过 MVCC 来解决写不阻塞读的问题,不是通过经典的锁来实现。

为什么会出现不可重复读取? 
在经典的 read commited 方式下,对于读取过的数据并不加锁(读取的当下加共享锁,读取完成后释放共享锁),那么再次访问时数据可能已经被其他事务修改。

如何才能做到可重复读? 
老一辈的数据库艺术家用的方式是加锁,对读取过的数据加锁,就能保证每一次读取到的数据都是一样的,因为对读取过的数据加锁后,数据无法发生修改了。这也是 repeatable read 这个隔离级别要解决的问题,但是经典的实现可重复读的方式会产生幻读。现在绝大多数的主流数据库,是通过 MVCC 来做到的可重复读,不是通过加锁。

经典的可重复读提供了一个一致性(语句级和事务级)的读取方式,虽存在幻读,单从一致性的角度看,并不是一个大的缺陷,如果以经典的锁的方式去实现可重复读,发生死锁的概率极大,但带来的一个(好的)副作用,解决了丢失更新的问题。

按照经典的隔离级别定义,read uncommited,read commited,都不能提供一致性读。因为当下主流数据库都基于 MVCC 实现,不基于经典的锁方式,所以都实现了在 read commited 级别下的语句级的一致性。经典的隔离级别下,repeatable read 在语句级一致性的基础上还做到了事务级的一致性。

针对 oracle 的隔离级别来说,read commited 级别能够提供语句级的一致性,这个隔离级别避免不了事务级的幻读的问题,需要 read only 或者最高的 SERIALIZABLE 级别。

在一个采用共享读锁(而不是多版本)的数据库中,如果启用了 REPEATABLE READ,可以避免丢失更新的问题。原因是:已被读取的数据会在上面加一个锁(共享读锁,非排它锁),这个锁会保证数据不能被任何其他事务修改。

在可重复读(REPEATABLE READ,简称 RR)隔离级别下,read view 是在第一个读请求发起时创建的。在读已提交(READ COMMITTED,简称 RC)隔离级别下,则是在每次读请求时都会重新创建一份 read view。根据上面提到的说法,RC 隔离级别下,是每次发起 SELECT 都会创建 read view,也就是每次 SELECT 都能读取到本次查询开始时的已经 commit 的数据,所以才会出现不可重复读、幻读现象。

read view 判断当前版本数据项是否可见  
在 innodb 中,创建一个新事务的时候,innodb 会将当前系统中的活跃事务列表(trx_sys- trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务 id 列表。当用户在这个事务中要读取该行记录的时候,innodb 会将该行当前的版本号与该 read view 进行比较。 
具体的算法如下: 

设该行的当前事务 id 为 trx_id_0,read view 中最早的事务 id 为 trx_id_1, 最迟的事务 id 为 trx_id_2.

如果 trx_id_0 trx_id_1 的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的。跳到步骤 6.

如果 trx_id_0 trx_id_2 的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见. 跳到步骤 5。

如果 trx_id_1 =trx_id_0 =trx_id_2, 那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从 trx_id_1 到 trx_id_2 进行遍历,如果 trx_id_0 等于他们之中的某个事务 id 的话,那么不可见。跳到步骤 5.

从该行记录的 DB_ROLL_PTR 指针所指向的回滚段中取出最新的 undo-log 的版本号,将它赋值该 trx_id_0,然后跳到步骤 2.

将该可见行的值返回。

需要注意的是,新建事务 (当前事务) 与正在内存中 commit 的事务不在活跃事务链表中。

到此,相信大家对“数据库的事务隔离级别怎么理解”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-17发表,共计2464字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)