Evade extra table_tuple_fetch_row_version() in ExecUpdate()/ExecDelete()
authorAlexander Korotkov <akorotkov@postgresql.org>
Wed, 22 Mar 2023 21:12:00 +0000 (00:12 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Wed, 22 Mar 2023 21:26:59 +0000 (00:26 +0300)
When we lock tuple using table_tuple_lock() then we at the same time fetch
the locked tuple to the slot.  In this case we can skip extra
table_tuple_fetch_row_version() thank to we've already fetched the 'old' tuple
and nobody can change it concurrently since it's locked.

Discussion: https://postgr.es/m/CAPpHfdua-YFw3XTprfutzGp28xXLigFtzNbuFY8yPhqeq6X5kg%40mail.gmail.com
Reviewed-by: Aleksander Alekseev, Pavel Borisov, Vignesh C, Mason Sharp
Reviewed-by: Andres Freund, Chris Travers
src/backend/executor/nodeModifyTable.c

index 3a673895082a170914bcadd6da479bb1afc349b5..93ebfdbb0d83080ea4676d7a4fd07c3d1e80f887 100644 (file)
@@ -1559,6 +1559,22 @@ ldelete:
                    {
                        case TM_Ok:
                            Assert(context->tmfd.traversed);
+
+                           /*
+                            * Save locked tuple for further processing of
+                            * RETURNING clause.
+                            */
+                           if (processReturning &&
+                               resultRelInfo->ri_projectReturning &&
+                               !resultRelInfo->ri_FdwRoutine)
+                           {
+                               TupleTableSlot *returningSlot;
+
+                               returningSlot = ExecGetReturningSlot(estate, resultRelInfo);
+                               ExecCopySlot(returningSlot, inputslot);
+                               ExecMaterializeSlot(returningSlot);
+                           }
+
                            epqslot = EvalPlanQual(context->epqstate,
                                                   resultRelationDesc,
                                                   resultRelInfo->ri_RangeTableIndex,
@@ -1673,12 +1689,17 @@ ldelete:
        }
        else
        {
+           /*
+            * Tuple can be already fetched to the returning slot in case
+            * we've previously locked it.  Fetch the tuple only if the slot
+            * is empty.
+            */
            slot = ExecGetReturningSlot(estate, resultRelInfo);
            if (oldtuple != NULL)
            {
                ExecForceStoreHeapTuple(oldtuple, slot, false);
            }
-           else
+           else if (TupIsNull(slot))
            {
                if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,
                                                   SnapshotAny, slot))
@@ -2393,6 +2414,19 @@ redo_act:
                        case TM_Ok:
                            Assert(context->tmfd.traversed);
 
+                           /* Make sure ri_oldTupleSlot is initialized. */
+                           if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
+                               ExecInitUpdateProjection(context->mtstate,
+                                                        resultRelInfo);
+
+                           /*
+                            * Save the locked tuple for further calculation
+                            * of the new tuple.
+                            */
+                           oldSlot = resultRelInfo->ri_oldTupleSlot;
+                           ExecCopySlot(oldSlot, inputslot);
+                           ExecMaterializeSlot(oldSlot);
+
                            epqslot = EvalPlanQual(context->epqstate,
                                                   resultRelationDesc,
                                                   resultRelInfo->ri_RangeTableIndex,
@@ -2401,18 +2435,6 @@ redo_act:
                                /* Tuple not passing quals anymore, exiting... */
                                return NULL;
 
-                           /* Make sure ri_oldTupleSlot is initialized. */
-                           if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
-                               ExecInitUpdateProjection(context->mtstate,
-                                                        resultRelInfo);
-
-                           /* Fetch the most recent version of old tuple. */
-                           oldSlot = resultRelInfo->ri_oldTupleSlot;
-                           if (!table_tuple_fetch_row_version(resultRelationDesc,
-                                                              tupleid,
-                                                              SnapshotAny,
-                                                              oldSlot))
-                               elog(ERROR, "failed to fetch tuple being updated");
                            slot = ExecGetUpdateNewTuple(resultRelInfo,
                                                         epqslot, oldSlot);
                            goto redo_act;