]> granicus.if.org Git - postgresql/commitdiff
Fix serializable mode with index-only scans.
authorKevin Grittner <kgrittn@postgresql.org>
Wed, 5 Sep 2012 02:13:11 +0000 (21:13 -0500)
committerKevin Grittner <kgrittn@postgresql.org>
Wed, 5 Sep 2012 02:13:11 +0000 (21:13 -0500)
Serializable Snapshot Isolation used for serializable transactions
depends on acquiring SIRead locks on all heap relation tuples which
are used to generate the query result, so that a later delete or
update of any of the tuples can flag a read-write conflict between
transactions.  This is normally handled in heapam.c, with tuple level
locking.  Since an index-only scan avoids heap access in many cases,
building the result from the index tuple, the necessary predicate
locks were not being acquired for all tuples in an index-only scan.

To prevent problems with tuple IDs which are vacuumed and re-used
while the transaction still matters, the xmin of the tuple is part of
the tag for the tuple lock.  Since xmin is not available to the
index-only scan for result rows generated from the index tuples, it
is not possible to acquire a tuple-level predicate lock in such
cases, in spite of having the tid.  If we went to the heap to get the
xmin value, it would no longer be an index-only scan.  Rather than
prohibit index-only scans under serializable transaction isolation,
we acquire an SIRead lock on the page containing the tuple, when it
was not necessary to visit the heap for other reasons.

Backpatch to 9.2.

Kevin Grittner and Tom Lane

src/backend/executor/nodeIndexonlyscan.c
src/test/isolation/expected/index-only-scan.out [new file with mode: 0644]
src/test/isolation/isolation_schedule
src/test/isolation/specs/index-only-scan.spec [new file with mode: 0644]

index 38078763f57e8bdbcb1141bb819f8389a4a5963d..e72ebc8c3a8f74006fce657ccb2eddc0413725a5 100644 (file)
@@ -30,6 +30,7 @@
 #include "executor/nodeIndexonlyscan.h"
 #include "executor/nodeIndexscan.h"
 #include "storage/bufmgr.h"
+#include "storage/predicate.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -52,7 +53,6 @@ IndexOnlyNext(IndexOnlyScanState *node)
        ExprContext *econtext;
        ScanDirection direction;
        IndexScanDesc scandesc;
-       HeapTuple       tuple;
        TupleTableSlot *slot;
        ItemPointer tid;
 
@@ -78,6 +78,8 @@ IndexOnlyNext(IndexOnlyScanState *node)
         */
        while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
        {
+               HeapTuple       tuple = NULL;
+
                /*
                 * We can skip the heap fetch if the TID references a heap page on
                 * which all tuples are known visible to everybody.  In any case,
@@ -147,6 +149,18 @@ IndexOnlyNext(IndexOnlyScanState *node)
                        }
                }
 
+               /*
+                * Predicate locks for index-only scans must be acquired at the page
+                * level when the heap is not accessed, since tuple-level predicate
+                * locks need the tuple's xmin value.  If we had to visit the tuple
+                * anyway, then we already have the tuple-level lock and can skip the
+                * page lock.
+                */
+               if (tuple == NULL)
+                       PredicateLockPage(scandesc->heapRelation,
+                                                         ItemPointerGetBlockNumber(tid),
+                                                         estate->es_snapshot);
+
                return slot;
        }
 
diff --git a/src/test/isolation/expected/index-only-scan.out b/src/test/isolation/expected/index-only-scan.out
new file mode 100644 (file)
index 0000000..47983eb
--- /dev/null
@@ -0,0 +1,41 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rxwy1 c1 rywx2 c2
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c1: COMMIT;
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c2: COMMIT;
+
+starting permutation: rxwy1 rywx2 c1 c2
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR:  could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy1 rywx2 c2 c1
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR:  could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rywx2 rxwy1 c1 c2
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR:  could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rywx2 rxwy1 c2 c1
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR:  could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rywx2 c2 rxwy1 c1
+step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+step c2: COMMIT;
+step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+step c1: COMMIT;
index 2184975dcb12e3a20e3e37f724985798a4df58eb..75e33bc99fc30a1116d5676bbb0fe72c0da3f495 100644 (file)
@@ -9,6 +9,7 @@ test: ri-trigger
 test: partial-index
 test: two-ids
 test: multiple-row-versions
+test: index-only-scan
 test: fk-contention
 test: fk-deadlock
 test: fk-deadlock2
diff --git a/src/test/isolation/specs/index-only-scan.spec b/src/test/isolation/specs/index-only-scan.spec
new file mode 100644 (file)
index 0000000..417bb02
--- /dev/null
@@ -0,0 +1,46 @@
+# index-only scan test
+#
+# This test tries to expose problems with the interaction between index-only
+# scans and SSI.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+  CREATE TABLE tabx (id int NOT NULL);
+  INSERT INTO tabx SELECT generate_series(1,10000);
+  ALTER TABLE tabx ADD PRIMARY KEY (id);
+  CREATE TABLE taby (id int NOT NULL);
+  INSERT INTO taby SELECT generate_series(1,10000);
+  ALTER TABLE taby ADD PRIMARY KEY (id);
+}
+setup { VACUUM FREEZE ANALYZE tabx; }
+setup { VACUUM FREEZE ANALYZE taby; }
+
+teardown
+{
+  DROP TABLE tabx;
+  DROP TABLE taby;
+}
+
+session "s1"
+setup
+{
+  BEGIN ISOLATION LEVEL SERIALIZABLE;
+  SET LOCAL seq_page_cost = 0.1;
+  SET LOCAL random_page_cost = 0.1;
+  SET LOCAL cpu_tuple_cost = 0.03;
+}
+step "rxwy1" { DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup
+{
+  BEGIN ISOLATION LEVEL SERIALIZABLE;
+  SET LOCAL seq_page_cost = 0.1;
+  SET LOCAL random_page_cost = 0.1;
+  SET LOCAL cpu_tuple_cost = 0.03;
+}
+step "rywx2" { DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby); }
+step "c2" { COMMIT; }