更新:我最终设法以最小的设置重现了这一点,我将其作为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
的行上。我在最后提供了实际的堆栈跟踪。
在Connection
的实施中,conn
SERIALIZABLE
对象的事务隔离级别设置为getConnection()
:
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)
答案 0 :(得分:11)
每当您请求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: 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
不像原子一样你想。