写点什么

深入剖析数据库事务的隔离级别

用户头像
小舰
关注
发布于: 2021 年 03 月 19 日
深入剖析数据库事务的隔离级别

说到隔离级别,我们可能只是简单了解四种隔离级别以及对应的数据异常。但是,数据库光是隔离级别展开讲就能讲一节课,所以这篇文章对隔离级别进行更深入对剖析。

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 无法实现


总结以上就对事务的隔离级别及其实现方式进行了分析,事务是数据库的核心功能之一,在实现起来要比上述讲的复杂的多,但是原理是一样的,以上是基于个人理解进行的分析,欢迎大家批评指正和交流~

发布于: 2021 年 03 月 19 日阅读数: 18
用户头像

小舰

关注

还未添加个人签名 2020.11.12 加入

还未添加个人简介

评论

发布
暂无评论
深入剖析数据库事务的隔离级别