首页 > 解决方案 > 为什么在可序列化事务中选择后更新会导致死锁?

问题描述

我正在使用注释测试具有不同隔离级别的数据库事务

@Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE)在春天。

我有一个表“帐户”:

create table account (id int primary key, balance int);

里面有一条记录:

insert into account values(1, 999);

我写了一些Java代码,基本上类似于一个事务中的以下SQL:

select * from account where id=1;
update account set balance=balance-1 where id=1;

我创建了 999 个线程,每个线程运行一次。

我原以为记录的余额为 0,但大多数线程org.springframework.dao.DeadlockLoserDataAccessException: Deadlock found when trying to get lock; try restarting transaction在尝试更新时都会抛出,并且记录的余额约为 700。

我认为当在可序列化事务中选择时,事务在它读取的行上获取读锁,并且只有在提交后才会释放它,这样其他事务在提交之前无法选择行。

我是否误解了有关可序列化或事务或锁定的内容?还是我在 Java 中的实现导致了问题?我正在将 myBatis 与 mySQL 一起使用。这是我用于交易的实际 Java 代码:

@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
@Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE)
public class MyTestService {

    private final AccountMapper accountMapper;

    public void decreaseMoneyByOne(String id){
        Account account = accountMapper.selectByPrimaryKey(id);
        int balanceBefore = account.getBalance();
        account.setBalance(balanceBefore - 1);
        accountMapper.updateByPrimaryKey(account);
    }
}

标签: javaspringtransactions

解决方案


下面是 Postgres 如何进行可序列化(Serializable Snapshot Isolation)的。如果您使用不同的数据库,它可能使用不同的机制。真正的数据库不会按照您描述的方式进行操作,因为这基本上是一种幼稚的实现,在锁定所有这些行时性能会很糟糕。有更轻(虽然更复杂)的方法,包括杀死死锁的事务等等。

如果您想获得余额为 0 的最终结果,则错误消息显示“尝试重新启动交易”。


推荐阅读