5

也许有人可以帮助我解决 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”同时执行并到达注释“comparison”(方法 insertObject)。然后他们都被允许插入一个新对象并提交他们的事务。

为什么我没有 RollbackException?据我所知,这就是 Serializable 隔离级别的用途。回到前面的场景,如果 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

}

我确定我错过了一些东西。任何的想法?

谢谢你的时间,

哈维尔

4

1 回答 1

8

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


从您的描述中弄清楚实际的 SQL 是什么以及您期望得到回滚的原因是很棘手的。看起来您严重误解了可序列化隔离,也许认为它完美地测试了所有谓词,但事实并非如此,尤其是在 Pg 8.4 中。

SERIALIZABLE并不能完全保证事务像串行运行一样执行——因为如果可能的话,从性能的角度来看这样做会非常昂贵。它只提供有限的检查。确切检查的内容以及从数据库到数据库以及版本到版本的差异,因此您需要阅读您的数据库版本的文档。

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

阅读 Pg 中有关事务隔离的文档以了解更多信息。请注意,SERIALIZABLEPg 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 中同时执行时将不起作用 - 它与上面链接的文档中使用的异常示例几乎相同。令人惊讶的是,它实际上适用于 Pg 9.1;我什至没想到 9.1 的谓词锁定会捕获聚合的使用。

你这样写:

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

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

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

以下是如何在两个 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 secondCOMMIT` 失败并显示:

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 中添加所有用于可序列化的谓词锁定代码。

于 2012-10-11T01:47:58.377 回答