关于mysql:事务隔离级别和锁引起的数据不一致

​最近生产环境零星呈现了几笔脏数据,即同一业务编号呈现了两条数据(咱们零碎中唯一性并未依附于数据库的索引)。明明代码中曾经加锁了, 还呈现这样的问题,经定位,发现是事务的隔离性,导致第二个事务看不到第一个事务的数据,从而导致数据反复。

业务伪代码

// 省略了一些必要的异样解决。
@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办法抛出异样时,进行回滚。然而如果数据库事务隔离级别为读已提交及以上,在高并发量下,还是会呈现反复数据。

起因剖析

  1. 事务t1启动。
  2. 事务t2启动。
  3. 线程t1获取锁,胜利。
  4. 事务t2获取锁,失败,并期待。
  5. 线程t1查看是否存在entity1,发现不存在,保留数据。
  6. 线程t1开释锁。
  7. 事务t1提交。
  8. 事务t2获取锁。
  9. 事务t2 查看是否存在entity1,发现不存在,保留数据。
  10. 事务t2 开释锁。
  11. 事务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();
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理