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元。在Oracle数据中,不会发生脏读的情况。
不可重复读是指A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额发生不一致
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元,把余额改为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元 |
在同一个事务中T4和T7时间点读取的账户存款余额不一致
A事务读取B提交的新增数据,这时A事务将出现幻想读的问题。幻读一般发生在计算统计数据的事务中。举个例子,假设银行系统在同一个事务中两次统计存款的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时两次统计的总金额将不一致。
时间 | 统计金额事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计存款总金额为10000元 | |
T4 | 新增一个存款账户,存款为100元 | |
T5 | 提交事务 | |
T6 | 再次统计存款总金额为10100元(幻象读) |
如果新增的数据刚好满足事务的查询条件,那么这个新数据就会进入事务的视野,因而导致两次统计结果不一致的情况。
幻读和不可重复读是两个容易混淆的概念,前者是指读到了其他事物已经提交的新增数据,而后者是读到了已经提交事务的更改数据(更改或删除)。为了避免这两种情况,采取的策略是不同的:防止读到更改数据,只需对操作的数据添加行级锁,阻止操作过程中的数据发送变化,而防止读到新增数据,则往往需要添加一个表级锁–将整张表锁定,防止新增数据(Oracle使用多版本数据的方式实现)
A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错可能会造成很严重的问题。通过下面的账号取款转账就可以看出来。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账号余额为1000元 | |
T4 | 查询余额为1000元 | |
T5 | 汇入100元,把余额改为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元,把余额改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
A事务在撤销时,“不小心”将B事务已经转入账号的金额给抹去了。
A事务覆盖B事务已经提交的数据,造成B事务所操作丢失。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账号余额为1000元 | |
T4 | 查询余额为1000元 | |
T5 | 取出100元,把余额改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元,把余额改为1100元 | |
T8 | 提交事务 | |
T9 | 把余额改为1100元(丢失更新) |
在上面的例子,由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务先提交,那么用户损失了100元。
不能被spring AOP 事务增强的方法
动态代理策略 | 不能被事务增强的方法 |
---|---|
基于接口的动态代理(JDK) | 除了public方法均不能,此外public static 也不能增强 |
CGLib 动态代理 | private,static ,final 方法 |
对于private方法,由于最终会被public方法封装后再开放给外部调用者,而public方法是可以事务增强的,所以基本没有什么问题。在实际开发中,最容易造成隐患的基于CGlib代理的 public static 和 public final 方法。原因是它们本身是public的,因此可以直接被外部调用,只要调用方没有事务上下文,这些方法就会以无事务的方式运行。