mirror of https://github.com/postgres/postgres
Under some circumstances, concurrent MERGE operations could lead to inconsistent results, that varied according the plan chosen. This was caused by a lack of rowmarks on the source relation, which meant that EvalPlanQual rechecking was not guaranteed to return the same source tuples when re-running the join query. Fix by ensuring that preprocess_rowmarks() sets up PlanRowMarks for all non-target relations used in MERGE, in the same way that it does for UPDATE and DELETE. Per bug #18103. Back-patch to v15, where MERGE was introduced. Dean Rasheed, reviewed by Richard Guo. Discussion: https://postgr.es/m/18103-c4386baab8e355e3%40postgresql.orgpull/144/head
parent
f02154652d
commit
1d5caec221
@ -0,0 +1,148 @@ |
||||
Parsed test spec with 2 sessions |
||||
|
||||
starting permutation: b1 m1 s1 c1 b2 m2 s2 c2 |
||||
step b1: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step m1: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
step s1: SELECT * FROM tgt; |
||||
id|val |
||||
--+--- |
||||
1| 10 |
||||
2| 20 |
||||
3| 30 |
||||
(3 rows) |
||||
|
||||
step c1: COMMIT; |
||||
step b2: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step m2: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
step s2: SELECT * FROM tgt; |
||||
id|val |
||||
--+--- |
||||
1| 10 |
||||
2| 20 |
||||
3| 30 |
||||
(3 rows) |
||||
|
||||
step c2: COMMIT; |
||||
|
||||
starting permutation: b1 b2 m1 hj ex m2 c1 c2 s1 |
||||
step b1: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step b2: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step m1: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
step hj: SET LOCAL enable_mergejoin = off; SET LOCAL enable_nestloop = off; |
||||
step ex: EXPLAIN (verbose, costs off) |
||||
MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
QUERY PLAN |
||||
--------------------------------------------------- |
||||
Merge on public.tgt |
||||
-> Hash Left Join |
||||
Output: tgt.ctid, src.val, src.id, src.ctid |
||||
Inner Unique: true |
||||
Hash Cond: (src.id = tgt.id) |
||||
-> Seq Scan on public.src |
||||
Output: src.val, src.id, src.ctid |
||||
-> Hash |
||||
Output: tgt.ctid, tgt.id |
||||
-> Seq Scan on public.tgt |
||||
Output: tgt.ctid, tgt.id |
||||
(11 rows) |
||||
|
||||
step m2: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); <waiting ...> |
||||
step c1: COMMIT; |
||||
step m2: <... completed> |
||||
step c2: COMMIT; |
||||
step s1: SELECT * FROM tgt; |
||||
id|val |
||||
--+--- |
||||
1| 10 |
||||
2| 20 |
||||
3| 30 |
||||
(3 rows) |
||||
|
||||
|
||||
starting permutation: b1 b2 m1 mj ex m2 c1 c2 s1 |
||||
step b1: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step b2: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step m1: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
step mj: SET LOCAL enable_hashjoin = off; SET LOCAL enable_nestloop = off; |
||||
step ex: EXPLAIN (verbose, costs off) |
||||
MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
QUERY PLAN |
||||
--------------------------------------------------- |
||||
Merge on public.tgt |
||||
-> Merge Left Join |
||||
Output: tgt.ctid, src.val, src.id, src.ctid |
||||
Inner Unique: true |
||||
Merge Cond: (src.id = tgt.id) |
||||
-> Index Scan using src_pkey on public.src |
||||
Output: src.val, src.id, src.ctid |
||||
-> Index Scan using tgt_pkey on public.tgt |
||||
Output: tgt.ctid, tgt.id |
||||
(9 rows) |
||||
|
||||
step m2: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); <waiting ...> |
||||
step c1: COMMIT; |
||||
step m2: <... completed> |
||||
step c2: COMMIT; |
||||
step s1: SELECT * FROM tgt; |
||||
id|val |
||||
--+--- |
||||
1| 10 |
||||
2| 20 |
||||
3| 30 |
||||
(3 rows) |
||||
|
||||
|
||||
starting permutation: b1 b2 m1 nl ex m2 c1 c2 s1 |
||||
step b1: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step b2: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step m1: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
step nl: SET LOCAL enable_hashjoin = off; SET LOCAL enable_mergejoin = off; |
||||
step ex: EXPLAIN (verbose, costs off) |
||||
MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); |
||||
QUERY PLAN |
||||
--------------------------------------------------- |
||||
Merge on public.tgt |
||||
-> Nested Loop Left Join |
||||
Output: tgt.ctid, src.val, src.id, src.ctid |
||||
Inner Unique: true |
||||
-> Seq Scan on public.src |
||||
Output: src.val, src.id, src.ctid |
||||
-> Index Scan using tgt_pkey on public.tgt |
||||
Output: tgt.ctid, tgt.id |
||||
Index Cond: (tgt.id = src.id) |
||||
(9 rows) |
||||
|
||||
step m2: MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); <waiting ...> |
||||
step c1: COMMIT; |
||||
step m2: <... completed> |
||||
step c2: COMMIT; |
||||
step s1: SELECT * FROM tgt; |
||||
id|val |
||||
--+--- |
||||
1| 10 |
||||
2| 20 |
||||
3| 30 |
||||
(3 rows) |
||||
|
@ -0,0 +1,45 @@ |
||||
# MERGE JOIN |
||||
# |
||||
# This test checks the EPQ recheck mechanism during MERGE when joining to a |
||||
# source table using different join methods, per bug #18103 |
||||
|
||||
setup |
||||
{ |
||||
CREATE TABLE src (id int PRIMARY KEY, val int); |
||||
CREATE TABLE tgt (id int PRIMARY KEY, val int); |
||||
INSERT INTO src SELECT x, x*10 FROM generate_series(1,3) g(x); |
||||
INSERT INTO tgt SELECT x, x FROM generate_series(1,3) g(x); |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
DROP TABLE src, tgt; |
||||
} |
||||
|
||||
session s1 |
||||
step b1 { BEGIN ISOLATION LEVEL READ COMMITTED; } |
||||
step m1 { MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); } |
||||
step s1 { SELECT * FROM tgt; } |
||||
step c1 { COMMIT; } |
||||
|
||||
session s2 |
||||
step b2 { BEGIN ISOLATION LEVEL READ COMMITTED; } |
||||
step hj { SET LOCAL enable_mergejoin = off; SET LOCAL enable_nestloop = off; } |
||||
step mj { SET LOCAL enable_hashjoin = off; SET LOCAL enable_nestloop = off; } |
||||
step nl { SET LOCAL enable_hashjoin = off; SET LOCAL enable_mergejoin = off; } |
||||
step ex { EXPLAIN (verbose, costs off) |
||||
MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); } |
||||
step m2 { MERGE INTO tgt USING src ON tgt.id = src.id |
||||
WHEN MATCHED THEN UPDATE SET val = src.val |
||||
WHEN NOT MATCHED THEN INSERT VALUES (src.id, src.val); } |
||||
step s2 { SELECT * FROM tgt; } |
||||
step c2 { COMMIT; } |
||||
|
||||
permutation b1 m1 s1 c1 b2 m2 s2 c2 |
||||
permutation b1 b2 m1 hj ex m2 c1 c2 s1 |
||||
permutation b1 b2 m1 mj ex m2 c1 c2 s1 |
||||
permutation b1 b2 m1 nl ex m2 c1 c2 s1 |
Loading…
Reference in new issue