原创|深入剖析数据库事务的隔离级别
上一篇中讲到了,数据库及分布式的那些一致性,在讲到隔离级别的时候,只是简单介绍了一下四种隔离级别以及对应的数据异常。但是,数据库光是隔离级别展开讲就能讲一节课,所以这篇文章对隔离级别进行更深入对剖析。
1. 隔离级别
讲隔离级别之前先回忆一下隔离型,隔离性是指多个用户并发访问数据库的时候,不同用户的事务操作之间要保持的互相独立性。在数据库中,隔离级别是通过并发控制管理器或调度器来保障的。通过隔离级别保证并发事务之间的数据可见性,进而导致了不同级别的一致性,也可以说隔离级别破坏了一致性,低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。因此在数据库系统实现的时候要在它们之间当然要做一个权衡。
1.1 四种隔离级别及数据异常
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。这四种不同的隔离级别对应着在事务的并发操作中可能会出现的四种不同的不一致性问题:脏读,不可重复读,幻读。
下面以通过“加锁”的方式为例,剖析不同的隔离级别及其实现。
读取未提交(Read Uncommitted)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
如上图所示,在tx1和tx2事务中,Tx2在t4时刻读取到了还未提交的数据A=15,其实后面Tx1事务在t5时刻又进行了回滚,因此这里就产生了读脏数据的异常。
读已提交(Read Committed)
这是大多数数据库系统的默认隔离级别(例如Oracle,但MySQL默认的是可重复读)。读已提交满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。
如上图所示,在事务进行读写的时候,加入了锁(读锁和写锁,锁的相容性矩阵),这样在tx1进行修改的时候,加入了写锁(排他锁)直到事务提交释放锁,这样就保证了tx2就无法读取该值,解决了脏读的问题。
但是读已提交仍然会存在不可重复读的问题,如上图所示,tx2在t3时刻读取了A=10,在t5时刻tx1事务提交了修改,tx2在t6时刻读取到了A=15,这就出现了两次重复读数据不一致读情形。
可重读(Repeatable Read)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
如上图所示,可重复读的隔离级别下,事务读锁不再是读完即释放,而是跟写锁一样,在事务结束的时候才释放,这样就可以避免不可重复读读情况,因为一旦tx2开始读取某个记录,该记录就会被加锁,其他事务就没有机会进行修改,所以会避免上面出现读问题。
不过理论上,这个隔离级别还是会导致一个问题--幻读。由于上面所提到的加锁都是指行级锁,因此,如果出现上图所示,tx2在t3时刻统计整个表的记录数,会在每个已经存在的记录上加锁,但是这个时候,tx1在t4时刻可以进行数据的插入,导致tx2在t6时刻再次统计的时候会发现多了一条数据,感觉像是产生了幻觉。
可串行化(Serializable)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。
上面提到的幻读却是很难解决,因此就需要更进一步的措施来解决。可串行化的隔离级别下啊,将行级锁升级到表级锁,这样通过将整个表锁定避免新纪录插入来解决这个幻读问题。
2. 隔离级别对实现方式
上面介绍了不同的隔离级别以及以加锁的方式为例进行了简单的梳理,下面再进行总结一下如何实现不同的隔离级别。
这里主要介绍两种常见的实现方式:锁和MVCC,当然这两种在一些例如MySQL中也会混合使用锁和MVCC机制,这里主要来说一下不同的实现方式在不同隔离级别下的异同。
2.1 加锁方式
加锁方式其实在上面讲解的时候已经讲过了,这里再做一个总结。
读未提交:读操作不加共享锁
读已提交:读操作加共享锁,写操作加排他锁,读操作完毕释放共享锁,排他锁直到事务提交才释放
可重复读:读操作加共享锁,写操作加排他锁,读操作和写操作直到事务提交才释放共享锁和排他锁
可串行化:额外添加范围锁,例如表锁或者叶锁,或者如InnoDB在记录锁上加间隙锁或者读锁阻塞其他操作直到事务结束
2.2 MVCC机制
先说一下,MVCC就是多版本并发控制,主要通过数据版本可见行的方式来控制不同事务之间的数据可见性,实现事务隔离。
读未提交:允许读到最新记录,不用做什么
读已提交:读写操作加锁,同时会用到MVCC的可见行判断,不用加间隙锁,不用解决幻象问题;在一个事务块里,如果存在多条select语句,则每条select语句分别使用自己的快照(ReadView,select结束调用MVCC的view_close方法关闭ReadView)
可重复读:为了不看到晚于他的其他事务的更新,在该级别下为事务设置了要一个“一致性读视图“,之后读取的数据是根据这个快照来获取,即沿用老的快照
可串行化:可串行化隔离级别需要对所有读取的行都加锁,单纯使用MVCC无法实现
总结
以上就对事务的隔离级别及其实现方式进行了分析,事务是数据库的核心功能之一,在实现起来要比上述讲的复杂的多,但是原理是一样的,以上是基于个人理解进行的分析,欢迎大家批评指正和交流~
●
●
●
●
长按二维码识别关注
文章都看完了不点个 吗
欢迎 点赞、在看、分享 三连哦~~