共计 1015 个字符,预计需要花费 3 分钟才能阅读完成。
最近生产环境零星呈现了几笔脏数据,即同一业务编号呈现了两条数据(咱们零碎中唯一性并未依附于数据库的索引)。明明代码中曾经加锁了,还呈现这样的问题,经定位,发现是事务的隔离性,导致第二个事务看不到第一个事务的数据,从而导致数据反复。
业务伪代码
// 省略了一些必要的异样解决。@Transactional
public void saveAndComplete(T entity) {lock.lock();
code = repository.findByCode(entity.getCode());
if (code.isEmpty()) {repository.save(entity);
}
this.Complete(entity.getCode());
lock.unlock();}
代码很简略,有一个 saveAndComplete
办法,用来保留实体,并实现工作,并用 Spring 的事务管理器治理事务,当 Complete
办法抛出异样时,进行回滚。然而如果数据库事务隔离级别为 读已提交 及以上,在高并发量下,还是会呈现反复数据。
起因剖析
- 事务
t1
启动。 - 事务
t2
启动。 - 线程
t1
获取锁,胜利。 - 事务
t2
获取锁,失败,并期待。 - 线程
t1
查看是否存在entity1
,发现不存在,保留数据。 - 线程
t1
开释锁。 - 事务
t1
提交。 - 事务
t2
获取锁。 - 事务
t2
查看是否存在entity1
,发现不存在,保留数据。 - 事务
t2
开释锁。 - 事务
t2
提交。
次要起因就在步骤 9,因为 InnoDB
默认的隔离级别是 可反复读
,所以即便事务t1
曾经将 entity1
插入数据库,事务 t2
也是看不到的,所以会呈现反复数据。
问题解决
问题解决起来也不难,只有保障事务 t2
在事务 t1
完结之后再开始即可,即 插入数据这个动作的事务线性化。代码如下
// 省略了一些必要的异样解决。public void saveAndComplete(T entity) {lock.lock();
transactionManager.getTransaction(); // 先获取锁,再开启事务
code = repository.findByCode(entity.getCode());
if (code.isEmpty()) {repository.save(entity);
}
this.Complete(entity.getCode());
transactionManager.commit(); // 先提交事务,最初开释锁。lock.unlock();}
正文完