乐趣区

关于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();}
退出移动版