Spring-JDBC中的隔离级别SERIALIZABLE

也许有人可以帮我解决Spring(3.1)/ Postgresql(8.4.11)中的事务问题

我的交易服务如下:

@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = false) @Override public Foo insertObject(Bar bar) { // these methods are just examples int x = firstDao.getMaxNumberOfAllowedObjects(bar) int y = secondDao.getNumerOfExistingObjects(bar) // comparison if (x - y > 0){ secondDao.insertNewObject(...) } .... } 

Spring配置Webapp包含:

 @Configuration @EnableTransactionManagement public class ....{ @Bean public DataSource dataSource() { org.apache.tomcat.jdbc.pool.DataSource ds = new DataSource(); ....configuration details return ds; } @Bean public DataSourceTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } } 

让我们说请求“x”和请求“y”同时执行并到达注释“比较”(方法insertObject)。 然后允许它们都插入一个新对象并提交它们的事务。

为什么我没有RollbackException? 据我所知,这是Serializable isolotation级别的用途。 回到上一个场景,如果x设法插入一个新对象并提交其事务,那么不应该允许“y”的事务提交,因为有一个他没有读过的新对象。

也就是说,如果“y”再次读取secondDao.getNumerOfExistingObjects(bar)的值,它将意识到有更多的新对象。 幻影?

事务配置似乎工作正常:

  • 对于每个请求,我可以看到firstDao和secondDao的相同连接
  • 每次调用insertObject时都会创建一个事务

第一个和第二个DAO如下:

 @Autowired public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public Object daoMethod(Object param) { //uses jdbcTemplate } 

我相信我错过了一些东西。 任何想法?

谢谢你的时间,

哈维尔

TL; DR:Pg 9.1中可检测性的可串行性冲突得到了显着改善,因此升级。


从您的描述中弄清楚实际的SQL是什么以及您期望获得回滚的原因是很棘手的。 看起来你已经严重误解了可序列化的隔离,或许认为它完美地测试了所有的谓词,但它没有,特别是在Pg 8.4中。

SERIALIZABLE并不能完美地保证事务的执行就好像它们是串行运行一样 – 如果它完全可能的话,从性能的角度来看这样做会非常昂贵。 它只提供有限的检查。 确切地说检查了什么以及数据库与数据库以及版本之间的差异如何,因此您需要阅读适用于您的数据库版本的文档。

exception是可能的,其中以SERIALIZABLE模式执行的两个事务产生不同的结果,如果这些事务真正串行执行。

阅读Pg中有关事务隔离的文档以了解更多信息。 请注意, SERIALIZABLE在Pg 9.1中显着改变了行为,因此请务必阅读适合您的Pg版本的手册版本。 这是8.4版本 。 特别是阅读13.2.2.1。 可序列化隔离与真正可串行化 。 现在将其与Pg 9.1文档中描述的大大改进的基于谓词锁定的序列化支持进行比较。

看起来你正在尝试执行类似这种伪代码的逻辑:

 count = query("SELECT count(*) FROM the_table"); if (count < threshold): query("INSERT INTO the_table (...) VALUES (...)"); 

如果是这样,那么当并发执行时,这不会在Pg 8.4中起作用 - 它与上面链接的文档中使用的exception示例几乎相同。 令人惊讶的是它实际上适用于Pg 9.1; 我没想到甚至9.1的谓词锁定来捕获聚合的使用。

你写的是:

回到上一个场景,如果x设法插入一个新对象并提交其事务,那么不应该允许“y”的事务提交,因为有一个他没有读过的新对象。

但是8.4不会检测到这两个事务是相互依赖的,你可以通过使用两个psql会话来测试它。 只有在9.1中引入的真正可串行化的东西才能实现 - 坦率地说,我很惊讶它在9.1中工作。

如果你想在Pg 8.4中强制执行最大行数,你需要LOCK表以防止并发INSERT ,手动或通过触发函数进行锁定。 在触发器中执行它本身就需要锁定升级,因此会经常死锁,但会成功完成工作。 最好在应用程序中完成,你可以在从表中获取甚至SELECT之前发出LOCK TABLE my_table IN EXCLUSIVE MODE ,因此它已经具有表所需的最高锁模式,因此不需要容易死锁的锁推广。 EXCLUSIVE锁定模式是合适的,因为它允许SELECT但没有别的。

以下是在两个psql会话中测试它的方法:

 SESSION 1 SESSION 2 create table ser_test( x text ); BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT count(*) FROM ser_test ; SELECT count(*) FROM ser_test ; INSERT INTO ser_test(x) VALUES ('bob'); INSERT INTO ser_test(x) VALUES ('bob'); COMMIT; COMMIT; 

当在Pg 9.1上运行时, st commits succeeds then the second COMMIT`失败:

 regress=# COMMIT; ERROR: could not serialize access due to read/write dependencies among transactions DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt. HINT: The transaction might succeed if retried. 

但是当在8.4上运行时,两个提交提交都成功,因为8.4没有在9.1中添加可序列化的所有谓词锁定代码。