Postgresql源码(137)执行器参数传递与使用

参考
《Postgresql源码(127)投影ExecProject的表达式执行分析》

0 总结速查

prepare p_04(int,int) as select b from tbl_01 where a = $1 and b = $2为例。

  • custom计划中,在表达式计算中使用参数的值,因为custom计划会带参数值,所以表达式计算时不需要从参数列表中取值,直接EEO_CASE(EEOP_FUNCEXPR_STRICT)计算即可。
  • generic计划中,在表达式计算中使用参数的值,generic计划不带参数值,所以需要多走一步EEO_CASE(EEOP_PARAM_EXTERN)、EEO_CASE(EEOP_FUNCEXPR_STRICT)。具体调用ExecEvalParamExtern函数拿到参数值。
  • generic计划中,使用参数的节点会放一个Param。

1 执行器如何使用参数?

在下面例子中,execute语句执行时带了两个参数,执行器是在哪里带入参数执行的?下面做一些分析。

sql 复制代码
prepare p_04(int,int) as select b from tbl_01 where a = $1 and b = $2;
execute p_04(2,200);

带入参数的位置是PortalStart函数。

extern void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot);

PortalStart的第二个参数可以接受参数列表ParamListInfo,从代码上看,PortalStart有三处调用位置:

复制代码
exec_simple_query
  PortalStart(portal, NULL, 0, InvalidSnapshot)

exec_bind_message
  PortalStart(portal, params, 0, InvalidSnapshot)

ExecuteQuery
  PortalStart(portal, paramLI, eflags, GetActiveSnapshot())

SPI_cursor_open_internal
  PortalStart(portal, paramLI, 0, snapshot)
  1. exec_simple_query:执行简单SQL语句,不需要传参。
  2. exec_bind_message:PEB协议中的B阶段,绑定参数。
  3. ExecuteQuery:SQL语法提供的PBE协议,在execute阶段,绑定参数。
  4. SPI_cursor_open_internal:PL中打开游标时,绑定参数。

下面对3、4两种情况展开分析。

2 execute执行时执行器的传参

栗子:

sql 复制代码
create table tbl_01(a int, b int);
insert into tbl_01 values (1,100);
insert into tbl_01 values (2,200);
prepare p_04(int,int) as select b from tbl_01 where a = $1 and b = $2;
execute p_04(2,200);

execute p_04(2,200); 执行时,PortalStart会带入ParamListInfo。

2.1 问题一:ParamListInfo的来源?

c 复制代码
ExecuteQuery
  EvaluateParams
    // 准备计算参数表达式的值
    exprstates = ExecPrepareExprList(params, estate)
    
    makeParamList
      retval->parserSetup = paramlist_parser_setup
      // paramlist_parser_setup
      //   pstate->p_paramref_hook = paramlist_param_ref
      //
      // paramlist_param_ref
      //   param = makeNode(Param)
      //   param->paramkind = PARAM_EXTERN
      //   param->paramid = paramno
      //   ...
      //   钩子函数中生成Param,该实例不会走这个钩子,注意Param结构是不带值的,只用于中间过程。
    
    // 走表达式计算参数的值
    // 注意值都在paramLI->params[i]->value中
    i = 0;
	foreach(l, exprstates)
	{
		ExprState  *n = (ExprState *) lfirst(l);
		ParamExternData *prm = &paramLI->params[i];

		prm->ptype = param_types[i];
		prm->pflags = PARAM_FLAG_CONST;
		prm->value = ExecEvalExprSwitchContext(n,
											   GetPerTupleExprContext(estate),
											   &prm->isnull);

		i++;
	}

生成ParamListInfo(包含参数的全部信息,包括值)

c 复制代码
paramLI = 
  {paramFetch = 0x0,
   paramFetchArg = 0x0,
   paramCompile = 0x0,
   paramCompileArg = 0x0,
   parserSetup = 0x8357c1 <paramlist_parser_setup>,
   parserSetupArg = 0x3238da8,
   paramValuesStr = 0x0,
   numParams = 2,
   params = 0x3238de8}

2.2 问题二:ParamListInfo的使用位置?

custom plan

