mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
Improve the plan cache invalidation mechanism to make it invalidate plans
when user-defined functions used in a plan are modified. Also invalidate plans when schemas, operators, or operator classes are modified; but for these cases we just invalidate everything rather than tracking exact dependencies, since these types of objects seldom change in a production database. Tom Lane; loosely based on a patch by Martin Pihlak.
This commit is contained in:
262
src/backend/utils/cache/plancache.c
vendored
262
src/backend/utils/cache/plancache.c
vendored
@ -12,7 +12,7 @@
|
||||
*
|
||||
* The plan cache manager itself is principally responsible for tracking
|
||||
* whether cached plans should be invalidated because of schema changes in
|
||||
* the tables they depend on. When (and if) the next demand for a cached
|
||||
* the objects they depend on. When (and if) the next demand for a cached
|
||||
* plan occurs, the query will be replanned. Note that this could result
|
||||
* in an error, for example if a column referenced by the query is no
|
||||
* longer present. The creator of a cached plan can specify whether it
|
||||
@ -20,20 +20,22 @@
|
||||
* could happen with "SELECT *" for example) --- if so, it's up to the
|
||||
* caller to notice changes and cope with them.
|
||||
*
|
||||
* Currently, we use only relcache invalidation events to invalidate plans.
|
||||
* This means that changes such as modification of a function definition do
|
||||
* not invalidate plans using the function. This is not 100% OK --- for
|
||||
* example, changing a SQL function that's been inlined really ought to
|
||||
* cause invalidation of the plan that it's been inlined into --- but the
|
||||
* cost of tracking additional types of object seems much higher than the
|
||||
* gain, so we're just ignoring them for now.
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.20 2008/08/25 22:42:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.21 2008/09/09 18:58:08 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -44,6 +46,7 @@
|
||||
#include "catalog/namespace.h"
|
||||
#include "executor/executor.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "optimizer/planmain.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "tcop/pquery.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
@ -52,19 +55,7 @@
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/snapmgr.h"
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void (*callback) ();
|
||||
void *arg;
|
||||
} ScanQueryWalkerContext;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Oid inval_relid;
|
||||
CachedPlan *plan;
|
||||
} InvalRelidContext;
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
static List *cached_plans_list = NIL;
|
||||
@ -73,28 +64,28 @@ static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
|
||||
MemoryContext plan_context);
|
||||
static void AcquireExecutorLocks(List *stmt_list, bool acquire);
|
||||
static void AcquirePlannerLocks(List *stmt_list, bool acquire);
|
||||
static void LockRelid(Oid relid, LOCKMODE lockmode, void *arg);
|
||||
static void UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg);
|
||||
static void ScanQueryForRelids(Query *parsetree,
|
||||
void (*callback) (),
|
||||
void *arg);
|
||||
static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
|
||||
static void ScanQueryForLocks(Query *parsetree, bool acquire);
|
||||
static bool ScanQueryWalker(Node *node, bool *acquire);
|
||||
static bool rowmark_member(List *rowMarks, int rt_index);
|
||||
static bool plan_list_is_transient(List *stmt_list);
|
||||
static void PlanCacheCallback(Datum arg, Oid relid);
|
||||
static void InvalRelid(Oid relid, LOCKMODE lockmode,
|
||||
InvalRelidContext *context);
|
||||
static void PlanCacheRelCallback(Datum arg, Oid relid);
|
||||
static void PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
|
||||
static void PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
|
||||
|
||||
|
||||
/*
|
||||
* InitPlanCache: initialize module during InitPostgres.
|
||||
*
|
||||
* All we need to do is hook into inval.c's callback list.
|
||||
* All we need to do is hook into inval.c's callback lists.
|
||||
*/
|
||||
void
|
||||
InitPlanCache(void)
|
||||
{
|
||||
CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0);
|
||||
CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -337,6 +328,18 @@ StoreCachedPlan(CachedPlanSource *plansource,
|
||||
plan->refcount = 1; /* for the parent's link */
|
||||
plan->generation = ++(plansource->generation);
|
||||
plan->context = plan_context;
|
||||
if (plansource->fully_planned)
|
||||
{
|
||||
/* Planner already extracted dependencies, we don't have to */
|
||||
plan->relationOids = plan->invalItems = NIL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Use the planner machinery to extract dependencies */
|
||||
extract_query_dependencies(stmt_list,
|
||||
&plan->relationOids,
|
||||
&plan->invalItems);
|
||||
}
|
||||
|
||||
Assert(plansource->plan == NULL);
|
||||
plansource->plan = plan;
|
||||
@ -432,8 +435,8 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
|
||||
plan->dead = true;
|
||||
|
||||
/*
|
||||
* By now, if any invalidation has happened, PlanCacheCallback will
|
||||
* have marked the plan dead.
|
||||
* By now, if any invalidation has happened, the inval callback
|
||||
* functions will have marked the plan dead.
|
||||
*/
|
||||
if (plan->dead)
|
||||
{
|
||||
@ -637,37 +640,15 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
|
||||
Query *query = (Query *) lfirst(lc);
|
||||
|
||||
Assert(IsA(query, Query));
|
||||
if (acquire)
|
||||
ScanQueryForRelids(query, LockRelid, NULL);
|
||||
else
|
||||
ScanQueryForRelids(query, UnlockRelid, NULL);
|
||||
ScanQueryForLocks(query, acquire);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ScanQueryForRelids callback functions for AcquirePlannerLocks
|
||||
* ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks.
|
||||
*/
|
||||
static void
|
||||
LockRelid(Oid relid, LOCKMODE lockmode, void *arg)
|
||||
{
|
||||
LockRelationOid(relid, lockmode);
|
||||
}
|
||||
|
||||
static void
|
||||
UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg)
|
||||
{
|
||||
UnlockRelationOid(relid, lockmode);
|
||||
}
|
||||
|
||||
/*
|
||||
* ScanQueryForRelids: recursively scan one Query and apply the callback
|
||||
* function to each relation OID found therein. The callback function
|
||||
* takes the arguments relation OID, lockmode, pointer arg.
|
||||
*/
|
||||
static void
|
||||
ScanQueryForRelids(Query *parsetree,
|
||||
void (*callback) (),
|
||||
void *arg)
|
||||
ScanQueryForLocks(Query *parsetree, bool acquire)
|
||||
{
|
||||
ListCell *lc;
|
||||
int rt_index;
|
||||
@ -685,27 +666,22 @@ ScanQueryForRelids(Query *parsetree,
|
||||
switch (rte->rtekind)
|
||||
{
|
||||
case RTE_RELATION:
|
||||
|
||||
/*
|
||||
* Determine the lock type required for this RTE.
|
||||
*/
|
||||
/* Acquire or release the appropriate type of lock */
|
||||
if (rt_index == parsetree->resultRelation)
|
||||
lockmode = RowExclusiveLock;
|
||||
else if (rowmark_member(parsetree->rowMarks, rt_index))
|
||||
lockmode = RowShareLock;
|
||||
else
|
||||
lockmode = AccessShareLock;
|
||||
|
||||
(*callback) (rte->relid, lockmode, arg);
|
||||
if (acquire)
|
||||
LockRelationOid(rte->relid, lockmode);
|
||||
else
|
||||
UnlockRelationOid(rte->relid, lockmode);
|
||||
break;
|
||||
|
||||
case RTE_SUBQUERY:
|
||||
|
||||
/*
|
||||
* The subquery RTE itself is all right, but we have to
|
||||
* recurse to process the represented subquery.
|
||||
*/
|
||||
ScanQueryForRelids(rte->subquery, callback, arg);
|
||||
/* Recurse into subquery-in-FROM */
|
||||
ScanQueryForLocks(rte->subquery, acquire);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -720,21 +696,17 @@ ScanQueryForRelids(Query *parsetree,
|
||||
*/
|
||||
if (parsetree->hasSubLinks)
|
||||
{
|
||||
ScanQueryWalkerContext context;
|
||||
|
||||
context.callback = callback;
|
||||
context.arg = arg;
|
||||
query_tree_walker(parsetree, ScanQueryWalker,
|
||||
(void *) &context,
|
||||
(void *) &acquire,
|
||||
QTW_IGNORE_RT_SUBQUERIES);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Walker to find sublink subqueries for ScanQueryForRelids
|
||||
* Walker to find sublink subqueries for ScanQueryForLocks
|
||||
*/
|
||||
static bool
|
||||
ScanQueryWalker(Node *node, ScanQueryWalkerContext *context)
|
||||
ScanQueryWalker(Node *node, bool *acquire)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
@ -743,17 +715,16 @@ ScanQueryWalker(Node *node, ScanQueryWalkerContext *context)
|
||||
SubLink *sub = (SubLink *) node;
|
||||
|
||||
/* Do what we came for */
|
||||
ScanQueryForRelids((Query *) sub->subselect,
|
||||
context->callback, context->arg);
|
||||
ScanQueryForLocks((Query *) sub->subselect, *acquire);
|
||||
/* Fall through to process lefthand args of SubLink */
|
||||
}
|
||||
|
||||
/*
|
||||
* Do NOT recurse into Query nodes, because ScanQueryForRelids already
|
||||
* Do NOT recurse into Query nodes, because ScanQueryForLocks already
|
||||
* processed subselects of subselects for us.
|
||||
*/
|
||||
return expression_tree_walker(node, ScanQueryWalker,
|
||||
(void *) context);
|
||||
(void *) acquire);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -863,17 +834,16 @@ PlanCacheComputeResultDesc(List *stmt_list)
|
||||
}
|
||||
|
||||
/*
|
||||
* PlanCacheCallback
|
||||
* PlanCacheRelCallback
|
||||
* Relcache inval callback function
|
||||
*
|
||||
* Invalidate all plans mentioning the given rel, or all plans mentioning
|
||||
* any rel at all if relid == InvalidOid.
|
||||
*/
|
||||
static void
|
||||
PlanCacheCallback(Datum arg, Oid relid)
|
||||
PlanCacheRelCallback(Datum arg, Oid relid)
|
||||
{
|
||||
ListCell *lc1;
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc1, cached_plans_list)
|
||||
{
|
||||
@ -885,6 +855,9 @@ PlanCacheCallback(Datum arg, Oid relid)
|
||||
continue;
|
||||
if (plan->fully_planned)
|
||||
{
|
||||
/* Have to check the per-PlannedStmt relid lists */
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc2, plan->stmt_list)
|
||||
{
|
||||
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
|
||||
@ -903,42 +876,117 @@ PlanCacheCallback(Datum arg, Oid relid)
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* For not-fully-planned entries we use ScanQueryForRelids, since
|
||||
* a recursive traversal is needed. The callback API is a bit
|
||||
* tedious but avoids duplication of coding.
|
||||
*/
|
||||
InvalRelidContext context;
|
||||
/* Otherwise check the single list we built ourselves */
|
||||
if ((relid == InvalidOid) ? plan->relationOids != NIL :
|
||||
list_member_oid(plan->relationOids, relid))
|
||||
plan->dead = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.inval_relid = relid;
|
||||
context.plan = plan;
|
||||
/*
|
||||
* PlanCacheFuncCallback
|
||||
* Syscache inval callback function for PROCOID cache
|
||||
*
|
||||
* Invalidate all plans mentioning the given catalog entry, or all plans
|
||||
* mentioning any member of this cache if tuplePtr == NULL.
|
||||
*
|
||||
* 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, ItemPointer tuplePtr)
|
||||
{
|
||||
ListCell *lc1;
|
||||
|
||||
foreach(lc1, cached_plans_list)
|
||||
{
|
||||
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
|
||||
CachedPlan *plan = plansource->plan;
|
||||
|
||||
/* No work if it's already invalidated */
|
||||
if (!plan || plan->dead)
|
||||
continue;
|
||||
if (plan->fully_planned)
|
||||
{
|
||||
/* Have to check the per-PlannedStmt inval-item lists */
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc2, plan->stmt_list)
|
||||
{
|
||||
Query *query = (Query *) lfirst(lc2);
|
||||
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
|
||||
ListCell *lc3;
|
||||
|
||||
Assert(IsA(query, Query));
|
||||
ScanQueryForRelids(query, InvalRelid, (void *) &context);
|
||||
Assert(!IsA(plannedstmt, Query));
|
||||
if (!IsA(plannedstmt, PlannedStmt))
|
||||
continue; /* Ignore utility statements */
|
||||
foreach(lc3, plannedstmt->invalItems)
|
||||
{
|
||||
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3);
|
||||
|
||||
if (item->cacheId != cacheid)
|
||||
continue;
|
||||
if (tuplePtr == NULL ||
|
||||
ItemPointerEquals(tuplePtr, &item->tupleId))
|
||||
{
|
||||
/* Invalidate the plan! */
|
||||
plan->dead = true;
|
||||
break; /* out of invalItems scan */
|
||||
}
|
||||
}
|
||||
if (plan->dead)
|
||||
break; /* out of stmt_list scan */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise check the single list we built ourselves */
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc2, plan->invalItems)
|
||||
{
|
||||
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
|
||||
|
||||
if (item->cacheId != cacheid)
|
||||
continue;
|
||||
if (tuplePtr == NULL ||
|
||||
ItemPointerEquals(tuplePtr, &item->tupleId))
|
||||
{
|
||||
/* Invalidate the plan! */
|
||||
plan->dead = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PlanCacheSysCallback
|
||||
* Syscache inval callback function for other caches
|
||||
*
|
||||
* Just invalidate everything...
|
||||
*/
|
||||
static void
|
||||
PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
|
||||
{
|
||||
ResetPlanCache();
|
||||
}
|
||||
|
||||
/*
|
||||
* ResetPlanCache: drop all cached plans.
|
||||
*/
|
||||
void
|
||||
ResetPlanCache(void)
|
||||
{
|
||||
PlanCacheCallback((Datum) 0, InvalidOid);
|
||||
}
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* ScanQueryForRelids callback function for PlanCacheCallback
|
||||
*/
|
||||
static void
|
||||
InvalRelid(Oid relid, LOCKMODE lockmode, InvalRelidContext *context)
|
||||
{
|
||||
if (relid == context->inval_relid || context->inval_relid == InvalidOid)
|
||||
context->plan->dead = true;
|
||||
foreach(lc, cached_plans_list)
|
||||
{
|
||||
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
||||
CachedPlan *plan = plansource->plan;
|
||||
|
||||
if (plan)
|
||||
plan->dead = true;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user