From 2325ae403196f73865f7c6b3224db925ca406969 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Thu, 10 Jun 2021 23:35:16 +1200 Subject: [PATCH v2 1/2] Use tuple-level SIREAD locks for index-only scans. When index-only scans manage to avoid fetching heap tuples, previously we'd predicate-lock the whole heap page (since commit cdf91edb). Commits c01262a8 and 6f38d4da made it possible to lock the tuple instead with only a TID, to match the behavior of regular index scans and avoid some false conflicts. Discussion: https://postgr.es/m/CAEepm%3D2GK3FVdnt5V3d%2Bh9njWipCv_fNL%3DwjxyUhzsF%3D0PcbNg%40mail.gmail.com --- src/backend/executor/nodeIndexonlyscan.c | 12 ++++-- .../isolation/expected/index-only-scan-2.out | 15 +++++++ .../isolation/expected/index-only-scan-3.out | 16 ++++++++ src/test/isolation/isolation_schedule | 2 + .../isolation/specs/index-only-scan-2.spec | 39 +++++++++++++++++++ .../isolation/specs/index-only-scan-3.spec | 33 ++++++++++++++++ 6 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/test/isolation/expected/index-only-scan-2.out create mode 100644 src/test/isolation/expected/index-only-scan-3.out create mode 100644 src/test/isolation/specs/index-only-scan-2.spec create mode 100644 src/test/isolation/specs/index-only-scan-3.spec diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c index 0754e28a9a..f7185b4519 100644 --- a/src/backend/executor/nodeIndexonlyscan.c +++ b/src/backend/executor/nodeIndexonlyscan.c @@ -243,12 +243,16 @@ IndexOnlyNext(IndexOnlyScanState *node) /* * If we didn't access the heap, then we'll need to take a predicate - * lock explicitly, as if we had. For now we do that at page level. + * lock explicitly, as if we had. We don't have the inserter's xid, + * but that's only used by PredicateLockTID to check if it matches the + * caller's top level xid, and it can't possibly have been inserted by + * us or the page wouldn't be all visible. */ if (!tuple_from_heap) - PredicateLockPage(scandesc->heapRelation, - ItemPointerGetBlockNumber(tid), - estate->es_snapshot); + PredicateLockTID(scandesc->heapRelation, + tid, + estate->es_snapshot, + InvalidTransactionId); return slot; } diff --git a/src/test/isolation/expected/index-only-scan-2.out b/src/test/isolation/expected/index-only-scan-2.out new file mode 100644 index 0000000000..fef5b8d398 --- /dev/null +++ b/src/test/isolation/expected/index-only-scan-2.out @@ -0,0 +1,15 @@ +Parsed test spec with 2 sessions + +starting permutation: r1 r2 w1 w2 c1 c2 +step r1: SELECT id1 FROM account WHERE id1 = 1; +id1 + +1 +step r2: SELECT id2 FROM account WHERE id2 = 2; +id2 + +2 +step w1: UPDATE account SET balance = 200 WHERE id1 = 1; +step w2: UPDATE account SET balance = 200 WHERE id2 = 2; +step c1: COMMIT; +step c2: COMMIT; diff --git a/src/test/isolation/expected/index-only-scan-3.out b/src/test/isolation/expected/index-only-scan-3.out new file mode 100644 index 0000000000..efef162779 --- /dev/null +++ b/src/test/isolation/expected/index-only-scan-3.out @@ -0,0 +1,16 @@ +Parsed test spec with 2 sessions + +starting permutation: r1 r2 w1 w2 c1 c2 +step r1: SELECT id1 FROM account WHERE id1 = 2; +id1 + +2 +step r2: SELECT id2 FROM account WHERE id2 = 1; +id2 + +1 +step w1: UPDATE account SET balance = 200 WHERE id1 = 1; +step w2: UPDATE account SET balance = 200 WHERE id2 = 2; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index f4c01006fc..570aedb900 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -17,6 +17,8 @@ test: partial-index test: two-ids test: multiple-row-versions test: index-only-scan +test: index-only-scan-2 +test: index-only-scan-3 test: predicate-lock-hot-tuple test: update-conflict-out test: deadlock-simple diff --git a/src/test/isolation/specs/index-only-scan-2.spec b/src/test/isolation/specs/index-only-scan-2.spec new file mode 100644 index 0000000000..cd3c599af8 --- /dev/null +++ b/src/test/isolation/specs/index-only-scan-2.spec @@ -0,0 +1,39 @@ +# Test the granularity of predicate locks on heap tuples, for index-only scans. + +# Access the accounts through two different indexes, so that index predicate +# locks don't confuse matters; here we only want to test heap locking. Since +# s1 and s2 access different heap tuples there is no cycle, so both +# transactions should be able to commit. Before PostgreSQL 15, a serialization +# failure was reported because of a page-level SIREAD lock on the heap that +# produced a false conflict. + +setup +{ + CREATE TABLE account (id1 int, id2 int, balance int); + CREATE UNIQUE INDEX ON account(id1); + CREATE UNIQUE INDEX ON account(id2); + INSERT INTO account VALUES (1, 1, 100), (2, 2, 100); +} +setup +{ + VACUUM account; +} + +teardown +{ + DROP TABLE account; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; SET enable_seqscan = off; } +step "r1" { SELECT id1 FROM account WHERE id1 = 1; } +step "w1" { UPDATE account SET balance = 200 WHERE id1 = 1; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; SET enable_seqscan = off; } +step "r2" { SELECT id2 FROM account WHERE id2 = 2; } +step "w2" { UPDATE account SET balance = 200 WHERE id2 = 2; } +step "c2" { COMMIT; } + +permutation "r1" "r2" "w1" "w2" "c1" "c2" diff --git a/src/test/isolation/specs/index-only-scan-3.spec b/src/test/isolation/specs/index-only-scan-3.spec new file mode 100644 index 0000000000..5f04923002 --- /dev/null +++ b/src/test/isolation/specs/index-only-scan-3.spec @@ -0,0 +1,33 @@ +# A variant of index-only-scan-2.spec that is a true conflict, detected via +# heap tuple locks only. + +setup +{ + CREATE TABLE account (id1 int, id2 int, balance int); + CREATE UNIQUE INDEX ON account(id1); + CREATE UNIQUE INDEX ON account(id2); + INSERT INTO account VALUES (1, 1, 100), (2, 2, 100); +} +setup +{ + VACUUM account; +} + +teardown +{ + DROP TABLE account; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; SET enable_seqscan = off; } +step "r1" { SELECT id1 FROM account WHERE id1 = 2; } +step "w1" { UPDATE account SET balance = 200 WHERE id1 = 1; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; SET enable_seqscan = off; } +step "r2" { SELECT id2 FROM account WHERE id2 = 1; } +step "w2" { UPDATE account SET balance = 200 WHERE id2 = 2; } +step "c2" { COMMIT; } + +permutation "r1" "r2" "w1" "w2" "c1" "c2" -- 2.30.2