前五次执行会生成customplan,customplan在GetCachedPlan中生成,customplan计划是带着参数生成的(按参数定制的计划,更准确,但每次参数变了都要重新生成),所以计划中会有参数的具体值,执行时不会使用执行器带入的ParamListInfo。

构造customplan时,会带入参数生成计划:

ExecQual调用表达式框架计算where a = $1 and b = $2,ExecQual函数进入表达计算。

复制代码
ExecScan
    for (;;)
        slot = ExecScanFetch
        ...
        if (qual == NULL || ExecQual(qual, econtext))
            ...
            return slot

ExecQual表达式计算流程:

  1. EEO_CASE(EEOP_SCAN_FETCHSOME)
  2. EEO_CASE(EEOP_SCAN_VAR)
  3. EEO_CASE(EEOP_FUNCEXPR_STRICT)
    • 计算where中第一个条件
    • fcinfo : int4eq
    • arg1 : 2
    • arg2 : 2
  4. EEO_CASE(EEOP_QUAL)
  5. EEO_CASE(EEOP_FUNCEXPR_STRICT)
    • 计算where中第二个条件
    • fcinfo : int4eq
    • arg1 : 200
    • arg2 : 200
  6. EEO_CASE(EEOP_QUAL)

generic plan

第六次执行时,choose_custom_plan返回false,BuildCachedPlan时没有传入任何参数构造generic plan(通用执行计划,可以接受任何参数,不用每次都生成计划,但没有customplan精确,毕竟这里没有给参数)

执行execute p_04(2,200);给的两个参数明显是用于过滤条件的,所以在ExecScan→ExecQual中寻找使用的位置:

在ExecEvalParamExtern函数中,从econtext->ecxt_param_list_info中拿到参数值,返回给表达式框架:

c 复制代码
ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
	ParamListInfo paramInfo = econtext->ecxt_param_list_info;
	int			paramId = op->d.param.paramid;

	if (likely(paramInfo &&
			   paramId > 0 && paramId <= paramInfo->numParams))
	{
		ParamExternData *prm;
		ParamExternData prmdata;
		...
		...
		
		prm = &paramInfo->params[paramId - 1];
		...
		...
		*op->resvalue = prm->value;
		*op->resnull = prm->isnull;
		return;
		}
	}
	...
	...
}

最终ExecScan在loop中,拿到的slot经过ExecQual的计算,决定是否保留该元组。

ExecQual调用表达式框架计算where a = $1 and b = $2,通过ExecEvalParamExtern函数拿到$1$2的值,完成计算,放回true OR false。

复制代码
ExecScan
    for (;;)
        slot = ExecScanFetch
        ...
        if (qual == NULL || ExecQual(qual, econtext))
            ...
            return slot

表达式计算流程:

  1. EEO_CASE(EEOP_SCAN_FETCHSOME)
  2. EEO_CASE(EEOP_SCAN_VAR)
  3. EEO_CASE(EEOP_PARAM_EXTERN)
  4. EEO_CASE(EEOP_FUNCEXPR_STRICT)
    • int4eq
    • arg1 : 2
    • arg2 : 2
  5. EEO_CASE(EEOP_QUAL)
  6. EEO_CASE(EEOP_SCAN_VAR)
  7. EEO_CASE(EEOP_PARAM_EXTERN)
  8. EEO_CASE(EEOP_FUNCEXPR_STRICT)
    • int4eq
    • arg1 : 200
    • arg2 : 200

从计划上来看,参数的位置是两个Param。

3 游标打开时执行器的传参

复制代码
create table tbl_01(a int, b int);
insert into tbl_01 values (1,100);
insert into tbl_01 values (2,200);

do $$
declare
  res int;
  c_04 CURSOR (p1 int, p2 int) FOR select b from tbl_01 where a = p1 and b = p2;
begin
  open c_04(2,200);
  fetch c_04 into res;
  raise notice '%',res;
end; $$;

PL的参数在transform阶段已经转成const了,所以执行类似customplan:

fetch堆栈:

相关推荐
jiayou641 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤2 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
爱可生开源社区3 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1773 天前
《从零搭建NestJS项目》
数据库·typescript
加号34 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏4 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐4 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再4 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip
tryCbest4 天前
数据库SQL学习
数据库·sql
jnrjian4 天前
ORA-01017 查找机器名 用户名 以及library cache lock 参数含义
数据库·oracle