From: Tom Lane Date: Wed, 15 Aug 2007 21:39:50 +0000 (+0000) Subject: Arrange to cache a ResultRelInfo in the executor's EState for relations that X-Git-Tag: REL9_0_0~5201 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=817946bb04e1dcac02d85572103f1e1381102a0a;p=pg-rex%2Fsyncrep.git Arrange to cache a ResultRelInfo in the executor's EState for relations that are not one of the query's defined result relations, but nonetheless have triggers fired against them while the query is active. This was formerly impossible but can now occur because of my recent patch to fix the firing order for RI triggers. Caching a ResultRelInfo avoids duplicating work by repeatedly opening and closing the same relation, and also allows EXPLAIN ANALYZE to "see" and report on these extra triggers. Use the same mechanism to cache open relations when firing deferred triggers at transaction shutdown; this replaces the former one-element-cache strategy used in that case, and should improve performance a bit when there are deferred triggers on a number of relations. --- diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 39e8073f55..c9d454bc49 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.164 2007/05/25 17:54:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.165 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -52,6 +52,8 @@ typedef struct ExplainState static void ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString, ParamListInfo params, TupOutputState *tstate); +static void report_triggers(ResultRelInfo *rInfo, bool show_relname, + StringInfo buf); static double elapsed_time(instr_time *starttime); static void explain_outNode(StringInfo str, Plan *plan, PlanState *planstate, @@ -310,50 +312,21 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, if (es->printAnalyze) { ResultRelInfo *rInfo; + bool show_relname; int numrels = queryDesc->estate->es_num_result_relations; + List *targrels = queryDesc->estate->es_trig_target_relations; int nr; + ListCell *l; + show_relname = (numrels > 1 || targrels != NIL); rInfo = queryDesc->estate->es_result_relations; for (nr = 0; nr < numrels; rInfo++, nr++) - { - int nt; - - if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) - continue; - for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++) - { - Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; - Instrumentation *instr = rInfo->ri_TrigInstrument + nt; - char *conname; + report_triggers(rInfo, show_relname, &buf); - /* Must clean up instrumentation state */ - InstrEndLoop(instr); - - /* - * We ignore triggers that were never invoked; they likely - * aren't relevant to the current query type. - */ - if (instr->ntuples == 0) - continue; - - if (OidIsValid(trig->tgconstraint) && - (conname = get_constraint_name(trig->tgconstraint)) != NULL) - { - appendStringInfo(&buf, "Trigger for constraint %s", - conname); - pfree(conname); - } - else - appendStringInfo(&buf, "Trigger %s", trig->tgname); - - if (numrels > 1) - appendStringInfo(&buf, " on %s", - RelationGetRelationName(rInfo->ri_RelationDesc)); - - appendStringInfo(&buf, ": time=%.3f calls=%.0f\n", - 1000.0 * instr->total, - instr->ntuples); - } + foreach(l, targrels) + { + rInfo = (ResultRelInfo *) lfirst(l); + report_triggers(rInfo, show_relname, &buf); } } @@ -382,6 +355,51 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, pfree(es); } +/* + * report_triggers - + * report execution stats for a single relation's triggers + */ +static void +report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf) +{ + int nt; + + if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) + return; + for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++) + { + Trigger *trig = rInfo->ri_TrigDesc->triggers + nt; + Instrumentation *instr = rInfo->ri_TrigInstrument + nt; + char *conname; + + /* Must clean up instrumentation state */ + InstrEndLoop(instr); + + /* + * We ignore triggers that were never invoked; they likely + * aren't relevant to the current query type. + */ + if (instr->ntuples == 0) + continue; + + if (OidIsValid(trig->tgconstraint) && + (conname = get_constraint_name(trig->tgconstraint)) != NULL) + { + appendStringInfo(buf, "Trigger for constraint %s", conname); + pfree(conname); + } + else + appendStringInfo(buf, "Trigger %s", trig->tgname); + + if (show_relname) + appendStringInfo(buf, " on %s", + RelationGetRelationName(rInfo->ri_RelationDesc)); + + appendStringInfo(buf, ": time=%.3f calls=%.0f\n", + 1000.0 * instr->total, instr->ntuples); + } +} + /* Compute elapsed time in seconds since given timestamp */ static double elapsed_time(instr_time *starttime) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 2788684d1a..afcdaa5e91 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.217 2007/08/15 19:15:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.218 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2313,11 +2313,10 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, * Scan the given event list for events that are marked as to be fired * in the current firing cycle, and fire them. * - * If estate isn't NULL, then we expect that all the firable events are - * for triggers of the relations included in the estate's result relation - * array. This allows us to re-use the estate's open relations and - * trigger cache info. When estate is NULL, we have to find the relations - * the hard way. + * If estate isn't NULL, we use its result relation info to avoid repeated + * openings and closing of trigger target relations. If it is NULL, we + * make one locally to cache the info in case there are multiple trigger + * events per rel. * * When delete_ok is TRUE, it's okay to delete fully-processed events. * The events list pointers are updated. @@ -2332,12 +2331,19 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, AfterTriggerEvent event, prev_event; MemoryContext per_tuple_context; - bool locally_opened = false; + bool local_estate = false; Relation rel = NULL; TriggerDesc *trigdesc = NULL; FmgrInfo *finfo = NULL; Instrumentation *instr = NULL; + /* Make a local EState if need be */ + if (estate == NULL) + { + estate = CreateExecutorState(); + local_estate = true; + } + /* Make a per-tuple memory context for trigger function calls */ per_tuple_context = AllocSetContextCreate(CurrentMemoryContext, @@ -2360,77 +2366,21 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, event->ate_firing_id == firing_id) { /* - * So let's fire it... but first, open the correct relation if + * So let's fire it... but first, find the correct relation if * this is not the same relation as before. */ - if (rel == NULL || rel->rd_id != event->ate_relid) + if (rel == NULL || RelationGetRelid(rel) != event->ate_relid) { - if (locally_opened) - { - /* close prior rel if any */ - if (rel) - heap_close(rel, NoLock); - if (trigdesc) - FreeTriggerDesc(trigdesc); - if (finfo) - pfree(finfo); - Assert(instr == NULL); /* never used in this case */ - } - locally_opened = true; - - if (estate) - { - /* Find target relation among estate's result rels */ - ResultRelInfo *rInfo; - int nr; - - rInfo = estate->es_result_relations; - nr = estate->es_num_result_relations; - while (nr > 0) - { - if (rInfo->ri_RelationDesc->rd_id == event->ate_relid) - { - rel = rInfo->ri_RelationDesc; - trigdesc = rInfo->ri_TrigDesc; - finfo = rInfo->ri_TrigFunctions; - instr = rInfo->ri_TrigInstrument; - locally_opened = false; - break; - } - rInfo++; - nr--; - } - } - - if (locally_opened) - { - /* Hard way: open target relation for ourselves */ - - /* - * We assume that an appropriate lock is still held by the - * executor, so grab no new lock here. - */ - rel = heap_open(event->ate_relid, NoLock); - - /* - * Copy relation's trigger info so that we have a stable - * copy no matter what the called triggers do. - */ - trigdesc = CopyTriggerDesc(rel->trigdesc); - - if (trigdesc == NULL) /* should not happen */ - elog(ERROR, "relation %u has no triggers", - event->ate_relid); - - /* - * Allocate space to cache fmgr lookup info for triggers. - */ - finfo = (FmgrInfo *) - palloc0(trigdesc->numtriggers * sizeof(FmgrInfo)); - - /* Never any EXPLAIN info in this case */ - instr = NULL; - } + ResultRelInfo *rInfo; + + rInfo = ExecGetTriggerResultRel(estate, event->ate_relid); + rel = rInfo->ri_RelationDesc; + trigdesc = rInfo->ri_TrigDesc; + finfo = rInfo->ri_TrigFunctions; + instr = rInfo->ri_TrigInstrument; + if (trigdesc == NULL) /* should not happen */ + elog(ERROR, "relation %u has no triggers", + event->ate_relid); } /* @@ -2480,17 +2430,22 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, events->tail = prev_event; /* Release working resources */ - if (locally_opened) + MemoryContextDelete(per_tuple_context); + + if (local_estate) { - if (rel) - heap_close(rel, NoLock); - if (trigdesc) - FreeTriggerDesc(trigdesc); - if (finfo) - pfree(finfo); - Assert(instr == NULL); /* never used in this case */ + ListCell *l; + + foreach(l, estate->es_trig_target_relations) + { + ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l); + + /* Close indices and then the relation itself */ + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + } + FreeExecutorState(estate); } - MemoryContextDelete(per_tuple_context); } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index dfd1a84ab7..322c1d1a27 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.295 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.296 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -66,8 +66,8 @@ typedef struct evalPlanQual /* decls for local routines only used within this module */ static void InitPlan(QueryDesc *queryDesc, int eflags); static void initResultRelInfo(ResultRelInfo *resultRelInfo, + Relation resultRelationDesc, Index resultRelationIndex, - List *rangeTable, CmdType operation, bool doInstrument); static void ExecEndPlan(PlanState *planstate, EState *estate); @@ -494,9 +494,15 @@ InitPlan(QueryDesc *queryDesc, int eflags) resultRelInfo = resultRelInfos; foreach(l, resultRelations) { + Index resultRelationIndex = lfirst_int(l); + Oid resultRelationOid; + Relation resultRelation; + + resultRelationOid = getrelid(resultRelationIndex, rangeTable); + resultRelation = heap_open(resultRelationOid, RowExclusiveLock); initResultRelInfo(resultRelInfo, - lfirst_int(l), - rangeTable, + resultRelation, + resultRelationIndex, operation, estate->es_instrument); resultRelInfo++; @@ -831,19 +837,20 @@ InitPlan(QueryDesc *queryDesc, int eflags) */ static void initResultRelInfo(ResultRelInfo *resultRelInfo, + Relation resultRelationDesc, Index resultRelationIndex, - List *rangeTable, CmdType operation, bool doInstrument) { - Oid resultRelationOid; - Relation resultRelationDesc; - - resultRelationOid = getrelid(resultRelationIndex, rangeTable); - resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock); - + /* + * Check valid relkind ... parser and/or planner should have noticed + * this already, but let's make sure. + */ switch (resultRelationDesc->rd_rel->relkind) { + case RELKIND_RELATION: + /* OK */ + break; case RELKIND_SEQUENCE: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -862,8 +869,15 @@ initResultRelInfo(ResultRelInfo *resultRelInfo, errmsg("cannot change view \"%s\"", RelationGetRelationName(resultRelationDesc)))); break; + default: + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change relation \"%s\"", + RelationGetRelationName(resultRelationDesc)))); + break; } + /* OK, fill in the node */ MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); resultRelInfo->type = T_ResultRelInfo; resultRelInfo->ri_RangeTableIndex = resultRelationIndex; @@ -905,6 +919,76 @@ initResultRelInfo(ResultRelInfo *resultRelInfo, } /* + * ExecGetTriggerResultRel + * + * Get a ResultRelInfo for a trigger target relation. Most of the time, + * triggers are fired on one of the result relations of the query, and so + * we can just return a member of the es_result_relations array. (Note: in + * self-join situations there might be multiple members with the same OID; + * if so it doesn't matter which one we pick.) However, it is sometimes + * necessary to fire triggers on other relations; this happens mainly when an + * RI update trigger queues additional triggers on other relations, which will + * be processed in the context of the outer query. For efficiency's sake, + * we want to have a ResultRelInfo for those triggers too; that can avoid + * repeated re-opening of the relation. (It also provides a way for EXPLAIN + * ANALYZE to report the runtimes of such triggers.) So we make additional + * ResultRelInfo's as needed, and save them in es_trig_target_relations. + */ +ResultRelInfo * +ExecGetTriggerResultRel(EState *estate, Oid relid) +{ + ResultRelInfo *rInfo; + int nr; + ListCell *l; + Relation rel; + MemoryContext oldcontext; + + /* First, search through the query result relations */ + rInfo = estate->es_result_relations; + nr = estate->es_num_result_relations; + while (nr > 0) + { + if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + return rInfo; + rInfo++; + nr--; + } + /* Nope, but maybe we already made an extra ResultRelInfo for it */ + foreach(l, estate->es_trig_target_relations) + { + rInfo = (ResultRelInfo *) lfirst(l); + if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + return rInfo; + } + /* Nope, so we need a new one */ + + /* + * Open the target relation's relcache entry. We assume that an + * appropriate lock is still held by the backend from whenever the + * trigger event got queued, so we need take no new lock here. + */ + rel = heap_open(relid, NoLock); + + /* + * Make the new entry in the right context. Currently, we don't need + * any index information in ResultRelInfos used only for triggers, + * so tell initResultRelInfo it's a DELETE. + */ + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + rInfo = makeNode(ResultRelInfo); + initResultRelInfo(rInfo, + rel, + 0, /* dummy rangetable index */ + CMD_DELETE, + estate->es_instrument); + estate->es_trig_target_relations = + lappend(estate->es_trig_target_relations, rInfo); + MemoryContextSwitchTo(oldcontext); + + return rInfo; +} + +/* * ExecContextForcesOids * * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO, @@ -1020,6 +1104,17 @@ ExecEndPlan(PlanState *planstate, EState *estate) } /* + * likewise close any trigger target relations + */ + foreach(l, estate->es_trig_target_relations) + { + resultRelInfo = (ResultRelInfo *) lfirst(l); + /* Close indices and then the relation itself */ + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + } + + /* * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks */ foreach(l, estate->es_rowMarks) @@ -2267,6 +2362,7 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq) epqstate->es_num_result_relations = estate->es_num_result_relations; epqstate->es_result_relation_info = estate->es_result_relation_info; epqstate->es_junkFilter = estate->es_junkFilter; + /* es_trig_target_relations must NOT be copied */ epqstate->es_into_relation_descriptor = estate->es_into_relation_descriptor; epqstate->es_into_relation_use_wal = estate->es_into_relation_use_wal; epqstate->es_param_list_info = estate->es_param_list_info; @@ -2331,7 +2427,8 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq) * * This is a cut-down version of ExecutorEnd(); basically we want to do most * of the normal cleanup, but *not* close result relations (which we are - * just sharing from the outer query). + * just sharing from the outer query). We do, however, have to close any + * trigger target relations that got opened, since those are not shared. */ static void EvalPlanQualStop(evalPlanQual *epq) @@ -2360,6 +2457,15 @@ EvalPlanQualStop(evalPlanQual *epq) epqstate->es_evTuple[epq->rti - 1] = NULL; } + foreach(l, epqstate->es_trig_target_relations) + { + ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l); + + /* Close indices and then the relation itself */ + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + } + MemoryContextSwitchTo(oldcontext); FreeExecutorState(epqstate); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index fe76a33774..1d47806299 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.149 2007/07/31 16:36:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.150 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -184,6 +184,7 @@ CreateExecutorState(void) estate->es_junkFilter = NULL; + estate->es_trig_target_relations = NIL; estate->es_trig_tuple_slot = NULL; estate->es_into_relation_descriptor = NULL; diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 539e2f6fca..f4272cb758 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.141 2007/06/11 22:22:42 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.142 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -138,6 +138,7 @@ extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count); extern void ExecutorEnd(QueryDesc *queryDesc); extern void ExecutorRewind(QueryDesc *queryDesc); +extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids); extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ccc243cfd0..d886c0149f 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.176 2007/06/05 21:31:08 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.177 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -310,9 +310,12 @@ typedef struct EState ResultRelInfo *es_result_relation_info; /* currently active array elt */ JunkFilter *es_junkFilter; /* currently active junk filter */ + /* Stuff used for firing triggers: */ + List *es_trig_target_relations; /* trigger-only ResultRelInfos */ TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */ - Relation es_into_relation_descriptor; /* for SELECT INTO */ + /* Stuff used for SELECT INTO: */ + Relation es_into_relation_descriptor; bool es_into_relation_use_wal; /* Parameter info: */