数据库事务的隔离级别与并发事务异常

数据库事务的ACID特性中,I为Isolation的缩写,即隔离性,隔离性指在不同业务的处理过程中,事务能够对各业务正在读、写的数据相互独立做出保证,不会相互影响。

本文主要阐明数据库并发事务中常见的异常情况,如脏读、可重复读、幻读等情况,以及各常见隔离级别分别能够解决什么样的问题。

常见的事务隔离级别

如今的数据库事务常见的隔离级别有以下五类:

  • 串行化(Serializability)
  • 可重复读(Repeatable Read)
  • 快照隔离(SnapShot Isolation) / 多版本并发控制 (MVCC)
  • 读已提交(Read Committed)
  • 读未提交(Read Uncommitted)

此五类隔离级别自上至下,串行化提供最高强度的隔离性,但就以串行化作为隔离级别系统的可用性最差,读未提交相反,拥有隔离级别中最高的可用性,以及最弱的事务间数据隔离保证(理论上还有一种完全不隔离的策略,不纳入讨论范围)。

各隔离级别常见实现简述

多线程开发环境下,为了保证线程安全,程序开发人员通常会使用加锁做同步处理,数据库事务也是同理,使用加锁实现数据库事务的隔离性,除了快照实现隔离性除外,锁可以大致分为以下三类:

  • 读锁(共享锁):多个事务可以同时读(共享)同一个数据,对于被读锁锁定的数据的写操作应该被阻塞,等待读操作执行完毕后唤醒。
  • 写锁(排他锁):只有持有写锁的事务才能够对数据执行写入操作,其他事务对被写锁锁定的读、写操作均应被阻塞。
  • 范围锁:对一个范围的数据加写锁,整个选定范围的数据不应被其他事务读、写。Mysql中范围锁的体现为临键锁和排他锁,通过锁定范围防止幻读问题。

接下来说明不同隔离级别都采用了何种锁做同步处理。

  • 读未提交:对事务涉及到的数据加写锁,持有到当前事务结束。
  • 读已提交:对事务涉及到的数据加写锁和读锁,只有写锁会持有到当前事务结束,读锁在读操作执行完毕后立即释放
  • 可重复读:对事务涉及到的数据都加写锁和读锁,且一直持有到当前事务结束
  • 快照隔离:无锁实现,此处无锁特指无读锁,数据库内部保证新版本数据与老版本数据共存,保证任一写操作都不会覆盖之前的数据,因此读操作可以完全不加锁。Mysql中实现为多版本并发控制(MVCC),与读已提交和可重复读隔离级别共同使用。
  • 串行化:对事务的所有读、写操作加写锁、读锁、范围锁所有锁均持有到当前事务结束,保证指令串行。

常见并发事务异常

脏写

指一个事务的写操作,覆盖了另一个正在运行未提交的事务的写入值

脏写会破坏数据的完整性约束(例如数据库要求保证x + y = 100)。

例如,有事务A和事务B,事务A包含两个写操作x=1、y=1,事务B也包含两个写操作x=2、y=2,假设事务的一致性要求保证x与y的值必须相等,那么有可能出现以下的情况:

事务A事务B
x = 1
x = 2
y = 2
commit
y = 1
commit

提交后结果为x=2,y=1破坏了数据一致性的约束。

脏读

指一个事务读到了另一个事务尚未提交的写入值。

脏读可能导致事务回滚后数据库数据状态的矛盾,假如事务A读取到了事务B尚未提交的写入值,并基于写入值或依据该值作为前提,执行了其他的写入操作,若后续事务B执行失败回滚,事务A写入操作的依据就有可能是错误的。

不可重复读

指同一个事务对于同一事务的两次读操作,读到了不同的值

不可重复读可能导致数据的非法写入等问题(需要考虑具体场景),例如事务的第一次读取值作为某条件判断的依据,第二次读取的值需要执行某写入操作。

实例如下:

事务A事务B
Read(x)
x = 1
Read(x)
commitcommit

幻读

指同一个事务的两次条件查询或范围查询,得到的结果不同

示例如下:

事务A事务B
select count(*) from users
insert into users values (‘user’)
select count(*) from users
commitcommit

事务B相同查询条件的两次读取得到了不同的值,视具体业务场景,可能会导致意料之外的结果。

更新丢失

指两个事务都尝试对同一个数据执行写操作,后执行写操作的事务先提交,导致该事务的更新被覆盖。

假设有一物流管理的系统,事务A需要向仓库添加5件物品,事务B需要向仓库添加10件物品,两事务同时执行的情况下可能出现更新丢失,结果可能是事务A或事务B的添加被覆盖,导致少计算的若干物品。

示例如下:

事务A事务B
x = 5
x = 10
commit
commit

示例会导致x = 10写操作丢失。

读倾斜

指读到了数据一致性被破坏的数据,一致性特指业务逻辑上的约束,例如前文提及的x + y = 100

示例如下:

事务Axy事务B
5050Read(x)
Write(x,30)3050
Write(y,70)3070
commit3070Read(y)
3070commit

在该示例中,事务A负责更新x与y的值,事务B负责读取x与y的值用作某可能的条件判断,执行结果为B读取到的x与y分别为50与70,在事务B的视角这违背了数据的约束,与数据库的事实状态违背。

写倾斜

指两个同时执行的事务都以相同的约束为条件,随后各自执行自己的写操作,某时刻破坏了这个约束条件。

假设现有事务A和事务B以及数据x = 2,数据库存在数据约束x > 0,事务A和事务B中均会做x自减操作,意味着事务A和事务B执行的先决条件均为x > 0,如果两事务并发的执行,可能会导致两事务执行前的条件检查均通过且最终执行成功,执行完毕后却破坏了数据x的约束。

总结

根据本文内容,汇总表示不同隔离级别分别能解决的并发事务异常,行表示各隔离级别,列表示各并发事务异常,内容表示在当前隔离级别下是否会出现该并发异常

脏写脏读不可重复读幻读更新丢失读倾斜写倾斜
读未提交NoYesYesYesYesYesYes
读已提交NoNoYesYesYesYesYes
快照隔离NoNoNoYesNoNoYes
可重复读NoNoNoYesNoNoNo
串行化NoNoNoNoNoNoNo

参考书目:《深入理解分布式系统》唐伟志

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值