org.postgresql.util.PSQLException:错误:由于事务之间的读/写依赖关系,无法序列化访问

Posted

技术标签:

【中文标题】org.postgresql.util.PSQLException:错误:由于事务之间的读/写依赖关系,无法序列化访问【英文标题】:org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions 【发布时间】:2014-02-11 16:03:40 【问题描述】:

更新:我最终设法在一个最小的设置中重现了这个,我发布为separate question。

当我从在同一个 PostgreSQL 实例和表上并排运行的两个不同应用程序执行 JDBC 插入时遇到以下异常:

 org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
 [java] ERROR>  Detail: Reason code: Canceled on identification as a pivot, during write.
 [java] ERROR>  Hint: The transaction might succeed if retried.

尝试执行以下语句时发生异常:

public int logRepositoryOperationStart(String repoIvoid, MetadataPrefix prefix, RepositoryOperation operation, int pid, String command, String from_XMLGregCal) throws SQLException 
    Connection        conn = null;
    PreparedStatement ps   = null;
    try 
        conn = getConnection();
        conn.commit();
        String SQL = "INSERT INTO vo_business.repositoryoperation(ivoid, metadataprefix, operation, i, pid, command, from_xmlgregcal, start_sse)  "+
                     "(SELECT ?, ?, ?, COALESCE(MAX(i)+1,0), ?, ?, ?, ? FROM vo_business.repositoryoperation                                      "+
                     "WHERE ivoid=? AND metadataprefix=? AND operation=?)                                                                         ";
        ps = conn.prepareStatement(SQL);
        ps.setString(1, repoIvoid);
        ps.setString(2, prefix.value());
        ps.setString(3, operation.value());
        ps.setInt   (4, pid);
        ps.setString(5, command);
        ps.setString(6, from_XMLGregCal);
        ps.setInt   (7, Util.castToIntWithChecks(TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS)));
        ps.setString(8, repoIvoid);
        ps.setString(9, prefix.value());
        ps.setString(10, operation.value());
        if (ps.executeUpdate() != 1)  // line 217
            conn.rollback();
            throw new RuntimeException();
        
        conn.commit();
        return getMaxI(conn, repoIvoid, prefix, operation);
     catch (SQLException e) 
        conn.rollback();
        throw e;
     finally 
        DbUtils.closeQuietly(conn, ps, (ResultSet) null);
    


.. 上面标有line-217 的行。我在最后提供了实际的堆栈跟踪。

Connectionconn对象的事务隔离级别在getConnection()的实现中设置为SERIALIZABLE

protected Connection getConnection() throws SQLException 
    Connection conn = ds.getConnection();
    conn.setAutoCommit(false);
    conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
    return conn;

很可能另一个应用程序也试图同时在同一个表上写入,尽管它确实提供了不同的operation 字段,所以我看不出如何发生任何混淆。此外,这是一个单一的原子插入,所以我看不出访问序列化是如何发挥作用的。

这是什么类型的错误,我应该如何解决这个问题?我是否应该查看事务隔离级别、全表与特定于行的锁(如果 PostgreSQL 中有这样的概念)等?我应该重试吗(提示说“如果重试,事务可能会成功。”)。我会尝试在 SSCCE 中重现它,但我只是发布它以防它有明显的原因/解决方案

 [java] ERROR>org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
 [java] ERROR>  Detail: Reason code: Canceled on identification as a pivot, during write.
 [java] ERROR>  Hint: The transaction might succeed if retried.
 [java] ERROR>  at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
 [java] ERROR>  at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
 [java] ERROR>  at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
 [java] ERROR>  at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:500)
 [java] ERROR>  at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:388)
 [java] ERROR>  at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:334)
 [java] ERROR>  at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
 [java] ERROR>  at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
 [java] ERROR>  at _int.esa.esavo.dbbusiness.DBBusiness.logRepositoryOperationStart(DBBusiness.java:217)
 [java] ERROR>  at _int.esa.esavo.harvesting.H.main(H.java:278)

【问题讨论】:

【参考方案1】:

