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:
parent
ad3ae64770
commit
c6679e4fca
@ -3222,7 +3222,7 @@ CopyFrom(CopyState cstate)
|
|||||||
/* Compute stored generated columns */
|
/* Compute stored generated columns */
|
||||||
if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
|
if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
|
||||||
resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
|
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
|
* If the target is a plain table, check the constraints of
|
||||||
|
@ -419,7 +419,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
|
|||||||
/* Compute stored generated columns */
|
/* Compute stored generated columns */
|
||||||
if (rel->rd_att->constr &&
|
if (rel->rd_att->constr &&
|
||||||
rel->rd_att->constr->has_generated_stored)
|
rel->rd_att->constr->has_generated_stored)
|
||||||
ExecComputeStoredGenerated(estate, slot);
|
ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
|
||||||
|
|
||||||
/* Check the constraints of the tuple */
|
/* Check the constraints of the tuple */
|
||||||
if (rel->rd_att->constr)
|
if (rel->rd_att->constr)
|
||||||
@ -485,7 +485,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
|
|||||||
/* Compute stored generated columns */
|
/* Compute stored generated columns */
|
||||||
if (rel->rd_att->constr &&
|
if (rel->rd_att->constr &&
|
||||||
rel->rd_att->constr->has_generated_stored)
|
rel->rd_att->constr->has_generated_stored)
|
||||||
ExecComputeStoredGenerated(estate, slot);
|
ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
|
||||||
|
|
||||||
/* Check the constraints of the tuple */
|
/* Check the constraints of the tuple */
|
||||||
if (rel->rd_att->constr)
|
if (rel->rd_att->constr)
|
||||||
|
@ -246,7 +246,7 @@ ExecCheckTIDVisible(EState *estate,
|
|||||||
* Compute stored generated columns for a tuple
|
* Compute stored generated columns for a tuple
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
|
ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype)
|
||||||
{
|
{
|
||||||
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
|
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
|
||||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||||
@ -269,6 +269,7 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
|
|||||||
|
|
||||||
resultRelInfo->ri_GeneratedExprs =
|
resultRelInfo->ri_GeneratedExprs =
|
||||||
(ExprState **) palloc(natts * sizeof(ExprState *));
|
(ExprState **) palloc(natts * sizeof(ExprState *));
|
||||||
|
resultRelInfo->ri_NumGeneratedNeeded = 0;
|
||||||
|
|
||||||
for (int i = 0; i < natts; i++)
|
for (int i = 0; i < natts; i++)
|
||||||
{
|
{
|
||||||
@ -276,18 +277,41 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
|
|||||||
{
|
{
|
||||||
Expr *expr;
|
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);
|
expr = (Expr *) build_column_default(rel, i + 1);
|
||||||
if (expr == NULL)
|
if (expr == NULL)
|
||||||
elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
|
elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
|
||||||
i + 1, RelationGetRelationName(rel));
|
i + 1, RelationGetRelationName(rel));
|
||||||
|
|
||||||
resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
|
resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
|
||||||
|
resultRelInfo->ri_NumGeneratedNeeded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldContext);
|
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));
|
oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
|
||||||
|
|
||||||
values = palloc(sizeof(*values) * natts);
|
values = palloc(sizeof(*values) * natts);
|
||||||
@ -300,7 +324,8 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
|
|||||||
{
|
{
|
||||||
Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
|
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;
|
ExprContext *econtext;
|
||||||
Datum val;
|
Datum val;
|
||||||
@ -392,7 +417,7 @@ ExecInsert(ModifyTableState *mtstate,
|
|||||||
*/
|
*/
|
||||||
if (resultRelationDesc->rd_att->constr &&
|
if (resultRelationDesc->rd_att->constr &&
|
||||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||||
ExecComputeStoredGenerated(estate, slot);
|
ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* insert into foreign table: let the FDW do it
|
* insert into foreign table: let the FDW do it
|
||||||
@ -427,7 +452,7 @@ ExecInsert(ModifyTableState *mtstate,
|
|||||||
*/
|
*/
|
||||||
if (resultRelationDesc->rd_att->constr &&
|
if (resultRelationDesc->rd_att->constr &&
|
||||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||||
ExecComputeStoredGenerated(estate, slot);
|
ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check any RLS WITH CHECK policies.
|
* Check any RLS WITH CHECK policies.
|
||||||
@ -1088,7 +1113,7 @@ ExecUpdate(ModifyTableState *mtstate,
|
|||||||
*/
|
*/
|
||||||
if (resultRelationDesc->rd_att->constr &&
|
if (resultRelationDesc->rd_att->constr &&
|
||||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||||
ExecComputeStoredGenerated(estate, slot);
|
ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* update in foreign table: let the FDW do it
|
* update in foreign table: let the FDW do it
|
||||||
@ -1125,7 +1150,7 @@ ExecUpdate(ModifyTableState *mtstate,
|
|||||||
*/
|
*/
|
||||||
if (resultRelationDesc->rd_att->constr &&
|
if (resultRelationDesc->rd_att->constr &&
|
||||||
resultRelationDesc->rd_att->constr->has_generated_stored)
|
resultRelationDesc->rd_att->constr->has_generated_stored)
|
||||||
ExecComputeStoredGenerated(estate, slot);
|
ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check any RLS UPDATE WITH CHECK policies
|
* Check any RLS UPDATE WITH CHECK policies
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
#include "nodes/execnodes.h"
|
#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 ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
|
||||||
extern void ExecEndModifyTable(ModifyTableState *node);
|
extern void ExecEndModifyTable(ModifyTableState *node);
|
||||||
|
@ -457,6 +457,9 @@ typedef struct ResultRelInfo
|
|||||||
/* array of stored generated columns expr states */
|
/* array of stored generated columns expr states */
|
||||||
ExprState **ri_GeneratedExprs;
|
ExprState **ri_GeneratedExprs;
|
||||||
|
|
||||||
|
/* number of stored generated columns we need to compute */
|
||||||
|
int ri_NumGeneratedNeeded;
|
||||||
|
|
||||||
/* for removing junk attributes from tuples */
|
/* for removing junk attributes from tuples */
|
||||||
JunkFilter *ri_junkFilter;
|
JunkFilter *ri_junkFilter;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user