plancache.c
来自「postgresql8.3.4源码,开源数据库」· C语言 代码 · 共 1,016 行 · 第 1/2 页
C
1,016 行
/*------------------------------------------------------------------------- * * plancache.c * Plan cache management. * * We can store a cached plan in either fully-planned format, or just * parsed-and-rewritten if the caller wishes to postpone planning until * actual parameter values are available. CachedPlanSource has the same * contents either way, but CachedPlan contains a list of PlannedStmts * and bare utility statements in the first case, or a list of Query nodes * in the second case. * * 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 * 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 * is allowable for the query to change output tupdesc on replan (this * 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. * * * 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.15.2.1 2008/09/15 23:37:49 tgl Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include "utils/plancache.h"#include "access/transam.h"#include "catalog/namespace.h"#include "executor/executor.h"#include "optimizer/clauses.h"#include "storage/lmgr.h"#include "tcop/pquery.h"#include "tcop/tcopprot.h"#include "tcop/utility.h"#include "utils/inval.h"#include "utils/memutils.h"#include "utils/resowner.h"typedef struct{ void (*callback) (); void *arg;} ScanQueryWalkerContext;typedef struct{ Oid inval_relid; CachedPlan *plan;} InvalRelidContext;static List *cached_plans_list = NIL;static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list, MemoryContext plan_context);static List *do_planning(List *querytrees, int cursorOptions);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 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);/* * InitPlanCache: initialize module during InitPostgres. * * All we need to do is hook into inval.c's callback list. */voidInitPlanCache(void){ CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0);}/* * CreateCachedPlan: initially create a plan cache entry. * * The caller must already have successfully parsed/planned the query; * about all that we do here is copy it into permanent storage. * * raw_parse_tree: output of raw_parser() * query_string: original query text (can be NULL if not available, but * that is discouraged because it degrades error message quality) * commandTag: compile-time-constant tag for query, or NULL if empty query * param_types: array of parameter type OIDs, or NULL if none * num_params: number of parameters * cursor_options: options bitmask that was/will be passed to planner * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees * fully_planned: are we caching planner or rewriter output? * fixed_result: TRUE to disallow changes in result tupdesc */CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, const char *query_string, const char *commandTag, Oid *param_types, int num_params, int cursor_options, List *stmt_list, bool fully_planned, bool fixed_result){ CachedPlanSource *plansource; OverrideSearchPath *search_path; MemoryContext source_context; MemoryContext oldcxt; /* * Make a dedicated memory context for the CachedPlanSource and its * subsidiary data. We expect it can be pretty small. */ source_context = AllocSetContextCreate(CacheMemoryContext, "CachedPlanSource", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); /* * Fetch current search_path into new context, but do any recalculation * work required in caller's context. */ search_path = GetOverrideSearchPath(source_context); /* * Create and fill the CachedPlanSource struct within the new context. */ oldcxt = MemoryContextSwitchTo(source_context); plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource)); plansource->raw_parse_tree = copyObject(raw_parse_tree); plansource->query_string = query_string ? pstrdup(query_string) : NULL; plansource->commandTag = commandTag; /* no copying needed */ if (num_params > 0) { plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); } else plansource->param_types = NULL; plansource->num_params = num_params; plansource->cursor_options = cursor_options; plansource->fully_planned = fully_planned; plansource->fixed_result = fixed_result; plansource->search_path = search_path; plansource->generation = 0; /* StoreCachedPlan will increment */ plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); plansource->plan = NULL; plansource->context = source_context; plansource->orig_plan = NULL; /* * Copy the current output plans into the plancache entry. */ StoreCachedPlan(plansource, stmt_list, NULL); /* * Now we can add the entry to the list of cached plans. The List nodes * live in CacheMemoryContext. */ MemoryContextSwitchTo(CacheMemoryContext); cached_plans_list = lappend(cached_plans_list, plansource); MemoryContextSwitchTo(oldcxt); return plansource;}/* * FastCreateCachedPlan: create a plan cache entry with minimal data copying. * * For plans that aren't expected to live very long, the copying overhead of * CreateCachedPlan is annoying. We provide this variant entry point in which * the caller has already placed all the data in a suitable memory context. * The source data and completed plan are in the same context, since this * avoids extra copy steps during plan construction. If the query ever does * need replanning, we'll generate a separate new CachedPlan at that time, but * the CachedPlanSource and the initial CachedPlan share the caller-provided * context and go away together when neither is needed any longer. (Because * the parser and planner generate extra cruft in addition to their real * output, this approach means that the context probably contains a bunch of * useless junk as well as the useful trees. Hence, this method is a * space-for-time tradeoff, which is worth making for plans expected to be * short-lived.) * * raw_parse_tree, query_string, param_types, and stmt_list must reside in the * given context, which must have adequate lifespan (recommendation: make it a * child of CacheMemoryContext). Otherwise the API is the same as * CreateCachedPlan. */CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree, char *query_string, const char *commandTag, Oid *param_types, int num_params, int cursor_options, List *stmt_list, bool fully_planned, bool fixed_result, MemoryContext context){ CachedPlanSource *plansource; OverrideSearchPath *search_path; MemoryContext oldcxt; /* * Fetch current search_path into given context, but do any recalculation * work required in caller's context. */ search_path = GetOverrideSearchPath(context); /* * Create and fill the CachedPlanSource struct within the given context. */ oldcxt = MemoryContextSwitchTo(context); plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource)); plansource->raw_parse_tree = raw_parse_tree; plansource->query_string = query_string; plansource->commandTag = commandTag; /* no copying needed */ plansource->param_types = param_types; plansource->num_params = num_params; plansource->cursor_options = cursor_options; plansource->fully_planned = fully_planned; plansource->fixed_result = fixed_result; plansource->search_path = search_path; plansource->generation = 0; /* StoreCachedPlan will increment */ plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); plansource->plan = NULL; plansource->context = context; plansource->orig_plan = NULL; /* * Store the current output plans into the plancache entry. */ StoreCachedPlan(plansource, stmt_list, context); /* * Since the context is owned by the CachedPlan, advance its refcount. */ plansource->orig_plan = plansource->plan; plansource->orig_plan->refcount++; /* * Now we can add the entry to the list of cached plans. The List nodes * live in CacheMemoryContext. */ MemoryContextSwitchTo(CacheMemoryContext); cached_plans_list = lappend(cached_plans_list, plansource); MemoryContextSwitchTo(oldcxt); return plansource;}/* * StoreCachedPlan: store a built or rebuilt plan into a plancache entry. * * Common subroutine for CreateCachedPlan and RevalidateCachedPlan. */static voidStoreCachedPlan(CachedPlanSource *plansource, List *stmt_list, MemoryContext plan_context){ CachedPlan *plan; MemoryContext oldcxt; if (plan_context == NULL) { /* * Make a dedicated memory context for the CachedPlan and its * subsidiary data. It's probably not going to be large, but just in * case, use the default maxsize parameter. */ plan_context = AllocSetContextCreate(CacheMemoryContext, "CachedPlan", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * Copy supplied data into the new context. */ oldcxt = MemoryContextSwitchTo(plan_context); stmt_list = (List *) copyObject(stmt_list); } else { /* Assume subsidiary data is in the given context */ oldcxt = MemoryContextSwitchTo(plan_context); } /* * Create and fill the CachedPlan struct within the new context. */ plan = (CachedPlan *) palloc(sizeof(CachedPlan)); plan->stmt_list = stmt_list; plan->fully_planned = plansource->fully_planned; plan->dead = false; if (plansource->fully_planned && plan_list_is_transient(stmt_list)) { Assert(TransactionIdIsNormal(TransactionXmin)); plan->saved_xmin = TransactionXmin; } else plan->saved_xmin = InvalidTransactionId; plan->refcount = 1; /* for the parent's link */ plan->generation = ++(plansource->generation); plan->context = plan_context; Assert(plansource->plan == NULL); plansource->plan = plan; MemoryContextSwitchTo(oldcxt);}/* * DropCachedPlan: destroy a cached plan. * * Actually this only destroys the CachedPlanSource: the referenced CachedPlan * is released, but not destroyed until its refcount goes to zero. That * handles the situation where DropCachedPlan is called while the plan is * still in use. */voidDropCachedPlan(CachedPlanSource *plansource){ /* Validity check that we were given a CachedPlanSource */ Assert(list_member_ptr(cached_plans_list, plansource)); /* Remove it from the list */ cached_plans_list = list_delete_ptr(cached_plans_list, plansource); /* Decrement child CachePlan's refcount and drop if no longer needed */ if (plansource->plan) ReleaseCachedPlan(plansource->plan, false); /* * If CachedPlanSource has independent storage, just drop it. Otherwise * decrement the refcount on the CachePlan that owns the storage. */ if (plansource->orig_plan == NULL) { /* Remove the CachedPlanSource and all subsidiary data */ MemoryContextDelete(plansource->context); } else { Assert(plansource->context == plansource->orig_plan->context); ReleaseCachedPlan(plansource->orig_plan, false); }}/* * RevalidateCachedPlan: prepare for re-use of a previously cached plan. * * What we do here is re-acquire locks and rebuild the plan if necessary. * On return, the plan is valid and we have sufficient locks to begin * execution (or planning, if not fully_planned). * * On return, the refcount of the plan has been incremented; a later * ReleaseCachedPlan() call is expected. The refcount has been reported * to the CurrentResourceOwner if useResOwner is true. * * Note: if any replanning activity is required, the caller's memory context * is used for that work. */CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner){ CachedPlan *plan; /* Validity check that we were given a CachedPlanSource */ Assert(list_member_ptr(cached_plans_list, plansource)); /* * If the plan currently appears valid, acquire locks on the referenced * objects; then check again. We need to do it this way to cover the race * condition that an invalidation message arrives before we get the lock. */ plan = plansource->plan; if (plan && !plan->dead) { /* * Plan must have positive refcount because it is referenced by * plansource; so no need to fear it disappears under us here. */ Assert(plan->refcount > 0); if (plan->fully_planned) AcquireExecutorLocks(plan->stmt_list, true); else AcquirePlannerLocks(plan->stmt_list, true); /* * If plan was transient, check to see if TransactionXmin has * advanced, and if so invalidate it. */ if (!plan->dead && TransactionIdIsValid(plan->saved_xmin) && !TransactionIdEquals(plan->saved_xmin, TransactionXmin)) plan->dead = true; /* * By now, if any invalidation has happened, PlanCacheCallback will * have marked the plan dead. */ if (plan->dead) { /* Ooops, the race case happened. Release useless locks. */ if (plan->fully_planned) AcquireExecutorLocks(plan->stmt_list, false); else AcquirePlannerLocks(plan->stmt_list, false); } } /* * If plan has been invalidated, unlink it from the parent and release it. */ if (plan && plan->dead) { plansource->plan = NULL; ReleaseCachedPlan(plan, false); plan = NULL; } /* * Build a new plan if needed. */ if (!plan) { List *slist; TupleDesc resultDesc; /* * Restore the search_path that was in use when the plan was made. * (XXX is there anything else we really need to restore?) */ PushOverrideSearchPath(plansource->search_path); /* * Run parse analysis and rule rewriting. The parser tends to * scribble on its input, so we must copy the raw parse tree to * prevent corruption of the cache. Note that we do not use * parse_analyze_varparams(), assuming that the caller never wants the * parameter types to change from the original values. */ slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree), plansource->query_string, plansource->param_types, plansource->num_params); if (plansource->fully_planned) { /* Generate plans for queries */ slist = do_planning(slist, plansource->cursor_options); } /* * Check or update the result tupdesc. XXX should we use a weaker * condition than equalTupleDescs() here? */ resultDesc = PlanCacheComputeResultDesc(slist); if (resultDesc == NULL && plansource->resultDesc == NULL) { /* OK, doesn't return tuples */ } else if (resultDesc == NULL || plansource->resultDesc == NULL || !equalTupleDescs(resultDesc, plansource->resultDesc)) { MemoryContext oldcxt; /* can we give a better error message? */ if (plansource->fixed_result) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cached plan must not change result type"))); oldcxt = MemoryContextSwitchTo(plansource->context); if (resultDesc) resultDesc = CreateTupleDescCopy(resultDesc);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?