c
/*
* CachedPlanSource(更恰当的名称应为 CachedQuery) 表示预期会多次使用的 SQL 查询。它存储:
* - 查询源文本
* - 原始解析树
* - 经过分析和重写后的查询树
* - 以及相关附加数据
*
* 当查询依赖的对象发生 DDL 变更时,缓存可能失效。 此时会丢弃已分析和重写的查询树,并在下次需要时重建。
* CachedPlan 表示从 CachedPlanSource 派生的实际执行计划。
* 计划分为两种类型:
* - 通用计划(generic):适用于任意参数集
* - 自定义计划(custom):针对特定参数集优化
* plancache.c 包含决策逻辑,决定具体执行时采用哪种计划。 若使用通用缓存计划,它可跨多次执行重复使用, 因此调用方必须始终将 CachedPlan
* 视为只读。
*
* 成功构建并"保存"后,CachedPlanSource 通常与后端进程生命周期一致,但也可显式删除。
* CachedPlan 采用引用计数机制,当最后一个引用被释放时自动销毁。 注意:CachedPlan 可能比其来源的 CachedPlanSource 存活更久。
*
* "未保存"的 CachedPlanSource 可用于生成计划,但:
* - 驻留在临时存储中
* - 不会响应系统失效(sinval)事件更新
*
* 内存管理规则:
* - saved CachedPlanSource 创建的 CachedPlan存在于类似永久存储中,其引用必须由永久数据结构或 ResourceOwner 持有(避免内存泄漏)
* - unsaved CachedPlanSource 创建的 CachedPlan 位于调用方内存上下文的子空间中,其引用生命周期不应超过该上下文
* (引用计数在此场景多为形式要求,但若需提前丢弃计划仍有用)
*
* CachedPlanSource 有两个关联的内存上下文:一个保存存储结构体本身、查询源文本和原始解析树,另一个保存重写后的查询树及相关数据
* 这种设计使得查询树失效时可轻松丢弃。
*
* 针对一次性查询的特殊处理: 支持"oneshot"模式,该模式:
* - 不进行数据复制或失效检查
* - 无独立内存上下文,所有数据存于调用方 CurrentMemoryContext
* - 只能通过清除整个上下文释放内存
* oneshot 计划始终视为未保存。
*
* 注意:
* commandTag 引用的字符串不属于附属存储; 它被假定为编译时常量字符串。
* 与 portals 类似,commandTag 为 NULL 当且仅当原始查询字符串(重写前)为空字符串。
*/
typedef struct CachedPlanSource
{
int magic; /* should equal CACHEDPLANSOURCE_MAGIC */
struct RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */
struct Query *analyzed_parse_tree; /* analyzed parse tree, or NULL */
const char *query_string; //query 原文本
CommandTag commandTag; /* command tag for query */
Oid *param_types; /* array of parameter type OIDs, or NULL */
int num_params; /* length of param_types array */
ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg;
PostRewriteHook postRewrite; /* see SetPostRewriteHook */
void *postRewriteArg;
int cursor_options; /* cursor options used for planning */
bool fixed_result; /* disallow change in result tupdesc? */
TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */
MemoryContext context; //上下文1,保存存储结构体本身、查询源文本和原始解析树,保存以上字段内存
//以下字段为查询重写相关
List *query_list; /* list of Query nodes, or NIL if not valid */
List *relationOids; /* OIDs of relations the queries depend on */
List *invalItems; /* other dependencies, as PlanInvalItems */
struct SearchPathMatcher *search_path; /* search_path used for parsing
* and planning */
MemoryContext query_context; //上下文2 保存重写后的查询树及相关数据,以上字段
Oid rewriteRoleId; /* Role ID we did rewriting for */
bool rewriteRowSecurity; /* row_security used during rewrite */
bool dependsOnRLS; /* is rewritten query specific to the above? */
/* If we have a generic plan, this is a reference-counted link to it: */
struct CachedPlan *gplan; /* generic plan, or NULL if not valid */
/* Some state flags: */
bool is_oneshot; /* is it a "oneshot" plan? */
bool is_complete; /* has CompleteCachedPlan been done? */
bool is_saved; /* has CachedPlanSource been "saved"? */
bool is_valid; /* is the query_list currently valid? */
int generation; /* increments each time we create a plan */
/* If CachedPlanSource has been saved, it is a member of a global list */
dlist_node node; /* list link, if is_saved */
/* State kept to help decide whether to use custom or generic plans: */
double generic_cost; /* cost of generic plan, or -1 if not known */
double total_custom_cost; /* total cost of custom plans so far */
int64 num_custom_plans; /* # of custom plans included in total */
int64 num_generic_plans; /* # of generic plans */
} CachedPlanSource;
c
//CachedPlan 表示源自 CachedPlanSource 的执行计划。引用计数包含来自父级 CachedPlanSource 的链接(如有)以及任何活跃的计划执行,因此
//当引用计数归零时,该计划可被安全丢弃。结构体本身及其附属数据均存储在由 context 字段指定的上下文中。这种设计使得释放不再需要的缓存计划变
//得简单。(但若 is_oneshot 为 true,则上下文不完全属于 CachedPlan,因此无法执行释放操作。)
typedef struct CachedPlan
{
int magic; /* should equal CACHEDPLAN_MAGIC */
List *stmt_list; /* list of PlannedStmts */
bool is_oneshot; /* is it a "oneshot" plan? */
bool is_saved; /* is CachedPlan in a long-lived context? */
bool is_valid; /* is the stmt_list currently valid? */
Oid planRoleId; /* Role ID the plan was created for */
bool dependsOnRole; /* is plan specific to that role? */
TransactionId saved_xmin; /* if valid, replan when TransactionXmin changes from this value */
int generation; /* parent's generation number for this plan */
int refcount; /* count of live references to this struct */
MemoryContext context; /* context containing this CachedPlan */
} CachedPlan;
plansource 实际存储在双向链表中,正常只有pbe协议发起的sql,以及存储过程中会使用到plansource ,或者手动使用prepare语句会创建plansource ,psql连接的sql使用不到plansource
c
/*
* 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 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 dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
注册失效消息的回调函数
c
PostgresMain
RelationCacheInitialize
InitCatalogCache
InitPlanCache
InitPlanCache(void)
{
//plan 依赖的表结构,procoid,typeoid改变时,失效plancache
CacheRegisterRelcacheCallback(PlanCacheRelCallback, (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);
CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
}