如何让两个完全相同的读写并发事务等待对方?
问题描述:
我有一个从数据库读取的Spring事务,如果找不到它,它会创建它。例如:如何让两个完全相同的读写并发事务等待对方?
@Transactional
public int getUserIdCreateIfNotExistent(String email) throws Exception {
try {
return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email);
} catch (IncorrectResultSizeDataAccessException e) {
Thread.sleep(10000);
return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email);
}
}
当这个方法被调用两次的同时,将在具有两个不同的用户在数据库相同的电子邮件的结果(有独特的电子邮件没有限制,这只是为了演示)。
我希望第二个事务等待第一个事务,以便只创建一个用户。我试着将事务隔离级别更改为Isolation.SERIALIZABLE
,但它所做的只是使提交最后一次回滚的事务。
编辑: 之前有人建议使用或类似的东西锁定的Java解决方案,是要明白,这个方法是一个Web应用程序的一部分,在特定端点被击中称为是非常重要的。该Web应用程序在多个不同的计算机上运行,并且负载平衡,并且在端点被同时调用两次时发生问题。这意味着代码可能在两台不同的计算机上并行执行,与另一台计算机上的同一个数据库对话。
答
解决与咨询锁:
@Transactional
public int getUserIdCreateIfNotExistent(String email) throws Exception {
try {
jdbcTemplate.queryForRowSet("SELECT pg_advisory_xact_lock(hashtext('users'), hashtext(?))", email);
return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email);
} catch (IncorrectResultSizeDataAccessException e) {
Thread.sleep(10000);
return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email);
}
}
答
您可以使用“锁定”表。它将包含您需要锁定的所有内容,即表名(例如“用户”)和行密钥(例如“电子邮件”)。
如果您需要更快的速度,那么您可以在整个服务器上使用memcached进行复制。如果你的插入不是很多,那么数据库解决方案可能会很好。
隔离级别不会帮助,因为你插入新行。除非遇到独特的限制。 –