数据库并发问题:脏读,不可重复读,幻读,第一类丢失更新,第二类丢失更新
一个数据库,多个客户端并发访问数据库。在数据库中的相同数据可能被多个事物同时访问,如果没有采取必要的隔离措施,就会导致并发问题,破坏数据的完整性。这些问题可以归结为5类:3类数据库读问题,2类数据库更新问题。
1.脏读(dirty read)
A事物读取B事物尚未提交更改的数据,并在这个数据的基础上操作。如果B事物恰巧回滚,那么A事物读取到的事物是根本不被承认的。如下列:
时间 | 事物A | 事物B |
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询余额1000元 | |
T4 | 取出500元,余额改为500元 | |
T5 | 查询余额500元(脏读) | |
T6 | 撤销事物余额1000元 | |
T7 | 汇入100元,余额改为600元 | |
T8 | 提交事物 |
在这个场景中B希望取出500元,而后又撤销了动作,而A往相同账户转入100元,就因为A读取到了B未提交更改数据,造成账户白白丢失500元。(注:在oracal数据库中,不会发生脏读的情况)
2.不可重复读(unrepeatable read)
不可重复读是指:A事物读取到B事物已提交的更改数据。假设A在取款事物过程中,B往账户转入100元,A两次读取到的余额不一致。
时间 | 事物A | 事物B |
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额位1000元 | |
T5 | 取出100元,修改余额为900元 | |
T6 | 提交事物 | |
T7 | 查询账户余额900元(和T4查询不一致) |
同一事物中两次查询账户余额不一致
3.幻读(phantom read)
A事物读取B事物提交的新增数据,这时A事物将出现幻读
时间 | 事物A | 事物B |
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 统计账户总存款为10000元 | |
T4 | 新增一个存款账户,并转入100元 | |
T5 | 提交事物 | |
T6 | 再次统计存款为10100元(幻读) |
如果新增数据刚好满足查询条件,这个数据就会进入事物的视野,因而产生两次统计结果不一致的情况
注:幻读和不可重复读的区别在于前者读取到的是已提交的新增数据,后者读取到的是已提交的更新数据(或者删除的数据)。为了避免这两种情况,采取的策略是不同的,防止读到更改操作,只需要对操作数据添加行级锁,阻止操作中的数据发生变化;而防止读到新增数据,则往往添加表级锁--将整张表锁定,防止新增数据(Oracal通过多版本数据的方式实现)
4.第一类丢失数据更新
A事物撤销时,覆盖掉 B事物已提交的更新数据。
时间 | 事物A | 事物B |
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户为1000元 | |
T4 | 查询账户为1000元 | |
T5 | 转入100元,修改余额为1100元 | |
T6 | 提交事物 | |
T7 | 取出100元,修改余额为900元 | |
T8 | 撤销事物(或提交事物) | |
T9 | 余额回复为1000元(丢失更新) |
A事物在撤销时,将B事物转入的100元抹去了
5.第二类丢失更新
A事物覆盖B事物已提交的数据,造成B事物所有的操作丢失
时间 | 事物A | 事物B |
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户为1000元 | |
T4 | 查询账户为1000元 | |
T5 | 取出100元,修改余额为900元 | |
T6 | 提交事物 | |
T7 | 转入100元 | |
T8 | 提交事物 | |
T9 | 修改余额为1100元(丢失更新) |