小编典典

Spring-JDBC中的隔离级别SERIALIZABLE

java

也许有人可以帮助我解决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?据我所知,这就是可序列化等值线的用途。回到前面的场景,如果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

}

我确定我缺少什么。任何想法?

谢谢你的时间,

哈维尔


阅读 212

收藏
2020-11-19

共1个答案

小编典典

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中引入了true-
serializability才可以正常工作-坦率地说,我很惊讶它在9.1中可以工作。

如果要执行第8.4页中的强制最大行数之类的操作,则需要对LOCK进行阻止以防止并发INSERTs,可以手动或通过触发函数进行锁定。在触发器中执行此操作本质上需要提升锁,因此经常会死锁,但会成功完成此工作。最好在应用程序中完成,在该应用程序中您可以LOCK TABLE my_table IN EXCLUSIVE MODESELECT从表获得偶数之前发出,因此它已经具有在表上需要的最高锁定模式,因此不需要死锁倾向的锁升级。的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 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中添加所有可序列化的谓词锁定代码。

2020-11-19