diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 8b488cfd8f6..eda0445550b 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1224,19 +1224,23 @@ heap_beginscan(Relation relation, Snapshot snapshot, } /* - * For seqscan and sample scans in a serializable transaction, acquire a - * predicate lock on the entire relation. This is required not only to - * lock all the matching tuples, but also to conflict with new insertions - * into the table. In an indexscan, we take page locks on the index pages - * covering the range specified in the scan qual, but in a heap scan there - * is nothing more fine-grained to lock. A bitmap scan is a different - * story, there we have already scanned the index and locked the index - * pages covering the predicate. But in that case we still have to lock - * any matching heap tuples. For sample scan we could optimize the locking - * to be at least page-level granularity, but we'd need to add per-tuple - * locking for that. - */ - if (scan->rs_base.rs_flags & (SO_TYPE_SEQSCAN | SO_TYPE_SAMPLESCAN)) + * For seqscan, sample and TID range scans in a serializable transaction, + * acquire a predicate lock on the entire relation. This is required not + * only to lock all the matching tuples, but also to conflict with new + * insertions into the table. In an indexscan, we take page locks on the + * index pages covering the range specified in the scan qual, but in a + * heap scan there is nothing more fine-grained to lock. A bitmap scan is + * a different story, there we have already scanned the index and locked + * the index pages covering the predicate. But in that case we still have + * to lock any matching heap tuples. For sample scan we could optimize the + * locking to be at least page-level granularity, but we'd need to add + * per-tuple locking for that. A TID range scan is like a seqscan in this + * respect: it reads heap blocks directly with no index involved, so there + * is nothing finer to lock, and heap_insert() only checks for conflicts + * against relation-level predicate locks anyway. + */ + if (scan->rs_base.rs_flags & (SO_TYPE_SEQSCAN | SO_TYPE_SAMPLESCAN | + SO_TYPE_TIDRANGESCAN)) { /* * Ensure a missing snapshot is noticed reliably, even if the diff --git a/src/test/isolation/expected/predicate-tidrangescan.out b/src/test/isolation/expected/predicate-tidrangescan.out new file mode 100644 index 00000000000..57200e51bdd --- /dev/null +++ b/src/test/isolation/expected/predicate-tidrangescan.out @@ -0,0 +1,106 @@ +Parsed test spec with 2 sessions + +starting permutation: rxy1 rxy2 wx1 wy2 c1 c2 +step rxy1: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step rxy2: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step wx1: update tidrange_tbl set p = 1 where ctid = '(0,1)'; +step wy2: update tidrange_tbl set p = 1 where ctid = '(0,2)'; +step c1: commit; +step c2: commit; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 rxy2 wy2 wx1 c1 c2 +step rxy1: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step rxy2: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step wy2: update tidrange_tbl set p = 1 where ctid = '(0,2)'; +step wx1: update tidrange_tbl set p = 1 where ctid = '(0,1)'; +step c1: commit; +step c2: commit; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy2 rxy1 wx1 wy2 c2 c1 +step rxy2: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step rxy1: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step wx1: update tidrange_tbl set p = 1 where ctid = '(0,1)'; +step wy2: update tidrange_tbl set p = 1 where ctid = '(0,2)'; +step c2: commit; +step c1: commit; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 rxy2 wi1 wi2 c1 c2 +step rxy1: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step rxy2: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step wi1: insert into tidrange_tbl values (3, 10); +step wi2: insert into tidrange_tbl values (4, 20); +step c1: commit; +step c2: commit; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy2 rxy1 wi2 wi1 c2 c1 +step rxy2: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step rxy1: select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; +sum +--- + 0 +(1 row) + +step wi2: insert into tidrange_tbl values (4, 20); +step wi1: insert into tidrange_tbl values (3, 10); +step c2: commit; +step c1: 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 b8ebe92553c..9f3b85d2a06 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -105,6 +105,7 @@ test: vacuum-conflict test: vacuum-skip-locked test: stats test: horizons +test: predicate-tidrangescan test: predicate-hash test: predicate-gist test: predicate-gin diff --git a/src/test/isolation/specs/predicate-tidrangescan.spec b/src/test/isolation/specs/predicate-tidrangescan.spec new file mode 100644 index 00000000000..e7944dd3ba8 --- /dev/null +++ b/src/test/isolation/specs/predicate-tidrangescan.spec @@ -0,0 +1,56 @@ +# Test for relation level predicate locking in TID range scans +# +# A TID range scan reads a range of heap blocks directly, with no index +# involved, so like a sequential scan it has nothing finer to lock than the +# whole relation. Verify that the relation level SIREAD lock is acquired, by +# checking that write skew and phantom rows seen through a TID range scan are +# detected. + +setup +{ + create table tidrange_tbl (id int, p int); + insert into tidrange_tbl values (1, 0), (2, 0); +} + +teardown +{ + drop table tidrange_tbl; +} + +session s1 +setup +{ + begin isolation level serializable; + set enable_seqscan = off; +} +step rxy1 { select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; } +step wx1 { update tidrange_tbl set p = 1 where ctid = '(0,1)'; } +step wi1 { insert into tidrange_tbl values (3, 10); } +step c1 { commit; } + +session s2 +setup +{ + begin isolation level serializable; + set enable_seqscan = off; +} +step rxy2 { select sum(p) from tidrange_tbl + where ctid >= '(0,0)' and ctid < '(1,0)'; } +step wy2 { update tidrange_tbl set p = 1 where ctid = '(0,2)'; } +step wi2 { insert into tidrange_tbl values (4, 20); } +step c2 { commit; } + +# Both transactions read the whole TID range, then each updates a row that the +# other one read. No serial order produces sum(p) = 2, so one of them has to +# be aborted. + +permutation rxy1 rxy2 wx1 wy2 c1 c2 +permutation rxy1 rxy2 wy2 wx1 c1 c2 +permutation rxy2 rxy1 wx1 wy2 c2 c1 + +# Both transactions read the whole TID range, then each inserts a row that +# falls inside the range the other one read. + +permutation rxy1 rxy2 wi1 wi2 c1 c2 +permutation rxy2 rxy1 wi2 wi1 c2 c1