每当您请求SERIALIZABLE 隔离时,数据库将尝试根据它们产生的结果来创建并发查询集似乎已串行执行。这并不总是可能的,例如当两个事务相互依赖时。在这种情况下,PostgreSQL 将中止其中一个事务并出现序列化失败错误,告诉您应该重试。

使用SERIALIZABLE 的代码必须始终准备好重试事务。它必须检查SQLSTATE,并且对于序列化失败,重复事务。

见the transaction isolation documentation。

在这种情况下,我认为您的主要误解可能是:

这是一个单一的原子插入

因为它不是那种东西,它是一个INSERT ... SELECT,在阅读和写作方面都触及vo_business.repositoryoperation。这足以与另一个执行相同操作的事务或以另一种方式读取和写入表的事务建立潜在的依赖关系。

此外,出于效率原因,可序列化的隔离代码在某些情况下可以退化为保存块级依赖信息。所以它可能不一定是涉及相同行的事务,只是相同的存储块,尤其是在负载下。

如果不确定它是否安全,PostgreSQL 会更愿意中止可序列化的事务。证明系统有局限性。所以也有可能你刚刚发现了一个愚弄它的案例。

要确定我需要并排查看两笔交易,但这里有一个证据表明 insert ... select 可能与其自身发生冲突。打开三个psql 会话并运行:

session0: CREATE TABLE serialdemo(x integer, y integer);

session0: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

session0: LOCK TABLE serialdemo IN ACCESS EXCLUSIVE MODE;

session1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

session2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

session1: INSERT INTO serialdemo (x, y)
          SELECT 1, 2
          WHERE NOT EXISTS (SELECT 1 FROM serialdemo WHERE x = 1);

session2: INSERT INTO serialdemo (x, y)
          SELECT 1, 2
          WHERE NOT EXISTS (SELECT 1 FROM serialdemo WHERE x = 1);

session0: ROLLBACK;

session1: COMMIT;

session2: COMMIT;

session1 会正常提交。 session2 将失败:

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.

这与您的情况不同的序列化失败,并且不能证明 您的 语句可以相互冲突,但它表明 insert ... select 并不像您想象的那样原子.

【讨论】:

那么,SERIALIZABLE 是否可能将标准设置得太高?我是否应该选择较低的事务隔离设置(因为 PostgreSQL 也默认为“已提交读”,比“可序列化”低两个档次)。当一个平台,任何平台,要求我重试时,我也很担心。如果总是重试是 100% 正确的,那么我不明白为什么数据库不能重试。因此,我不确定是否应该盲目重试。 @MarcusJuniusBrutus 数据库无法重试,因为它还没有记录您到目前为止所做的所有 SQL 语句,它会中止整个事务,而不仅仅是当前语句。它希望您记住您想做的工作并重播它。这就是规范所说的,这意味着数据库不必支付在最常见的正常提交情况下记住一系列语句的成本。 关于您对不是单个原子插入的评论,我不确定这是否正确。这是一个原子声明。它不像单独的选择和单独的插入(这是此处显示的示例:postgresql.org/docs/9.1/static/transaction-iso.html)。事实上,我确实尝试使用完全相同的 INSERT/SELECT 子句结构(没有特定于域的细节)来缩小范围,即使在重负载下我也无法重现它。部分问题是在调试时 PostgreSQL 没有告诉你哪个是 OTHER 事务/语句。 @MarcusJuniusBrutus 除了LOCK TABLE ... IN EXCLUSIVE MODE 之外,并发行为没有简单的方法。如果您降低隔离级别,则不会出现序列化失败,但您确实可以在读取提交模式下处理允许的事务异常,这允许各种有趣的竞争条件。 (顺便说一句,是的,我同意它没有告诉您其他语句在解决死锁时的作用。问题是它不是另一个 statement,它是另一个 交易。) @MarcusJuniusBrutus 实际上,您的语句很像单独的选择和单独的插入-它在SERIALIZABLE 隔离中创建了读写依赖关系,并且SELECT 出现在INSERT 之前。 ACID 的“原子性”是指atomic commit,记住,它不是并发问题的自动退出。

以上是关于org.postgresql.util.PSQLException:错误:由于事务之间的读/写依赖关系,无法序列化访问的主要内容,如果未能解决你的问题,请参考以下文章