diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-26 02:26:45 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-26 02:26:45 +0000 |
commit | 9f2ee8f287098fb8067593b38da0650df458b20a (patch) | |
tree | 8998549ba80c6f5b397ad1e77dc6f03aefee00c2 /src/backend/executor/nodeLockRows.c | |
parent | 76d8883c8e3647ac2f7ff3c48226a25b1fd7888b (diff) | |
download | postgresql-9f2ee8f287098fb8067593b38da0650df458b20a.tar.gz postgresql-9f2ee8f287098fb8067593b38da0650df458b20a.zip |
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
Diffstat (limited to 'src/backend/executor/nodeLockRows.c')
-rw-r--r-- | src/backend/executor/nodeLockRows.c | 170 |
1 files changed, 88 insertions, 82 deletions
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 80f7e3cdafb..f38d34a0475 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeLockRows.c,v 1.1 2009/10/12 18:10:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeLockRows.c,v 1.2 2009/10/26 02:26:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,6 +25,7 @@ #include "executor/executor.h" #include "executor/nodeLockRows.h" #include "storage/bufmgr.h" +#include "utils/tqual.h" /* ---------------------------------------------------------------- @@ -37,7 +38,7 @@ ExecLockRows(LockRowsState *node) TupleTableSlot *slot; EState *estate; PlanState *outerPlan; - bool epq_pushed; + bool epq_started; ListCell *lc; /* @@ -47,30 +48,19 @@ ExecLockRows(LockRowsState *node) outerPlan = outerPlanState(node); /* - * Get next tuple from subplan, if any; but if we are evaluating - * an EvalPlanQual substitution, first finish that. + * Get next tuple from subplan, if any. */ lnext: - if (node->lr_useEvalPlan) - { - slot = EvalPlanQualNext(estate); - if (TupIsNull(slot)) - { - EvalPlanQualPop(estate, outerPlan); - node->lr_useEvalPlan = false; - slot = ExecProcNode(outerPlan); - } - } - else - slot = ExecProcNode(outerPlan); + slot = ExecProcNode(outerPlan); if (TupIsNull(slot)) return NULL; /* - * Attempt to lock the source tuple(s). + * Attempt to lock the source tuple(s). (Note we only have locking + * rowmarks in lr_rowMarks.) */ - epq_pushed = false; + epq_started = false; foreach(lc, node->lr_rowMarks) { ExecRowMark *erm = (ExecRowMark *) lfirst(lc); @@ -84,6 +74,10 @@ lnext: HTSU_Result test; HeapTuple copyTuple; + /* clear any leftover test tuple for this rel */ + if (node->lr_epqstate.estate != NULL) + EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL); + /* if child rel, must check whether it produced this row */ if (erm->rti != erm->prti) { @@ -115,7 +109,7 @@ lnext: tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); /* okay, try to lock the tuple */ - if (erm->forUpdate) + if (erm->markType == ROW_MARK_EXCLUSIVE) lockmode = LockTupleExclusive; else lockmode = LockTupleShared; @@ -129,8 +123,6 @@ lnext: { case HeapTupleSelfUpdated: /* treat it as deleted; do not process */ - if (epq_pushed) - EvalPlanQualPop(estate, outerPlan); goto lnext; case HeapTupleMayBeUpdated: @@ -146,35 +138,33 @@ lnext: &tuple.t_self)) { /* Tuple was deleted, so don't return it */ - if (epq_pushed) - EvalPlanQualPop(estate, outerPlan); goto lnext; } - /* updated, so look at updated version */ - copyTuple = EvalPlanQualFetch(estate, erm->rti, + /* updated, so fetch and lock the updated version */ + copyTuple = EvalPlanQualFetch(estate, erm->relation, lockmode, &update_ctid, update_xmax); if (copyTuple == NULL) { /* Tuple was deleted, so don't return it */ - if (epq_pushed) - EvalPlanQualPop(estate, outerPlan); goto lnext; } + /* remember the actually locked tuple's TID */ + tuple.t_self = copyTuple->t_self; /* - * Need to run a recheck subquery. - * Find or create a PQ stack entry. + * Need to run a recheck subquery. Initialize EPQ state + * if we didn't do so already. */ - if (!epq_pushed) + if (!epq_started) { - EvalPlanQualPush(estate, erm->rti, outerPlan); - epq_pushed = true; + EvalPlanQualBegin(&node->lr_epqstate, estate); + epq_started = true; } /* Store target tuple for relation's scan node */ - EvalPlanQualSetTuple(estate, erm->rti, copyTuple); + EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, copyTuple); /* Continue loop until we have all target tuples */ break; @@ -188,11 +178,52 @@ lnext: erm->curCtid = tuple.t_self; } - /* If we need to do EvalPlanQual testing, loop back to do that */ - if (epq_pushed) + /* + * If we need to do EvalPlanQual testing, do so. + */ + if (epq_started) { - node->lr_useEvalPlan = true; - goto lnext; + /* + * First, fetch a copy of any rows that were successfully locked + * without any update having occurred. (We do this in a separate + * pass so as to avoid overhead in the common case where there are + * no concurrent updates.) + */ + foreach(lc, node->lr_rowMarks) + { + ExecRowMark *erm = (ExecRowMark *) lfirst(lc); + HeapTupleData tuple; + Buffer buffer; + + if (EvalPlanQualGetTuple(&node->lr_epqstate, erm->rti) != NULL) + continue; /* it was updated and fetched above */ + + /* okay, fetch the tuple */ + tuple.t_self = erm->curCtid; + if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, + false, NULL)) + elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); + + /* successful, copy and store tuple */ + EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, + heap_copytuple(&tuple)); + ReleaseBuffer(buffer); + } + /* + * Now fetch any non-locked source rows --- the EPQ logic knows + * how to do that. + */ + EvalPlanQualSetSlot(&node->lr_epqstate, slot); + EvalPlanQualFetchRowMarks(&node->lr_epqstate); + /* + * And finally we can re-evaluate the tuple. + */ + slot = EvalPlanQualNext(&node->lr_epqstate); + if (TupIsNull(slot)) + { + /* Updated tuple fails qual, so ignore it and go on */ + goto lnext; + } } /* Got all locks, so return the current tuple */ @@ -210,8 +241,7 @@ LockRowsState * ExecInitLockRows(LockRows *node, EState *estate, int eflags) { LockRowsState *lrstate; - Plan *outerPlan; - JunkFilter *j; + Plan *outerPlan = outerPlan(node); ListCell *lc; /* check for unsupported flags */ @@ -223,7 +253,7 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) lrstate = makeNode(LockRowsState); lrstate->ps.plan = (Plan *) node; lrstate->ps.state = estate; - lrstate->lr_useEvalPlan = false; + EvalPlanQualInit(&lrstate->lr_epqstate, estate, outerPlan, node->epqParam); /* * Miscellaneous initialization @@ -239,7 +269,6 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) /* * then initialize outer plan */ - outerPlan = outerPlan(node); outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags); /* @@ -250,28 +279,18 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) lrstate->ps.ps_ProjInfo = NULL; /* - * Initialize a junkfilter that we'll use to extract the ctid junk - * attributes. (We won't actually apply the filter to remove the - * junk, we just pass the rows on as-is. This is because the - * junkfilter isn't smart enough to not remove junk attrs that - * might be needed further up.) - */ - j = ExecInitJunkFilter(outerPlan->targetlist, false, - ExecInitExtraTupleSlot(estate)); - lrstate->lr_junkFilter = j; - - /* * Locate the ExecRowMark(s) that this node is responsible for. * (InitPlan should already have built the global list of ExecRowMarks.) */ lrstate->lr_rowMarks = NIL; foreach(lc, node->rowMarks) { - RowMarkClause *rc = (RowMarkClause *) lfirst(lc); + PlanRowMark *rc = (PlanRowMark *) lfirst(lc); ExecRowMark *erm = NULL; - char resname[32]; ListCell *lce; + Assert(IsA(rc, PlanRowMark)); + /* ignore "parent" rowmarks; they are irrelevant at runtime */ if (rc->isParent) continue; @@ -279,36 +298,24 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) foreach(lce, estate->es_rowMarks) { erm = (ExecRowMark *) lfirst(lce); - if (erm->rti == rc->rti && - erm->prti == rc->prti && - erm->rowmarkId == rc->rowmarkId) + if (erm->rti == rc->rti) break; erm = NULL; } if (erm == NULL) - elog(ERROR, "failed to find ExecRowMark for RowMarkClause"); - if (AttributeNumberIsValid(erm->ctidAttNo)) - elog(ERROR, "ExecRowMark is already claimed"); - - /* Locate the junk attribute columns in the subplan output */ - - /* always need the ctid */ - snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId); - erm->ctidAttNo = ExecFindJunkAttribute(j, resname); - if (!AttributeNumberIsValid(erm->ctidAttNo)) - elog(ERROR, "could not find junk \"%s\" column", - resname); - /* if child relation, need tableoid too */ - if (erm->rti != erm->prti) - { - snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId); - erm->toidAttNo = ExecFindJunkAttribute(j, resname); - if (!AttributeNumberIsValid(erm->toidAttNo)) - elog(ERROR, "could not find junk \"%s\" column", - resname); - } - - lrstate->lr_rowMarks = lappend(lrstate->lr_rowMarks, erm); + elog(ERROR, "failed to find ExecRowMark for PlanRowMark %u", + rc->rti); + + /* + * Only locking rowmarks go into our own list. Non-locking marks + * are passed off to the EvalPlanQual machinery. This is because + * we don't want to bother fetching non-locked rows unless we + * actually have to do an EPQ recheck. + */ + if (RowMarkRequiresRowShareLock(erm->markType)) + lrstate->lr_rowMarks = lappend(lrstate->lr_rowMarks, erm); + else + EvalPlanQualAddRowMark(&lrstate->lr_epqstate, erm); } return lrstate; @@ -324,6 +331,7 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) void ExecEndLockRows(LockRowsState *node) { + EvalPlanQualEnd(&node->lr_epqstate); ExecEndNode(outerPlanState(node)); } @@ -331,8 +339,6 @@ ExecEndLockRows(LockRowsState *node) void ExecReScanLockRows(LockRowsState *node, ExprContext *exprCtxt) { - node->lr_useEvalPlan = false; - /* * if chgParam of subnode is not null then plan will be re-scanned by * first ExecProcNode. |