1
0
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:
Tom Lane
2008-09-09 18:58:09 +00:00
parent c06629c72e
commit ee33b95d9c
18 changed files with 522 additions and 275 deletions

View File

@ -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;
}
}