diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2018-12-13 13:24:43 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2018-12-13 13:24:43 -0500 |
commit | 04fe805a1734eccd8dcdd34c8cc0ddcb62c7240c (patch) | |
tree | 5e7731081fd4fd783141753c079e772fb452a999 /src/backend/utils/cache/plancache.c | |
parent | 52ac6cd2d0cd70e01291e0ac4ee6d068b69bc478 (diff) | |
download | postgresql-04fe805a1734eccd8dcdd34c8cc0ddcb62c7240c.tar.gz postgresql-04fe805a1734eccd8dcdd34c8cc0ddcb62c7240c.zip |
Drop no-op CoerceToDomain nodes from expressions at planning time.
If a domain has no constraints, then CoerceToDomain doesn't really do
anything and can be simplified to a RelabelType. This not only
eliminates cycles at execution, but allows the planner to optimize better
(for instance, match the coerced expression to an index on the underlying
column). However, we do have to support invalidating the plan later if
a constraint gets added to the domain. That's comparable to the case of
a change to a SQL function that had been inlined into a plan, so all the
necessary logic already exists for plans depending on functions. We
need only duplicate or share that logic for domains.
ALTER DOMAIN ADD/DROP CONSTRAINT need to be taught to send out sinval
messages for the domain's pg_type entry, since those operations don't
update that row. (ALTER DOMAIN SET/DROP NOT NULL do update that row,
so no code change is needed for them.)
Testing this revealed what's really a pre-existing bug in plpgsql:
it caches the SQL-expression-tree expansion of type coercions and
had no provision for invalidating entries in that cache. Up to now
that was only a problem if such an expression had inlined a SQL
function that got changed, which is unlikely though not impossible.
But failing to track changes of domain constraints breaks an existing
regression test case and would likely cause practical problems too.
We could fix that locally in plpgsql, but what seems like a better
idea is to build some generic infrastructure in plancache.c to store
standalone expressions and track invalidation events for them.
(It's tempting to wonder whether plpgsql's "simple expression" stuff
could use this code with lower overhead than its current use of the
heavyweight plancache APIs. But I've left that idea for later.)
Other stuff fixed in passing:
* Allow estimate_expression_value() to drop CoerceToDomain
unconditionally, effectively assuming that the coercion will succeed.
This will improve planner selectivity estimates for cases involving
estimatable expressions that are coerced to domains. We could have
done this independently of everything else here, but there wasn't
previously any need for eval_const_expressions_mutator to know about
CoerceToDomain at all.
* Use a dlist for plancache.c's list of cached plans, rather than a
manually threaded singly-linked list. That eliminates a potential
performance problem in DropCachedPlan.
* Fix a couple of inconsistencies in typecmds.c about whether
operations on domains drop RowExclusiveLock on pg_type. Our common
practice is that DDL operations do drop catalog locks, so standardize
on that choice.
Discussion: https://postgr.es/m/19958.1544122124@sss.pgh.pa.us
Diffstat (limited to 'src/backend/utils/cache/plancache.c')
-rw-r--r-- | src/backend/utils/cache/plancache.c | 228 |
1 files changed, 182 insertions, 46 deletions
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 9ec81c5f367..2201752ffee 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -27,15 +27,21 @@ * query to change output tupdesc on replan --- if so, it's up to the * caller to notice changes and cope with them. * - * Currently, we track exactly the dependencies of plans on relations and - * user-defined functions. On relcache invalidation events or pg_proc - * syscache invalidation events, we invalidate just those plans that depend - * on the particular object being modified. (Note: this scheme assumes - * that any table modification that requires replanning will generate a - * relcache inval event.) We also watch for inval events on certain other - * system catalogs, such as pg_namespace; but for them, our response is - * just to invalidate all plans. We expect updates on those catalogs to - * be infrequent enough that more-detailed tracking is not worth the effort. + * Currently, we track exactly the dependencies of plans on relations, + * user-defined functions, and domains. On relcache invalidation events or + * pg_proc or pg_type syscache invalidation events, we invalidate just those + * plans that depend on the particular object being modified. (Note: this + * scheme assumes that any table modification that requires replanning will + * generate a relcache inval event.) We also watch for inval events on + * certain other system catalogs, such as pg_namespace; but for them, our + * response is just to invalidate all plans. We expect updates on those + * catalogs to be infrequent enough that more-detailed tracking is not worth + * the effort. + * + * In addition to full-fledged query plans, we provide a facility for + * detecting invalidations of simple scalar expressions. This is fairly + * bare-bones; it's the caller's responsibility to build a new expression + * if the old one gets invalidated. * * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group @@ -57,6 +63,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/cost.h" #include "optimizer/planmain.h" +#include "optimizer/planner.h" #include "optimizer/prep.h" #include "parser/analyze.h" #include "parser/parsetree.h" @@ -82,10 +89,15 @@ /* * This is the head of the backend's list of "saved" CachedPlanSources (i.e., * those that are in long-lived storage and are examined for sinval events). - * We thread the structs manually instead of using List cells so that we can - * guarantee to save a CachedPlanSource without error. + * We use a dlist instead of separate List cells so that we can guarantee + * to save a CachedPlanSource without error. + */ +static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list); + +/* + * This is the head of the backend's list of CachedExpressions. */ -static CachedPlanSource *first_saved_plan = NULL; +static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list); static void ReleaseGenericPlan(CachedPlanSource *plansource); static List *RevalidateCachedQuery(CachedPlanSource *plansource, @@ -103,7 +115,7 @@ static void ScanQueryForLocks(Query *parsetree, bool acquire); static bool ScanQueryWalker(Node *node, bool *acquire); static TupleDesc PlanCacheComputeResultDesc(List *stmt_list); static void PlanCacheRelCallback(Datum arg, Oid relid); -static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue); +static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); /* GUC parameter */ @@ -118,7 +130,8 @@ void InitPlanCache(void) { CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0); - CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0); + CacheRegisterSyscacheCallback(PROCOID, PlanCacheObjectCallback, (Datum) 0); + CacheRegisterSyscacheCallback(TYPEOID, PlanCacheObjectCallback, (Datum) 0); CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); @@ -206,7 +219,6 @@ CreateCachedPlan(RawStmt *raw_parse_tree, plansource->is_saved = false; plansource->is_valid = false; plansource->generation = 0; - plansource->next_saved = NULL; plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; @@ -274,7 +286,6 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree, plansource->is_saved = false; plansource->is_valid = false; plansource->generation = 0; - plansource->next_saved = NULL; plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; @@ -471,8 +482,7 @@ SaveCachedPlan(CachedPlanSource *plansource) /* * Add the entry to the global list of cached plans. */ - plansource->next_saved = first_saved_plan; - first_saved_plan = plansource; + dlist_push_tail(&saved_plan_list, &plansource->node); plansource->is_saved = true; } @@ -493,21 +503,7 @@ DropCachedPlan(CachedPlanSource *plansource) /* If it's been saved, remove it from the list */ if (plansource->is_saved) { - if (first_saved_plan == plansource) - first_saved_plan = plansource->next_saved; - else - { - CachedPlanSource *psrc; - - for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved) - { - if (psrc->next_saved == plansource) - { - psrc->next_saved = plansource->next_saved; - break; - } - } - } + dlist_delete(&plansource->node); plansource->is_saved = false; } @@ -1399,7 +1395,6 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->is_saved = false; newsource->is_valid = plansource->is_valid; newsource->generation = plansource->generation; - newsource->next_saved = NULL; /* We may as well copy any acquired cost knowledge */ newsource->generic_cost = plansource->generic_cost; @@ -1459,6 +1454,85 @@ CachedPlanGetTargetList(CachedPlanSource *plansource, } /* + * GetCachedExpression: construct a CachedExpression for an expression. + * + * This performs the same transformations on the expression as + * expression_planner(), ie, convert an expression as emitted by parse + * analysis to be ready to pass to the executor. + * + * The result is stashed in a private, long-lived memory context. + * (Note that this might leak a good deal of memory in the caller's + * context before that.) The passed-in expr tree is not modified. + */ +CachedExpression * +GetCachedExpression(Node *expr) +{ + CachedExpression *cexpr; + List *relationOids; + List *invalItems; + MemoryContext cexpr_context; + MemoryContext oldcxt; + + /* + * Pass the expression through the planner, and collect dependencies. + * Everything built here is leaked in the caller's context; that's + * intentional to minimize the size of the permanent data structure. + */ + expr = (Node *) expression_planner_with_deps((Expr *) expr, + &relationOids, + &invalItems); + + /* + * Make a private memory context, and copy what we need into that. To + * avoid leaking a long-lived context if we fail while copying data, we + * initially make the context under the caller's context. + */ + cexpr_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedExpression", + ALLOCSET_SMALL_SIZES); + + oldcxt = MemoryContextSwitchTo(cexpr_context); + + cexpr = (CachedExpression *) palloc(sizeof(CachedExpression)); + cexpr->magic = CACHEDEXPR_MAGIC; + cexpr->expr = copyObject(expr); + cexpr->is_valid = true; + cexpr->relationOids = copyObject(relationOids); + cexpr->invalItems = copyObject(invalItems); + cexpr->context = cexpr_context; + + MemoryContextSwitchTo(oldcxt); + + /* + * Reparent the expr's memory context under CacheMemoryContext so that it + * will live indefinitely. + */ + MemoryContextSetParent(cexpr_context, CacheMemoryContext); + + /* + * Add the entry to the global list of cached expressions. + */ + dlist_push_tail(&cached_expression_list, &cexpr->node); + + return cexpr; +} + +/* + * FreeCachedExpression + * Delete a CachedExpression. + */ +void +FreeCachedExpression(CachedExpression *cexpr) +{ + /* Sanity check */ + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + /* Unlink from global list */ + dlist_delete(&cexpr->node); + /* Free all storage associated with CachedExpression */ + MemoryContextDelete(cexpr->context); +} + +/* * QueryListGetPrimaryStmt * Get the "primary" stmt within a list, ie, the one marked canSetTag. * @@ -1692,10 +1766,13 @@ PlanCacheComputeResultDesc(List *stmt_list) static void PlanCacheRelCallback(Datum arg, Oid relid) { - CachedPlanSource *plansource; + dlist_iter iter; - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + dlist_foreach(iter, &saved_plan_list) { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ @@ -1742,25 +1819,43 @@ PlanCacheRelCallback(Datum arg, Oid relid) } } } + + /* Likewise check cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + /* No work if it's already invalidated */ + if (!cexpr->is_valid) + continue; + + if ((relid == InvalidOid) ? cexpr->relationOids != NIL : + list_member_oid(cexpr->relationOids, relid)) + { + cexpr->is_valid = false; + } + } } /* - * PlanCacheFuncCallback - * Syscache inval callback function for PROCOID cache + * PlanCacheObjectCallback + * Syscache inval callback function for PROCOID and TYPEOID caches * * Invalidate all plans mentioning the object with the specified hash value, * or all plans mentioning any member of this cache if hashvalue == 0. - * - * Note that the coding would support use for multiple caches, but right - * now only user-defined functions are tracked this way. */ static void -PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) +PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue) { - CachedPlanSource *plansource; + dlist_iter iter; - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + dlist_foreach(iter, &saved_plan_list) { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); ListCell *lc; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); @@ -1825,6 +1920,34 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) } } } + + /* Likewise check cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + ListCell *lc; + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + /* No work if it's already invalidated */ + if (!cexpr->is_valid) + continue; + + foreach(lc, cexpr->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); + + if (item->cacheId != cacheid) + continue; + if (hashvalue == 0 || + item->hashValue == hashvalue) + { + cexpr->is_valid = false; + break; + } + } + } } /* @@ -1845,10 +1968,12 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) void ResetPlanCache(void) { - CachedPlanSource *plansource; + dlist_iter iter; - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + dlist_foreach(iter, &saved_plan_list) { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); ListCell *lc; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); @@ -1888,4 +2013,15 @@ ResetPlanCache(void) } } } + + /* Likewise invalidate cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + cexpr->is_valid = false; + } } |