spring环境有一个大现象还是我大错特错。 但是默认的 spring @Transactional 注释不是 ACID 而只是缺乏隔离的 ACD。这意味着如果你有方法:
@Transactional
public TheEntity updateEntity(TheEntity ent){
TheEntity storedEntity = loadEntity(ent.getId());
storedEntity.setData(ent.getData);
return saveEntity(storedEntity);
}
如果 2 个线程以不同的计划更新进入,会发生什么情况。他们都从数据库加载实体,他们都应用自己的更改,然后第一个被保存并提交,当第二个被保存并提交时,第一个 UPDATE IS LOST。真的是这样吗?使用调试器,它就是这样工作的。
最佳答案
丢失数据?
您不会丢失数据。可以将其视为更改代码中的变量。
int i = 0;
i = 5;
i = 10;
你“丢”了 5 吗?好吧,不,你替换了它。
现在,您提到的多线程的棘手部分是,如果这两个 SQL 更新同时发生怎么办?
从纯粹的更新角度(忘记读取)来看,这没什么不同。数据库将使用锁来序列化更新,因此一个仍然会在另一个之前进行。第二个自然获胜。
但是,这里有一个危险......
根据当前状态更新
如果更新是基于当前状态的有条件的怎么办?
public void updateEntity(UUID entityId) {
Entity blah = getCurrentState(entityId);
blah.setNumberOfUpdates(blah.getNumberOfUpdates() + 1);
blah.save();
}
现在您遇到了数据丢失的问题,因为如果两个并发线程执行读取 (getCurrentState
),它们将各自加 1
,得到相同的数字,并且第二次更新将丢失前一次的增量。
解决
有两种解决方案。
- Serializable 隔离级别 - 在大多数隔离级别中,读取(
select
)不持有任何独占锁,因此不会阻塞,无论它们是否在事务中。 Serializable 实际上会为读取的每一行获取并持有独占锁,并且仅在事务提交或回滚时释放这些锁。 - 在一条语句中执行更新。 - 单个
UPDATE
语句应该使我们成为原子,即UPDATE entity SET number_of_updates = number_of_updates + 1 WHERE entity_id = ?
。
一般来说,后者的可扩展性要高得多。您持有的锁越多,持有的时间越长,您获得的阻塞就越多,因此吞吐量就越低。
关于java - spring默认@Transactional和默认丢失更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49365965/