From f871fbae9d000938a194034eb58342cf88dbcee4 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Fri, 5 Sep 2025 08:25:03 +0100 Subject: Fix concurrent update issue with MERGE. When executing a MERGE UPDATE action, if there is more than one concurrent update of the target row, the lock-and-retry code would sometimes incorrectly identify the latest version of the target tuple, leading to incorrect results. This was caused by using the ctid field from the TM_FailureData returned by table_tuple_lock() in a case where the result was TM_Ok, which is unsafe because the TM_FailureData struct is not guaranteed to be fully populated in that case. Instead, it should use the tupleid passed to (and updated by) table_tuple_lock(). To reduce the chances of similar errors in the future, improve the commentary for table_tuple_lock() and TM_FailureData to make it clearer that table_tuple_lock() updates the tid passed to it, and most fields of TM_FailureData should not be relied on in non-failure cases. An exception to this is the "traversed" field, which is set in both success and failure cases. Reported-by: Dmitry Author: Yugo Nagata Reviewed-by: Dean Rasheed Reviewed-by: Chao Li Discussion: https://postgr.es/m/1570d30e-2b95-4239-b9c3-f7bf2f2f8556@yandex.ru Backpatch-through: 15 --- src/backend/executor/nodeModifyTable.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/backend/executor/nodeModifyTable.c') diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 8c8b895939c..2cf118c90f0 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -3127,7 +3127,7 @@ lmerge_matched:; * the tuple moved, and setting our current * resultRelInfo to that. */ - if (ItemPointerIndicatesMovedPartitions(&context->tmfd.ctid)) + if (ItemPointerIndicatesMovedPartitions(tupleid)) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be deleted was already moved to another partition due to concurrent update"))); @@ -3139,14 +3139,14 @@ lmerge_matched:; * that the first qualifying WHEN MATCHED action * is executed. * - * Update tupleid to that of the new tuple, for - * the refetch we do at the top. + * tupleid has been updated to that of the new + * tuple, as required for the refetch we do at the + * top. */ if (resultRelInfo->ri_needLockTagTuple) UnlockTuple(resultRelInfo->ri_RelationDesc, &lockedtid, InplaceUpdateTupleLock); - ItemPointerCopy(&context->tmfd.ctid, tupleid); goto lmerge_matched; case TM_Deleted: -- cgit v1.2.3