本篇内容主要讲解“数据库的事务隔离级别怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“数据库的事务...
本篇内容主要讲解“数据库的事务隔离级别怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“数据库的事务隔离级别怎么理解”吧!
在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 的事务不在活跃事务链表中。
到此,相信大家对“数据库的事务隔离级别怎么理解”有了更深的了解,不妨来实际操作一番吧!这里是捷杰建站网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!