当前位置: 代码迷 >> 综合 >> 脏读、不可重复读、幻读、两类丢失更新与四大隔离级别
  详细解决方案

脏读、不可重复读、幻读、两类丢失更新与四大隔离级别

热度:97   发布时间:2023-10-28 10:13:41.0

目录

1.什么是脏读、不可重复读、幻读、第一类丢失更新和第二类丢失更新?

2.为什么会产生脏读、不可重复读、幻读、第一类丢失更新和第二类丢失更新?

       2.1 第一类丢失更新

       2.2 脏读

       2.3 不可重复读

       2.4 第二类丢失更新

       2.5 幻读

3.如何防止脏读、不可重复读、幻读、第一类丢失更新和第二类丢失更新的发生?

       3.1 读未提交(Read Uncommitted)

       3.2 读已提交(Read Committed)

       3.3 可重复读(Repeatable Read)

       3.4 可串行化(Serializable)

4.如何设置数据库隔离级别?


1.什么是脏读、不可重复读、幻读、第一类丢失更新和第二类丢失更新?

       脏读:B事务读取到A事务尚未提交的更改过的数据,并在这个数据的基础上进行了操作。如果A事务回滚,那么B事务读到的数据就是脏数据,此现象称为脏读。

       不可重复读:B事务读取了A事务已经提交过的更改(或删除)的数据。如B事务第一次读取数据,然后A事务更改该数据并提交,B事务再次读取数据,导致两次读取的数据不一致,此现象称为不可重复读。

       幻读:B事务读取了A事务已经提交的新增数据。

       第一类丢失更新: A事务撤销事务,覆盖了B事务提交的事务(现代关系型数据库中已经不会发生)。

       第二类丢失更新: A事务提交事务,覆盖了B事务提交的事务(是不可重复读的特殊情况)。

       注:其中不可重复读和幻读容易混淆,不可重复读侧重于数据的修改,幻读侧重于数据的新增或删除;不可重复读侧重于单独的一条数据,幻读侧重于数据的集合。

2.为什么会产生脏读、不可重复读、幻读、第一类丢失更新和第二类丢失更新?

       2.1 第一类丢失更新

       如下两个事务,A事务先查询账户余额有1000元,存入500元使其账户变为1500,同时B事务进行转账并提交了事务,使账户金额变为500,而事务A最后因为某些原因,没有提交事务,而是回滚了事务,将账户金额重新设置为1000。但实际上账户已经被转走了500元,这就是第一类丢失更新。

时间序号 A事务 B事务
T1 开启事务 开启事务
T2 查询账户余额     balance=1000 查询账户余额  balance=1000
T3 存入500             balance=1500
T4 转出500         balance=500
T5 提交事务        balance=500
T6 取消事务,回滚 balance=1000

        总结:A事务update、B事务update同一条数据,A事务回滚

       2.2 脏读

       如下两个事务,A事务先查询账户内有1000元,转出500元后,同时B事务查询账户余额,查询到了事务A未提交的余额500元。此时A事务进行了回滚,则B事务所读取的数据就是不正确的,即为脏读。

时间序号 A事务 B事务
T1 开启事务 开启事务
T2 查询账户余额     balance=1000
T3    转出500         balance=500
T4 查询账户余额  balance=500
T5      回滚事务        balance=1000

        总结:A事务update、B事务select同一条数据、A事务回滚

       2.3 不可重复读

       如下两个事务,A、B开启事务,事务B查询账户余额为1000元。A事务转出了100元,余额变成了900元,并提交了事务。这时当事务B再次查询余额的时候,发现账户余额变成了900元,这就是不可重复读产生的原因。

时间序号 A事务 B事务
T1 开启事务 开启事务
T2 查询账户余额  balance=1000
T3 转出100            balance=900
T4 提交事务
T5 查询账户余额  balance=900

        总结:B事务select、A事务update并提交事务、B事务再次select

       2.4 第二类丢失更新

       理解了不可重复读出现的原因后,如果把上述例子换成事务A和事务B同时进行转出操作,就是第二类丢失更新产生的原因了,因为多个事务同时对一行数据进行更新导致的问题。

时间序号 A事务 B事务
T1 开启事务 开启事务
T2 查询账户余额     balance=1000
T3 查询账户余额  balance=1000
T4 转出100            balance=900
T5 转出100            balance=900
T6 提交事务         balance=900
T7 提交事务         balance=900

        总结:A事务select、B事务update并提交事务、A事务也update并提交事务

       2.5 幻读

       A事务读取到B事务提交的新增数据,就像产生了幻觉一样,所以称作幻读,幻读一般发生在数据统计事务中。

时间序号 A事务 B事务
T1 开启事务 开启事务
T2 统计存款笔数为10笔
T3 新增存款1笔
T4 提交事务
T5 再次统计存款笔数为11笔

        总结:A事务select count(*)、B事务insert、A事务再次select count(*)

3.如何防止脏读、不可重复读、幻读、第一类丢失更新和第二类丢失更新的发生?

       为了解决上面提及的并发问题,主流关系型数据库都会提供四种事务隔离级别。

       3.1 读未提交(Read Uncommitted)

       该隔离级别所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题。

       3.2 读已提交(Read Committed)

       这是大多数数据库系统的默认隔离级别(但不是MySQL默认)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。

       3.3 可重复读(Repeatable Read)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这种隔离级别可以防止除幻读外的其他问题。

       3.4 可串行化(Serializable)

       最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争。最直观的体现就是,当数据库隔离级别设置为串行化后,A事务在未提交之前,B事务对A事务数据的操作都会被阻塞。通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:比如乐观锁和悲观锁机制(下篇再详细分析)。列个表格总结下并发一致性问题和四大隔离级别的关系

隔离级别 脏读 不可重复读 幻读 第二类丢失更新
读未提交 不可预防 不可预防 不可预防 不可预防
读已提交 可以预防 不可预防 不可预防 不可预防
可重复读 可以预防 可以预防 不可预防 可以预防
可串行化 可以预防 可以预防 可以预防 可以预防

4.如何设置数据库隔离级别?

设置全局隔离级别:

set global transaction isolation level READ UNCOMMITTED;//读未提交
set global transaction isolation level READ COMMITTED;//读已提交
set global transaction isolation level REPEATABLE READ;//可重复读
set global transaction isolation level SERIALIZABLE;//串行化

设置session级别的隔离级别:

set session transaction isolation level READ UNCOMMITTED;//未提交读
set session transaction isolation level READ COMMITTED;//已提交度
set session transaction isolation level REPEATABLE READ;//可重复读
set session transaction isolation level SERIALIZABLE;//串行化

Spring事务:

Spring事务默认使用数据库的隔离级别,当然也可以通过@Transactional中的isolation参数调整当前Session级的隔离级别(数据库的隔离级别个人感觉粒度比较粗,Spring的隔离级别好一些 ,因为可以定义到方法上)。

solation的参数有以下五种:

(1)solation.DEFAULT:为数据源的默认隔离级别

(2)isolation=Isolation.READ_UNCOMMITTED:未授权读取级别

(3)iIsolation.READ_COMMITTED:授权读取级别

(4)iIsolation.REPEATABLE_READ:可重复读取级别

(5)iIsolation.SERIALIZABLE:序列化级别

  相关解决方案