From 3b80f3147c0e983a2e8ad41be7df54c6480c0d8f Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 20 Dec 2019 22:52:31 +0100 Subject: [PATCH] Optimize update of tables with generated columns 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. FIXME: ExecSimpleRelationUpdate() does not currently populate extraUpdatedCols. That needs fixing first. --- src/backend/commands/copy.c | 2 +- src/backend/executor/execReplication.c | 4 +-- src/backend/executor/nodeModifyTable.c | 37 +++++++++++++++++++++----- src/include/executor/nodeModifyTable.h | 2 +- src/include/nodes/execnodes.h | 3 +++ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 42a147b67d..a0758cd39c 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -3221,7 +3221,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 diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 95e027c970..8cbec36b18 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -416,7 +416,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) @@ -482,7 +482,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) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 9ba1d78344..5878041ee6 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -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 diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h index 891b119608..d44a64cb49 100644 --- a/src/include/executor/nodeModifyTable.h +++ b/src/include/executor/nodeModifyTable.h @@ -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); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 0c2a77aaf8..93cca2e08d 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -456,6 +456,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; -- 2.24.1