mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Fix concurrent delete handling in MERGE ... WHEN NOT MATCHED BY SOURCE.
When executing a WHEN NOT MATCHED BY SOURCE action in MERGE, if the tuple from the target table was concurrently deleted, the retry code would incorrectly execute a WHEN NOT MATCHED BY TARGET action for the row instead, giving incorrect results. Fix in ExecMergeMatched() -- when handling a TM_Deleted failure status, it should only set *matched to false (telling the caller to execute any WHEN NOT MATCHED BY TARGET actions) if the failed action was a WHEN MATCHED action. For a failed WHEN NOT MATCHED BY SOURCE action, the source tuple did not exist, and the target tuple has just been deleted, so it should do nothing. Back-patch to v17, where support for WHEN NOT MATCHED BY SOURCE actions was introduced. Reported-by: Jeff Davis <pgsql@j-davis.com> Author: Tender Wang <tndrwang@gmail.com> Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com> Discussion: https://postgr.es/m/ccdab5ba02c65af195b5a6d2d744a01d9de47cd3.camel@j-davis.com Backpatch-through: 17
This commit is contained in:
@@ -2864,10 +2864,11 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
|
||||
* Otherwise, we execute the qualifying action and return its RETURNING
|
||||
* result, if any, or NULL.
|
||||
*
|
||||
* On entry, "*matched" is assumed to be true. If a concurrent update or
|
||||
* delete is detected that causes the join quals to no longer pass, we set it
|
||||
* to false, indicating that the caller should process any NOT MATCHED [BY
|
||||
* TARGET] actions.
|
||||
* On entry, "*matched" is assumed to be true. If the join quals originally
|
||||
* passed (MATCHED case) and a concurrent update or delete is detected that
|
||||
* causes the join quals to no longer pass, we set "*matched" to false,
|
||||
* indicating that the caller should process any NOT MATCHED [BY TARGET]
|
||||
* actions.
|
||||
*
|
||||
* After a concurrent update, we restart from the first action to look for a
|
||||
* new qualifying action to execute. If the join quals originally passed, and
|
||||
@@ -3164,10 +3165,16 @@ lmerge_matched:
|
||||
errmsg("could not serialize access due to concurrent delete")));
|
||||
|
||||
/*
|
||||
* If the tuple was already deleted, set matched to false to
|
||||
* let caller handle it under NOT MATCHED [BY TARGET] clauses.
|
||||
* The target tuple was concurrently deleted by some other
|
||||
* transaction. If this was a MATCHED action, the source row
|
||||
* still exists, so set *matched to false, to let the caller
|
||||
* handle it using any NOT MATCHED [BY TARGET] actions.
|
||||
* Otherwise, for a NOT MATCHED BY SOURCE action, neither the
|
||||
* source row nor the target row now exists, so there is no
|
||||
* futher action to execute.
|
||||
*/
|
||||
*matched = false;
|
||||
if (relaction->mas_action->matchKind == MERGE_WHEN_MATCHED)
|
||||
*matched = false;
|
||||
goto out;
|
||||
|
||||
case TM_Updated:
|
||||
@@ -3320,10 +3327,18 @@ lmerge_matched:
|
||||
case TM_Deleted:
|
||||
|
||||
/*
|
||||
* tuple already deleted; tell caller to run NOT
|
||||
* MATCHED [BY TARGET] actions
|
||||
* The target tuple was concurrently deleted by
|
||||
* some other transaction. If this was a MATCHED
|
||||
* action, the source row still exists, so set
|
||||
* *matched to false, to let the caller handle it
|
||||
* using any NOT MATCHED [BY TARGET] actions.
|
||||
* Otherwise, for a NOT MATCHED BY SOURCE action,
|
||||
* neither the source row nor the target row now
|
||||
* exists, so there is no futher action to
|
||||
* execute.
|
||||
*/
|
||||
*matched = false;
|
||||
if (was_matched)
|
||||
*matched = false;
|
||||
goto out;
|
||||
|
||||
case TM_SelfModified:
|
||||
|
||||
@@ -234,3 +234,161 @@ key|val
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
starting permutation: delete merge_wnmbs_src2 c1 select2 c2
|
||||
step delete: DELETE FROM target t WHERE t.key = 1;
|
||||
step merge_wnmbs_src2:
|
||||
MERGE INTO target t
|
||||
USING (SELECT 2 AS key, 'merge2' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
<waiting ...>
|
||||
step c1: COMMIT;
|
||||
step merge_wnmbs_src2: <... completed>
|
||||
merge_action|key|val
|
||||
------------+---+------
|
||||
INSERT | 2|merge2
|
||||
(1 row)
|
||||
|
||||
step select2: SELECT * FROM target;
|
||||
key|val
|
||||
---+------
|
||||
2|merge2
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
starting permutation: delete_pa merge_wnmbs_src2_pa c1 select2_pa c2
|
||||
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
|
||||
step merge_wnmbs_src2_pa:
|
||||
MERGE INTO target_pa t
|
||||
USING (SELECT 2 AS key, 'merge2_pa' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
<waiting ...>
|
||||
step c1: COMMIT;
|
||||
step merge_wnmbs_src2_pa: <... completed>
|
||||
merge_action|key|val
|
||||
------------+---+---------
|
||||
INSERT | 2|merge2_pa
|
||||
(1 row)
|
||||
|
||||
step select2_pa: SELECT * FROM target_pa;
|
||||
key|val
|
||||
---+---------
|
||||
2|merge2_pa
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
starting permutation: delete_tg merge_wnmbs_src2_tg c1 select2_tg c2
|
||||
s1: NOTICE: Delete: (1,setup1)
|
||||
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
|
||||
step merge_wnmbs_src2_tg:
|
||||
MERGE INTO target_tg t
|
||||
USING (SELECT 2 AS key, 'merge2_tg' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
<waiting ...>
|
||||
step c1: COMMIT;
|
||||
s2: NOTICE: Insert: (2,merge2_tg)
|
||||
step merge_wnmbs_src2_tg: <... completed>
|
||||
merge_action|key|val
|
||||
------------+---+---------
|
||||
INSERT | 2|merge2_tg
|
||||
(1 row)
|
||||
|
||||
step select2_tg: SELECT * FROM target_tg;
|
||||
key|val
|
||||
---+---------
|
||||
2|merge2_tg
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
starting permutation: update delete merge_wnmbs_src2 c1 select2 c2
|
||||
step update: UPDATE target t SET val = t.val || ' update1' WHERE t.key = 1;
|
||||
step delete: DELETE FROM target t WHERE t.key = 1;
|
||||
step merge_wnmbs_src2:
|
||||
MERGE INTO target t
|
||||
USING (SELECT 2 AS key, 'merge2' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
<waiting ...>
|
||||
step c1: COMMIT;
|
||||
step merge_wnmbs_src2: <... completed>
|
||||
merge_action|key|val
|
||||
------------+---+------
|
||||
INSERT | 2|merge2
|
||||
(1 row)
|
||||
|
||||
step select2: SELECT * FROM target;
|
||||
key|val
|
||||
---+------
|
||||
2|merge2
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
starting permutation: update_pa delete_pa merge_wnmbs_src2_pa c1 select2_pa c2
|
||||
step update_pa: UPDATE target_pa t SET val = t.val || ' update1' WHERE t.key = 1;
|
||||
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
|
||||
step merge_wnmbs_src2_pa:
|
||||
MERGE INTO target_pa t
|
||||
USING (SELECT 2 AS key, 'merge2_pa' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
<waiting ...>
|
||||
step c1: COMMIT;
|
||||
step merge_wnmbs_src2_pa: <... completed>
|
||||
merge_action|key|val
|
||||
------------+---+---------
|
||||
INSERT | 2|merge2_pa
|
||||
(1 row)
|
||||
|
||||
step select2_pa: SELECT * FROM target_pa;
|
||||
key|val
|
||||
---+---------
|
||||
2|merge2_pa
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
starting permutation: update_tg delete_tg merge_wnmbs_src2_tg c1 select2_tg c2
|
||||
s1: NOTICE: Update: (1,setup1) -> (1,"setup1 update1")
|
||||
step update_tg: UPDATE target_tg t SET val = t.val || ' update1' WHERE t.key = 1;
|
||||
s1: NOTICE: Delete: (1,"setup1 update1")
|
||||
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
|
||||
step merge_wnmbs_src2_tg:
|
||||
MERGE INTO target_tg t
|
||||
USING (SELECT 2 AS key, 'merge2_tg' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
<waiting ...>
|
||||
step c1: COMMIT;
|
||||
s2: NOTICE: Insert: (2,merge2_tg)
|
||||
step merge_wnmbs_src2_tg: <... completed>
|
||||
merge_action|key|val
|
||||
------------+---+---------
|
||||
INSERT | 2|merge2_tg
|
||||
(1 row)
|
||||
|
||||
step select2_tg: SELECT * FROM target_tg;
|
||||
key|val
|
||||
---+---------
|
||||
2|merge2_tg
|
||||
(1 row)
|
||||
|
||||
step c2: COMMIT;
|
||||
|
||||
@@ -50,6 +50,9 @@ setup
|
||||
step "delete" { DELETE FROM target t WHERE t.key = 1; }
|
||||
step "delete_pa" { DELETE FROM target_pa t WHERE t.key = 1; }
|
||||
step "delete_tg" { DELETE FROM target_tg t WHERE t.key = 1; }
|
||||
step "update" { UPDATE target t SET val = t.val || ' update1' WHERE t.key = 1; }
|
||||
step "update_pa" { UPDATE target_pa t SET val = t.val || ' update1' WHERE t.key = 1; }
|
||||
step "update_tg" { UPDATE target_tg t SET val = t.val || ' update1' WHERE t.key = 1; }
|
||||
step "c1" { COMMIT; }
|
||||
|
||||
session "s2"
|
||||
@@ -65,6 +68,30 @@ step "merge2_pa" { MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as
|
||||
step "merge2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
|
||||
step "merge_delete2" { MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
|
||||
step "merge_delete2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
|
||||
step "merge_wnmbs_src2" {
|
||||
MERGE INTO target t
|
||||
USING (SELECT 2 AS key, 'merge2' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
}
|
||||
step "merge_wnmbs_src2_pa" {
|
||||
MERGE INTO target_pa t
|
||||
USING (SELECT 2 AS key, 'merge2_pa' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
}
|
||||
step "merge_wnmbs_src2_tg" {
|
||||
MERGE INTO target_tg t
|
||||
USING (SELECT 2 AS key, 'merge2_tg' AS val) s ON t.key = s.key
|
||||
WHEN MATCHED THEN UPDATE SET key = t.key + 1, val = t.val || ' match updated by ' || s.val
|
||||
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET key = t.key + 1, val = t.val || ' not matched by source'
|
||||
WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val)
|
||||
RETURNING merge_action(), t.*;
|
||||
}
|
||||
step "select2" { SELECT * FROM target; }
|
||||
step "select2_pa" { SELECT * FROM target_pa; }
|
||||
step "select2_tg" { SELECT * FROM target_tg; }
|
||||
@@ -94,3 +121,13 @@ permutation "delete_pa" "merge2_pa" "c1" "select2_pa" "c2"
|
||||
permutation "delete_tg" "merge2_tg" "c1" "select2_tg" "c2"
|
||||
permutation "delete" "merge_delete2" "c1" "select2" "c2"
|
||||
permutation "delete_tg" "merge_delete2_tg" "c1" "select2_tg" "c2"
|
||||
|
||||
# Concurrent DELETE vs MERGE ... WHEN NOT MATCHED BY SOURCE
|
||||
permutation "delete" "merge_wnmbs_src2" "c1" "select2" "c2"
|
||||
permutation "delete_pa" "merge_wnmbs_src2_pa" "c1" "select2_pa" "c2"
|
||||
permutation "delete_tg" "merge_wnmbs_src2_tg" "c1" "select2_tg" "c2"
|
||||
|
||||
# Concurrent UPDATE then DELETE vs MERGE ... WHEN NOT MATCHED BY SOURCE
|
||||
permutation "update" "delete" "merge_wnmbs_src2" "c1" "select2" "c2"
|
||||
permutation "update_pa" "delete_pa" "merge_wnmbs_src2_pa" "c1" "select2_pa" "c2"
|
||||
permutation "update_tg" "delete_tg" "merge_wnmbs_src2_tg" "c1" "select2_tg" "c2"
|
||||
|
||||
Reference in New Issue
Block a user