summaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
authorDean Rasheed <dean.a.rasheed@gmail.com>2025-09-05 08:25:03 +0100
committerDean Rasheed <dean.a.rasheed@gmail.com>2025-09-05 08:25:03 +0100
commitf871fbae9d000938a194034eb58342cf88dbcee4 (patch)
tree58dbbddcab1332dda0f466523b3e4c5d4d8230b8 /src/backend/executor/nodeModifyTable.c
parenta4624929dba3d6e5cef98a6cedd865320db2269c (diff)
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 <dsy.075@yandex.ru> Author: Yugo Nagata <nagata@sraoss.co.jp> Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com> Reviewed-by: Chao Li <li.evan.chao@gmail.com> Discussion: https://postgr.es/m/1570d30e-2b95-4239-b9c3-f7bf2f2f8556@yandex.ru Backpatch-through: 15
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c8
1 files changed, 4 insertions, 4 deletions
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: