Optimize update of tables with generated columns
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 17 Feb 2020 14:19:58 +0000 (15:19 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 17 Feb 2020 14:20:58 +0000 (15:20 +0100)
When updating a table row with generated columns, only recompute those
generated columns whose base columns have changed in this update and
keep the rest unchanged.  This can result in a significant performance
benefit.  The required information was already kept in
RangeTblEntry.extraUpdatedCols; we just have to make use of it.

Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/b05e781a-fa16-6b52-6738-761181204567@2ndquadrant.com

src/backend/commands/copy.c
src/backend/executor/execReplication.c
src/backend/executor/nodeModifyTable.c
src/include/executor/nodeModifyTable.h
src/include/nodes/execnodes.h

index 40a8ec1abd2d35a531f34e71d2d5f1c8efab9147..e79ede4cb859710b66813cb5b485693ac93ce4a0 100644 (file)
@@ -3222,7 +3222,7 @@ CopyFrom(CopyState cstate)
                /* Compute stored generated columns */
                if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
                    resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
-                   ExecComputeStoredGenerated(estate, myslot);
+                   ExecComputeStoredGenerated(estate, myslot, CMD_INSERT);
 
                /*
                 * If the target is a plain table, check the constraints of
index 30cba89da7ef1ff3d76bea2a783aaede78cdf06b..7194becfd99bc26047083d8f72dbf52063fe1a2d 100644 (file)
@@ -419,7 +419,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
        /* Compute stored generated columns */
        if (rel->rd_att->constr &&
            rel->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot);
+           ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
        /* Check the constraints of the tuple */
        if (rel->rd_att->constr)
@@ -485,7 +485,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
        /* Compute stored generated columns */
        if (rel->rd_att->constr &&
            rel->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot);
+           ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
        /* Check the constraints of the tuple */
        if (rel->rd_att->constr)
index 59d1a31c972dc0247a54ec160d1f446c52ecf7ea..d71c0a432207c41976244344d792308ad329c29b 100644 (file)
@@ -246,7 +246,7 @@ ExecCheckTIDVisible(EState *estate,
  * Compute stored generated columns for a tuple
  */
 void
-ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
+ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype)
 {
    ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
    Relation    rel = resultRelInfo->ri_RelationDesc;
@@ -269,6 +269,7 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
 
        resultRelInfo->ri_GeneratedExprs =
            (ExprState **) palloc(natts * sizeof(ExprState *));
+       resultRelInfo->ri_NumGeneratedNeeded = 0;
 
        for (int i = 0; i < natts; i++)
        {
@@ -276,18 +277,41 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
            {
                Expr       *expr;
 
+               /*
+                * If it's an update and the current column was not marked as
+                * being updated, then we can skip the computation.  But if
+                * there is a BEFORE ROW UPDATE trigger, we cannot skip
+                * because the trigger might affect additional columns.
+                */
+               if (cmdtype == CMD_UPDATE &&
+                   !(rel->trigdesc && rel->trigdesc->trig_update_before_row) &&
+                   !bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber,
+                                  exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, estate)->extraUpdatedCols))
+               {
+                   resultRelInfo->ri_GeneratedExprs[i] = NULL;
+                   continue;
+               }
+
                expr = (Expr *) build_column_default(rel, i + 1);
                if (expr == NULL)
                    elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
                         i + 1, RelationGetRelationName(rel));
 
                resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
+               resultRelInfo->ri_NumGeneratedNeeded++;
            }
        }
 
        MemoryContextSwitchTo(oldContext);
    }
 
+   /*
+    * If no generated columns have been affected by this change, then skip
+    * the rest.
+    */
+   if (resultRelInfo->ri_NumGeneratedNeeded == 0)
+       return;
+
    oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
    values = palloc(sizeof(*values) * natts);
@@ -300,7 +324,8 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
    {
        Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 
-       if (attr->attgenerated == ATTRIBUTE_GENERATED_STORED)
+       if (attr->attgenerated == ATTRIBUTE_GENERATED_STORED &&
+           resultRelInfo->ri_GeneratedExprs[i])
        {
            ExprContext *econtext;
            Datum       val;
@@ -392,7 +417,7 @@ ExecInsert(ModifyTableState *mtstate,
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot);
+           ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
        /*
         * insert into foreign table: let the FDW do it
@@ -427,7 +452,7 @@ ExecInsert(ModifyTableState *mtstate,
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot);
+           ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
        /*
         * Check any RLS WITH CHECK policies.
@@ -1088,7 +1113,7 @@ ExecUpdate(ModifyTableState *mtstate,
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot);
+           ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
        /*
         * update in foreign table: let the FDW do it
@@ -1125,7 +1150,7 @@ ExecUpdate(ModifyTableState *mtstate,
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot);
+           ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
        /*
         * Check any RLS UPDATE WITH CHECK policies
index 0495cae051c79eca103152f613b97830c73225a1..4ec4ebdabc13e81cac9fb3d04e313fc0a89a2247 100644 (file)
@@ -15,7 +15,7 @@
 
 #include "nodes/execnodes.h"
 
-extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot);
+extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype);
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
index 5d5b38b8799a635e9dc3a31bbeeb58edf12ba93b..cd3ddf781f1e58ae8f38d5139b269e25ac5e88c1 100644 (file)
@@ -457,6 +457,9 @@ typedef struct ResultRelInfo
    /* array of stored generated columns expr states */
    ExprState **ri_GeneratedExprs;
 
+   /* number of stored generated columns we need to compute */
+   int         ri_NumGeneratedNeeded;
+
    /* for removing junk attributes from tuples */
    JunkFilter *ri_junkFilter;