diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 8cbca78fb7..2293c75a9b 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -4542,6 +4542,21 @@ PreCommit_CheckForSerializationFailure(void) && !SxactIsReadOnly(farConflict->sxactOut) && !SxactIsDoomed(farConflict->sxactOut))) { + /* + * Normally, we kill the pivot transaction to make sure we + * make progress if the failing transaction is retried. + * However, we can't kill it if it's already prepared, so + * in that case we commit suicide instead. + */ + if (SxactIsPrepared(nearConflict->sxactOut)) + { + LWLockRelease(SerializableXactHashLock); + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on commit attempt with conflict in from prepared pivot."), + errhint("The transaction might succeed if retried."))); + } nearConflict->sxactOut->flags |= SXACT_FLAG_DOOMED; break; } diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out index 1a6b4ce1d9..328cd7426e 100644 --- a/src/test/regress/expected/prepared_xacts.out +++ b/src/test/regress/expected/prepared_xacts.out @@ -88,14 +88,6 @@ SELECT gid FROM pg_prepared_xacts; BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('fff'); -SELECT * FROM pxtest1; - foobar --------- - aaa - ddd - fff -(3 rows) - -- This should fail, because the gid foo3 is already in use PREPARE TRANSACTION 'foo3'; ERROR: transaction identifier "foo3" is already in use @@ -114,6 +106,49 @@ SELECT * FROM pxtest1; ddd (2 rows) +-- Test serialization failure (SSI) +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; +SELECT * FROM pxtest1; + foobar +-------- + aaa + eee +(2 rows) + +PREPARE TRANSACTION 'foo4'; +SELECT gid FROM pg_prepared_xacts; + gid +------ + foo4 +(1 row) + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * FROM pxtest1; + foobar +-------- + aaa + ddd +(2 rows) + +INSERT INTO pxtest1 VALUES ('fff'); +-- This should fail, because the two transactions have a write-skew anomaly +PREPARE TRANSACTION 'foo5'; +ERROR: could not serialize access due to read/write dependencies among transactions +DETAIL: Cancelled on commit attempt with conflict in from prepared pivot. +HINT: The transaction might succeed if retried. +SELECT gid FROM pg_prepared_xacts; + gid +------ + foo4 +(1 row) + +ROLLBACK PREPARED 'foo4'; +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + -- Clean up DROP TABLE pxtest1; -- Test subtransactions diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql index 2bdbb0d189..e06c9d47c1 100644 --- a/src/test/regress/sql/prepared_xacts.sql +++ b/src/test/regress/sql/prepared_xacts.sql @@ -54,7 +54,6 @@ SELECT gid FROM pg_prepared_xacts; BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('fff'); -SELECT * FROM pxtest1; -- This should fail, because the gid foo3 is already in use PREPARE TRANSACTION 'foo3'; @@ -65,6 +64,27 @@ ROLLBACK PREPARED 'foo3'; SELECT * FROM pxtest1; +-- Test serialization failure (SSI) +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; +SELECT * FROM pxtest1; +PREPARE TRANSACTION 'foo4'; + +SELECT gid FROM pg_prepared_xacts; + +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SELECT * FROM pxtest1; +INSERT INTO pxtest1 VALUES ('fff'); + +-- This should fail, because the two transactions have a write-skew anomaly +PREPARE TRANSACTION 'foo5'; + +SELECT gid FROM pg_prepared_xacts; + +ROLLBACK PREPARED 'foo4'; + +SELECT gid FROM pg_prepared_xacts; + -- Clean up DROP TABLE pxtest1;