mirror of
https://github.com/postgres/postgres.git
synced 2025-07-08 11:42:09 +03:00
Revise handling of dropped columns in JOIN alias lists to avoid a
performance problem pointed out by phil@vodafone: to wit, we were spending O(N^2) time to check dropped-ness in an N-deep join tree, even in the case where the tree was freshly constructed and couldn't possibly mention any dropped columns. Instead of recursing in get_rte_attribute_is_dropped(), change the data structure definition: the joinaliasvars list of a JOIN RTE must have a NULL Const instead of a Var at any position that references a now-dropped column. This costs nothing during normal parse-rewrite-plan path, and instead we have a linear-time update to make when loading a stored rule that might contain now-dropped columns. While at it, move the responsibility for acquring locks on relations referenced by rules into this separate function (which I therefore chose to call AcquireRewriteLocks). This saves effort --- namely, duplicated lock grabs in parser and rewriter --- in the normal path at a cost of one extra non-locked heap_open() in the stored-rule path; seems a good tradeoff. A fringe benefit is that it is now *much* clearer that we acquire lock on relations referenced in rules before we make any rewriter decisions based on their properties. (I don't know of any bug of that ilk, but it wasn't exactly clear before.)
This commit is contained in:
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.152 2005/05/29 18:34:57 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.153 2005/06/03 23:05:28 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -40,6 +40,7 @@ typedef struct rewrite_event
|
||||
CmdType event; /* type of rule being fired */
|
||||
} rewrite_event;
|
||||
|
||||
static bool acquireLocksOnSubLinks(Node *node, void *context);
|
||||
static Query *rewriteRuleAction(Query *parsetree,
|
||||
Query *rule_action,
|
||||
Node *rule_qual,
|
||||
@ -57,6 +58,181 @@ static List *matchLocks(CmdType event, RuleLock *rulelocks,
|
||||
static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
|
||||
|
||||
|
||||
/*
|
||||
* AcquireRewriteLocks -
|
||||
* Acquire suitable locks on all the relations mentioned in the Query.
|
||||
* These locks will ensure that the relation schemas don't change under us
|
||||
* while we are rewriting and planning the query.
|
||||
*
|
||||
* A secondary purpose of this routine is to fix up JOIN RTE references to
|
||||
* dropped columns (see details below). Because the RTEs are modified in
|
||||
* place, it is generally appropriate for the caller of this routine to have
|
||||
* first done a copyObject() to make a writable copy of the querytree in the
|
||||
* current memory context.
|
||||
*
|
||||
* This processing can, and for efficiency's sake should, be skipped when the
|
||||
* querytree has just been built by the parser: parse analysis already got
|
||||
* all the same locks we'd get here, and the parser will have omitted dropped
|
||||
* columns from JOINs to begin with. But we must do this whenever we are
|
||||
* dealing with a querytree produced earlier than the current command.
|
||||
*
|
||||
* About JOINs and dropped columns: although the parser never includes an
|
||||
* already-dropped column in a JOIN RTE's alias var list, it is possible for
|
||||
* such a list in a stored rule to include references to dropped columns.
|
||||
* (If the column is not explicitly referenced anywhere else in the query,
|
||||
* the dependency mechanism won't consider it used by the rule and so won't
|
||||
* prevent the column drop.) To support get_rte_attribute_is_dropped(),
|
||||
* we replace join alias vars that reference dropped columns with NULL Const
|
||||
* nodes.
|
||||
*
|
||||
* (In PostgreSQL 8.0, we did not do this processing but instead had
|
||||
* get_rte_attribute_is_dropped() recurse to detect dropped columns in joins.
|
||||
* That approach had horrible performance unfortunately; in particular
|
||||
* construction of a nested join was O(N^2) in the nesting depth.)
|
||||
*/
|
||||
void
|
||||
AcquireRewriteLocks(Query *parsetree)
|
||||
{
|
||||
ListCell *l;
|
||||
int rt_index;
|
||||
|
||||
/*
|
||||
* First, process RTEs of the current query level.
|
||||
*/
|
||||
rt_index = 0;
|
||||
foreach(l, parsetree->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
|
||||
Relation rel;
|
||||
LOCKMODE lockmode;
|
||||
List *newaliasvars;
|
||||
ListCell *ll;
|
||||
|
||||
++rt_index;
|
||||
switch (rte->rtekind)
|
||||
{
|
||||
case RTE_RELATION:
|
||||
/*
|
||||
* Grab the appropriate lock type for the relation, and
|
||||
* do not release it until end of transaction. This protects
|
||||
* the rewriter and planner against schema changes mid-query.
|
||||
*
|
||||
* If the relation is the query's result relation, then we
|
||||
* need RowExclusiveLock. Otherwise, check to see if the
|
||||
* relation is accessed FOR UPDATE/SHARE or not. We can't
|
||||
* just grab AccessShareLock because then the executor
|
||||
* would be trying to upgrade the lock, leading to possible
|
||||
* deadlocks.
|
||||
*/
|
||||
if (rt_index == parsetree->resultRelation)
|
||||
lockmode = RowExclusiveLock;
|
||||
else if (list_member_int(parsetree->rowMarks, rt_index))
|
||||
lockmode = RowShareLock;
|
||||
else
|
||||
lockmode = AccessShareLock;
|
||||
|
||||
rel = heap_open(rte->relid, lockmode);
|
||||
heap_close(rel, NoLock);
|
||||
break;
|
||||
|
||||
case RTE_JOIN:
|
||||
/*
|
||||
* Scan the join's alias var list to see if any columns
|
||||
* have been dropped, and if so replace those Vars with
|
||||
* NULL Consts.
|
||||
*/
|
||||
newaliasvars = NIL;
|
||||
foreach(ll, rte->joinaliasvars)
|
||||
{
|
||||
Var *aliasvar = (Var *) lfirst(ll);
|
||||
|
||||
/*
|
||||
* If the list item isn't a simple Var, then it must
|
||||
* represent a merged column, ie a USING column, and so it
|
||||
* couldn't possibly be dropped, since it's referenced in
|
||||
* the join clause. (Conceivably it could also be a
|
||||
* NULL constant already? But that's OK too.)
|
||||
*/
|
||||
if (IsA(aliasvar, Var))
|
||||
{
|
||||
/*
|
||||
* The elements of an alias list have to refer to
|
||||
* earlier RTEs of the same rtable, because that's
|
||||
* the order the planner builds things in. So we
|
||||
* already processed the referenced RTE, and so it's
|
||||
* safe to use get_rte_attribute_is_dropped on it.
|
||||
* (This might not hold after rewriting or planning,
|
||||
* but it's OK to assume here.)
|
||||
*/
|
||||
Assert(aliasvar->varlevelsup == 0);
|
||||
if (aliasvar->varno >= rt_index)
|
||||
elog(ERROR, "unexpected varno %d in JOIN RTE %d",
|
||||
aliasvar->varno, rt_index);
|
||||
if (get_rte_attribute_is_dropped(
|
||||
rt_fetch(aliasvar->varno, parsetree->rtable),
|
||||
aliasvar->varattno))
|
||||
{
|
||||
/*
|
||||
* can't use vartype here, since that might be a
|
||||
* now-dropped type OID, but it doesn't really
|
||||
* matter what type the Const claims to be.
|
||||
*/
|
||||
aliasvar = (Var *) makeNullConst(INT4OID);
|
||||
}
|
||||
}
|
||||
newaliasvars = lappend(newaliasvars, aliasvar);
|
||||
}
|
||||
rte->joinaliasvars = newaliasvars;
|
||||
break;
|
||||
|
||||
case RTE_SUBQUERY:
|
||||
/*
|
||||
* The subquery RTE itself is all right, but we have to
|
||||
* recurse to process the represented subquery.
|
||||
*/
|
||||
AcquireRewriteLocks(rte->subquery);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* ignore other types of RTEs */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Recurse into sublink subqueries, too. But we already did the ones
|
||||
* in the rtable.
|
||||
*/
|
||||
if (parsetree->hasSubLinks)
|
||||
query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
|
||||
QTW_IGNORE_RT_SUBQUERIES);
|
||||
}
|
||||
|
||||
/*
|
||||
* Walker to find sublink subqueries for AcquireRewriteLocks
|
||||
*/
|
||||
static bool
|
||||
acquireLocksOnSubLinks(Node *node, void *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, SubLink))
|
||||
{
|
||||
SubLink *sub = (SubLink *) node;
|
||||
|
||||
/* Do what we came for */
|
||||
AcquireRewriteLocks((Query *) sub->subselect);
|
||||
/* Fall through to process lefthand args of SubLink */
|
||||
}
|
||||
|
||||
/*
|
||||
* Do NOT recurse into Query nodes, because AcquireRewriteLocks already
|
||||
* processed subselects of subselects for us.
|
||||
*/
|
||||
return expression_tree_walker(node, acquireLocksOnSubLinks, context);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* rewriteRuleAction -
|
||||
* Rewrite the rule action with appropriate qualifiers (taken from
|
||||
@ -82,6 +258,12 @@ rewriteRuleAction(Query *parsetree,
|
||||
rule_action = (Query *) copyObject(rule_action);
|
||||
rule_qual = (Node *) copyObject(rule_qual);
|
||||
|
||||
/*
|
||||
* Acquire necessary locks and fix any deleted JOIN RTE entries.
|
||||
*/
|
||||
AcquireRewriteLocks(rule_action);
|
||||
(void) acquireLocksOnSubLinks(rule_qual, NULL);
|
||||
|
||||
current_varno = rt_index;
|
||||
rt_length = list_length(parsetree->rtable);
|
||||
new_varno = PRS2_NEW_VARNO + rt_length;
|
||||
@ -693,6 +875,9 @@ matchLocks(CmdType event,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ApplyRetrieveRule - expand an ON SELECT rule
|
||||
*/
|
||||
static Query *
|
||||
ApplyRetrieveRule(Query *parsetree,
|
||||
RewriteRule *rule,
|
||||
@ -713,11 +898,16 @@ ApplyRetrieveRule(Query *parsetree,
|
||||
elog(ERROR, "cannot handle per-attribute ON SELECT rule");
|
||||
|
||||
/*
|
||||
* Make a modifiable copy of the view query, and recursively expand
|
||||
* any view references inside it.
|
||||
* Make a modifiable copy of the view query, and acquire needed locks
|
||||
* on the relations it mentions.
|
||||
*/
|
||||
rule_action = copyObject(linitial(rule->actions));
|
||||
|
||||
AcquireRewriteLocks(rule_action);
|
||||
|
||||
/*
|
||||
* Recursively expand any view references inside the view.
|
||||
*/
|
||||
rule_action = fireRIRrules(rule_action, activeRIRs);
|
||||
|
||||
/*
|
||||
@ -868,7 +1058,6 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
|
||||
List *locks;
|
||||
RuleLock *rules;
|
||||
RewriteRule *rule;
|
||||
LOCKMODE lockmode;
|
||||
int i;
|
||||
|
||||
++rt_index;
|
||||
@ -904,26 +1093,10 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* This may well be the first access to the relation during the
|
||||
* current statement (it will be, if this Query was extracted from
|
||||
* a rule or somehow got here other than via the parser).
|
||||
* Therefore, grab the appropriate lock type for the relation, and
|
||||
* do not release it until end of transaction. This protects the
|
||||
* rewriter and planner against schema changes mid-query.
|
||||
*
|
||||
* If the relation is the query's result relation, then
|
||||
* RewriteQuery() already got the right lock on it, so we need no
|
||||
* additional lock. Otherwise, check to see if the relation is
|
||||
* accessed FOR UPDATE/SHARE or not.
|
||||
* We can use NoLock here since either the parser or
|
||||
* AcquireRewriteLocks should have locked the rel already.
|
||||
*/
|
||||
if (rt_index == parsetree->resultRelation)
|
||||
lockmode = NoLock;
|
||||
else if (list_member_int(parsetree->rowMarks, rt_index))
|
||||
lockmode = RowShareLock;
|
||||
else
|
||||
lockmode = AccessShareLock;
|
||||
|
||||
rel = heap_open(rte->relid, lockmode);
|
||||
rel = heap_open(rte->relid, NoLock);
|
||||
|
||||
/*
|
||||
* Collect the RIR rules that we must apply
|
||||
@ -1015,9 +1188,17 @@ CopyAndAddInvertedQual(Query *parsetree,
|
||||
int rt_index,
|
||||
CmdType event)
|
||||
{
|
||||
Query *new_tree = (Query *) copyObject(parsetree);
|
||||
/* Don't scribble on the passed qual (it's in the relcache!) */
|
||||
Node *new_qual = (Node *) copyObject(rule_qual);
|
||||
|
||||
/*
|
||||
* In case there are subqueries in the qual, acquire necessary locks and
|
||||
* fix any deleted JOIN RTE entries. (This is somewhat redundant with
|
||||
* rewriteRuleAction, but not entirely ... consider restructuring so
|
||||
* that we only need to process the qual this way once.)
|
||||
*/
|
||||
(void) acquireLocksOnSubLinks(new_qual, NULL);
|
||||
|
||||
/* Fix references to OLD */
|
||||
ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
|
||||
/* Fix references to NEW */
|
||||
@ -1030,9 +1211,9 @@ CopyAndAddInvertedQual(Query *parsetree,
|
||||
event,
|
||||
rt_index);
|
||||
/* And attach the fixed qual */
|
||||
AddInvertedQual(new_tree, new_qual);
|
||||
AddInvertedQual(parsetree, new_qual);
|
||||
|
||||
return new_tree;
|
||||
return parsetree;
|
||||
}
|
||||
|
||||
|
||||
@ -1112,7 +1293,7 @@ fireRules(Query *parsetree,
|
||||
if (!*instead_flag)
|
||||
{
|
||||
if (*qual_product == NULL)
|
||||
*qual_product = parsetree;
|
||||
*qual_product = copyObject(parsetree);
|
||||
*qual_product = CopyAndAddInvertedQual(*qual_product,
|
||||
event_qual,
|
||||
rt_index,
|
||||
@ -1177,15 +1358,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
|
||||
Assert(rt_entry->rtekind == RTE_RELATION);
|
||||
|
||||
/*
|
||||
* This may well be the first access to the result relation during
|
||||
* the current statement (it will be, if this Query was extracted
|
||||
* from a rule or somehow got here other than via the parser).
|
||||
* Therefore, grab the appropriate lock type for a result
|
||||
* relation, and do not release it until end of transaction. This
|
||||
* protects the rewriter and planner against schema changes
|
||||
* mid-query.
|
||||
* We can use NoLock here since either the parser or
|
||||
* AcquireRewriteLocks should have locked the rel already.
|
||||
*/
|
||||
rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
|
||||
rt_entry_relation = heap_open(rt_entry->relid, NoLock);
|
||||
|
||||
/*
|
||||
* If it's an INSERT or UPDATE, rewrite the targetlist into
|
||||
@ -1251,7 +1427,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
|
||||
}
|
||||
}
|
||||
|
||||
heap_close(rt_entry_relation, NoLock); /* keep lock! */
|
||||
heap_close(rt_entry_relation, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1295,8 +1471,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
|
||||
* Rewrite one query via query rewrite system, possibly returning 0
|
||||
* or many queries.
|
||||
*
|
||||
* NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was
|
||||
* moved here so that it would be invoked during EXPLAIN.
|
||||
* NOTE: the parsetree must either have come straight from the parser,
|
||||
* or have been scanned by AcquireRewriteLocks to acquire suitable locks.
|
||||
*/
|
||||
List *
|
||||
QueryRewrite(Query *parsetree)
|
||||
|
Reference in New Issue
Block a user