我最终设法将this SO question中的序列化问题重现为SSCCE(最短自包含完整示例)。我正在使用jdbc
和java
标签,但我相信这不是Java或JDBC特定的。
所以,我有两个表MASTERTABLE和DETAILTABLE。 DETAILTABLE中的行挂在MASTERTABLE中的行下:
CREATE TABLE public.MASTERTABLE(typename VARCHAR, i INTEGER);
ALTER TABLE public.MASTERTABLE ADD PRIMARY KEY (typename, i);
CREATE TABLE public.DETAILTABLE(typename VARCHAR, i INTEGER, j INTEGER);
ALTER TABLE public.DETAILTABLE ADD PRIMARY KEY (typename, i, j);
ALTER TABLE public.DETAILTABLE ADD CONSTRAINT detail_2_master FOREIGN KEY (typename, i) REFERENCES public.MASTERTABLE(typename, i);
然后我有代码(最后附加),它在无限循环中执行以下操作:
typename
当我在两个单独的实例中启动代码时,在几分钟后使用不同的typename
参数(比如一个使用type-a
和另一个type-b
)同时运行,我得到了" could not serialize access due to read/write dependencies among transactions
"消息(最后的跟踪)。
我不知道的是为什么PostgreSQL对这两个并发交易感到困惑,因为它们显然是在访问不同的"切片"表:
typename
值为" type-a
" typename
值" type-b
" 此外,PostgreSQL知道typename
是主表和详细信息表的主键的一部分,因此访问的行集保证不相交。
我附加在代码下面,这是一个导致错误的典型调用,以及抛出的跟踪。
如果修改以下代码以使用事务隔离级别Connection.TRANSACTION_REPEATABLE_READ
而不是Connection.TRANSACTION_SERIALIZABLE
,则不会触发错误条件。我没有在重新运行之前包含用于清空表的代码,因为我试图尽可能地缩短它。
import javax.sql.*;
import java.sql.*;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbutils.DbUtils;
public class FooMain {
public static void main(String args[]) throws Exception {
String typeName = args[0];
Integer transIsoLevel = Connection.TRANSACTION_SERIALIZABLE;
DataSource ds = getDataSource("jdbc:postgresql://localhost:5432/your-database", "your-user-name", "your-user-password");
Testing testingAPI = new Testing(ds, transIsoLevel);
for (int i = 0 ; i < 1000*1000 ; i++) {
System.out.printf("%s - %d\n", typeName, i);
testingAPI.addMasterRow(typeName, i);
for (int j = 0 ; j < 100 ; j++)
testingAPI.addDetailRow(typeName, i);
}
}
public static DataSource getDataSource(String dbURL, String user, String pwd) {
BasicDataSource dS = new BasicDataSource();
dS.setDriverClassName("org.postgresql.Driver");
dS.setUsername(user);
dS.setPassword(pwd);
dS.setUrl(dbURL);
dS.setMaxActive(1);
dS.setMaxIdle(1);
dS.setInitialSize(1);
dS.setValidationQuery("SELECT 1");
return dS;
}
}
class Testing {
private DataSource ds;
private int transactionLevel;
public Testing(DataSource ds, int transactionLevel) {
this.ds = ds;
this.transactionLevel = transactionLevel;
}
private Connection getConnection() throws SQLException {
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
conn.setTransactionIsolation(transactionLevel);
return conn;
}
public void addMasterRow(String typeName, int i) throws SQLException {
Connection conn = getConnection();
PreparedStatement ps1 = null;
ResultSet rs1 = null;
PreparedStatement ps2= null;
try {
conn.commit();
{ // this select statement does nothing, but I believe it is needed
// to trigger the error
String SQL="SELECT COUNT(*) FROM public.mastertable "+
"WHERE typename=? ";
ps1 = conn.prepareStatement(SQL);
ps1.setString (1, typeName);
rs1 = ps1.executeQuery();
}
{
String SQL="INSERT INTO public.mastertable(typename, i) "+
"VALUES(?, ?) ";
ps2 = conn.prepareStatement(SQL);
ps2.setString ( 1, typeName);
ps2.setInt ( 2, i);
ps2.executeUpdate();
}
conn.commit();
} finally {
DbUtils.closeQuietly( conn, ps1, rs1);
DbUtils.closeQuietly((Connection) null, ps2, (ResultSet) null);
}
}
public void addDetailRow(String typeName, int i) throws SQLException {
Connection conn = getConnection();
PreparedStatement ps = null;
try {
conn.commit();
String SQL="INSERT INTO public.detailtable(typename, i, j) "+
"(SELECT ?, ?, COALESCE(MAX(j)+1,0) FROM public.detailtable "+
"WHERE typename=? AND i=?) ";
ps = conn.prepareStatement(SQL);
ps.setString ( 1, typeName);
ps.setInt ( 2, i);
ps.setString ( 3, typeName);
ps.setInt ( 4, i);
ps.executeUpdate();
conn.commit();
} finally {
DbUtils.closeQuietly(conn, ps, (ResultSet) null);
}
}
}
只需同时调用两个实例,一个命令行参数为&#39; type-a&#39;另一个命令行参数为&#39; type-b&#39;:
java -classpath ... FooMain type-a &
java -classpath ... FooMain type-b &
Exception in thread "main" org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
Detail: Reason code: Canceled on conflict out to pivot 4720754, during read.
Hint: The transaction might succeed if retried.
Where: SQL statement "SELECT 1 FROM ONLY "public"."mastertable" x WHERE "typename"::pg_catalog.text OPERATOR(pg_catalog.=) $1::pg_catalog.text AND "i" OPERATOR(pg_catalog.=) $2 FOR SHARE OF x"
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:500)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:388)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:334)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
at Testing.addDetailRow(FooMain.java:98)
at FooMain.main(FooMain.java:18)