我用一种测试方法创建了服务:
@Service
public class DefaultTestService implements TestService {
private static final Logger LOGGER = Logger.getLogger(DefaultTestService.class);
@Autowired
private TestRepository testRepository;
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
@Override
public void incrementAndGet(Long testModelId) {
LOGGER.debug("Transaction is active: " + TransactionSynchronizationManager.isActualTransactionActive());
final TestModel tm = testRepository.findOne(testModelId);
if (tm != null) {
LOGGER.debug("Updated " + testModelId + " from value: " + tm.getValue());
tm.setValue(tm.getValue() + 1);
testRepository.save(tm);
} else {
LOGGER.debug("Saved with id: " + testModelId);
final TestModel ntm = new TestModel();
ntm.setId(testModelId);
testRepository.save(ntm);
}
}
}
我正在运行Gatling,使用testModelId = 1L
对此方法进行两次并发访问(一次2次调用)。
由于这些电话,我收到了错误:
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "test_model_pkey"
我从日志中可以看到,两个并发调用一次进入此方法并且每个打印日志
"Saved with id: 1"
"Saved with id: 1"
我假设在此方法上添加事务注释会阻止其中一个调用testRepository.findOne(testModelId)
,直到其他调用结束,但正如我从日志中看到的那样,它不能像那样工作。
所以我的问题是在出现并发访问时这种情况下交易是如何工作的?如何通过并发访问来处理这种情况?
答案 0 :(得分:1)
事务意味着在事务边界内执行的持久对象的所有修改都将:
在这种情况下交易如何运作?
两个线程中的一个到达事务的末尾并成功提交。另一个线程到达事务的末尾并且由于约束违规而无法提交,因此第二个事务终止于" rollback"状态。
为什么在第二次交易中findOne
没有被屏蔽?
只是因为尽管有SERIALIZABLE事务级别,但没有要锁定的行。 findOne
在两个事务中都没有返回任何结果,并且没有任何内容被锁定(当然,如果在第二个事务执行findOne
之前提交了第一个事务:它是另一个故事)。
如何处理特定情况下的并发事务(即插入新行时PK的约束违规)?
最常见的策略是让数据库将id分配给新行 - 借助序列 -
(作为一项实验,您可以尝试将隔离级别设置为READ_UNCOMMITED,以便第二个事务可以读取第一个事务中未经修改的更改。我不确定您是否发现任何差异,因为如果findOne
处于第二个事务中事务在第一个事务的testRepository.save(ntm);
之前执行,它仍将返回没有结果)
如何处理因一般的并发修改而导致的事务回滚?
这实际上取决于您的使用案例。基本上你可以选择:
请注意,如果事务以回滚状态终止:在事务期间修改的持久对象的图形不会恢复为其原始状态。
请注意,使用隔离级别SERIALIZABLE会导致巨大的性能问题,通常仅用于关键和偶然事务。
答案 1 :(得分:0)
我遇到了类似的并行调用问题,我使用 ReentrantLock 解决了它。
这是使用您的代码的一个示例:
@Service
public class DefaultTestService {
private final ReentrantLock lock = new ReentrantLock();
@Autowired
private TestRepository testRepository;
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
public void incrementAndGet(Long testModelId) {
lock.lock();
try {
final TestModel tm = testRepository.findOne(testModelId);
if (tm != null) {
LOGGER.debug("Updated " + testModelId + " from value: " + tm.getValue());
tm.setValue(tm.getValue() + 1);
testRepository.save(tm);
} else {
LOGGER.debug("Saved with id: " + testModelId);
final TestModel ntm = new TestModel();
ntm.setId(testModelId);
testRepository.save(ntm);
}
} finally {
lock.unlock();
}
}
}