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.

Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/b05e781a-fa16-6b52-6738-761181204567@2ndquadrant.com
This commit is contained in:
Peter Eisentraut 2020-02-17 15:19:58 +01:00
parent ad3ae64770
commit c6679e4fca
5 changed files with 38 additions and 10 deletions

View 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

View 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)

View 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

View 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);

View 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;