0%

脏读,不可重复读,幻读

spring(数据库)事务隔离级别分为四种(级别递减)

  1. Serializable(串行化): 最严格的级别,事务串行执行,资源消耗最大

  2. REPEATABLE READ(重复读): 保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失。

  3. READ COMMITTED(提交读): 大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。

  4. Read Uncommitted(未提交读): 事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”。

脏读,不可重复读,幻读

1. 脏读: 读取了其他事务未提交的数据

转账事务A 取款事务B
1 开始事务
2 开始事务
3 查询账户余额为2000元
4 取款1000元,余额被更改为1000元
5 查询账户余额为1000元(产生脏读)
6 取款操作发生未知错误,事务回滚,余额变更为2000元
7 转入2000元,余额被更改为3000元(脏读1000+2000)
8 提交事务

事务B取出金额1000元后事务未提交,事务A查到了余额1000元(事务B取出后的金额),当事务B回滚后, 这时事务A累加金额+2000元, 正确应该为4000元,但并不知道事务B已经把金额回滚到2000元, 事务A最后累加的总金额为1000+2000=3000元.

  • 正确应该是账户余额为4000元

2. 不可重复读: 前后多次读取, 数据内容不一致

转账事务A 取款事务B
1 开始事务
2 第一次查询账户余额为2000元
3 开始事务 其他操作
4 取款金额1000元, 余额1000元 其他操作
5 提交事务 其他操作
6 第二次查询账户余额为1000元

事务B的事务比较长, 且查询了两次账户, 第一次和第二次取出的金额不匹配, 因此数据不重复了, 系统不可以读取到重复的数据, 成为不可重复读.

  • 正确应该是事务B两次查询的金额是一致的

3. 幻读: 前后多次读取, 数据总量不一致

出入账事务A 流水事务B
1 开始事务
2 第一次查询总流水记录100条
3 开始事务 第一次查询总流水记录100条
4 进账出账流水记录+2条 其他操作
5 提交事务 其他操作
6 第二次查询总流水记录102条

事务B在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读

  • 正确应该是第二次查询也是100条记录和第一次一致

不可重复读和幻读的区别

  1. 不可重复读是读取了其他事务更改的数据,针对insert与update操作

    解决方法:

    • 用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据.
  2. 幻读是读取了其他事务新增的数据,针对insert与delete操作

    解决方法:

    • 使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据.
    • 幻读的另一种解决方案,版本控制,表级锁用了那和串行读没什么区别,性能太低,在mysql中innodb自带有版本控制,可以很好的解决,而且幻读产生的根本原因是采用的行级锁,所以只针对脏读和重复读有用