Spirng 事务隔离级别
Spring 事务隔离级别
事务并发: 在java Spring中比较常见的就是 @Transactional 注解的方法被多个线程同时执行,或者多个 @Transactional 注解的方法操作相同的数据,并且这些方法被同时执行。
事务并发会出现三种比较常见的问题,分别是脏读、不可重复读、幻读。
我们要在spring环境下验证以下问题时,有几个地方要特别注意
(1)junit中 @Transactional 是会自动回滚的,我们要在@Test 方法上加上 @Commint 注解
(2)mybatis有缓存机制,我们要关闭一级缓存,具体方法如下
假设有以下场景,小红和小明是夫妇,他们用的是同一张银行卡。银行卡中余额为1000元。
脏读:
时间 | 小明 | 小红 |
---|---|---|
t1 | 小明花费银行卡中的100元 | |
t2 | 小红查询银行卡中的余额 900元 | |
t3 | 小明支付失败,银行卡回滚到1000元 |
实际中,这种操作可能会带来很严重的后入,如小明收到工资转账1000块,小红查询到余额有2000,小红用这2000块做了这个月的预算,然后小明公司转账失败,银行卡余额回滚,银行卡只有1000元。这个时候小红就会读到错误的数据,做出了错误的预算。
解决这个问题的方法就是 @Transactional(isolation = Isolation.READ_COMMITTED) 把小红的事务设置成读已提交。
设置后,只有小明执行了commit 或者 roback 后,小红才能感知到银行卡余额的变化。
不可重复读:
时间 | 小明 | 小红 |
---|---|---|
t1 | 小明查询银行卡中有1000元 | |
t2 | 小红花费了100元 | |
t3 | 小红提交了事务 | |
t3 | 小明查询银行卡中有900元 | |
t3 | 小明提交了事务 |
如果小明在两个事务中,分别查询银行卡余额,那么查询到金额不一样是合理的。但是如果小明在一个事务中,两次查询余额不一样则是不合理的。后者这种现象称为不可重复读。
解决这个问题我们可以设置 @Transactional(isolation = Isolation.REPEATABLE_READ) 来解决,这样一来,小明在同一个事务中只会多次查询也只会得到相同的余额。
幻读:
时间 | 小明 | 小红 |
---|---|---|
t1 | 小明查询了所有银行卡 | |
t2 | 小红办理了三张银行卡 | |
t3 | 小红提交了事务 | |
t3 | 小明查询了所有银行卡 | |
t3 | 小明提交了事务 |
小明在一个事务中,按照余额1000作为条件查询到的只有一张银行卡,小红提交事务之后,小明再去查询仍然只有一张,但是更新时缺更新两张银行卡的余额,这就快照读不存在幻读现象,当前读存在幻读现象。
同一个事务中select 后update可以简单理解为是 select for update ——>当前读
幻读和不可重复读的区别是前者以表为单位,后者以行位单位。
解决幻读可以通过 @Transactional(isolation = Isolation.SERIALIZABLE) 但是这样有一个严重的问题,小明提交事务之前,小红会一直堵塞,不能操作这张表,知道小明提交事务小红才可以操作这张表。会影响效率。
结论: spring 默认采用 @Transactional(isolation = Isolation.DEFAULT) 使用mysql时是REPEATABLE_READ,oracle 时是READ_COMMITTED
现象 | READ_UNCOMMITTED | READ_COMMITTED | REPEATABLE_READ | REPEATABLE_READ |
---|---|---|---|---|
脏读 | √ | × | × | × |
不可重复读 | √ | √ | × | × |
幻读 | √ | √ | (当前√)(快照×) | × |