前言

在PL/pgSQL语言中,执行任何SQL都需要通过SPI调用SQL层解析执行,例如在SQL层执行表达式的入口:

static bool
exec_eval_simple_expr(PLpgSQL_execstate *estate,
					  PLpgSQL_expr *expr,
					  Datum *result,
					  bool *isNull,
					  Oid *rettype,
					  int32 *rettypmod)
{
	ExprContext *econtext = estate->eval_econtext;
	
	...
	...
	*result = ExecEvalExpr(expr->expr_simple_state,
						   econtext,
						   isNull);
	...
	...

表达式的运行时、内存都是在ExprContext中存放的,所以PL在调用任何函数前都会提前申请好ExprContext内存,执行表达式时可以直接使用。

使用后都会调用exec_eval_cleanup把eval_econtext的内存reset掉,避免影响后面执行的表达式。

在SQL层,ExprContext内存往往是挂在EState(SQL层的运行时)下的,PL也仿照SQL层做了一些事情,下面展开讲讲。

数据角度看PL中的ExprContext

  1. 例如在三层函数调用下,会产生三层PLpgSQL_execstate结构,代表PL的运行时。
  2. 无论几层函数,每层的PLpgSQL_execstate都会公用一个EState,EState会申请上下文es_query_cxt挂在事务上下文下,随顶层事务释放。所以在PL中执行COMMI后,所有子事务、顶层事务都会重建,EState上下文也会跟随释放,也需要重建。
  3. 每层函数的PLpgSQL_execstate都会自带ExprContext,这些ExprContext统一申请在ecxt_per_tuple_memory中,而ecxt_per_tuple_memory统一挂在同一个共享的es_query_cxt下。
  4. PL中事务提交分两种情况
    • 子事务提交:即exception子事务提交,这时顶层EState无需释放,只需要释放各层的ExprContext,这里通过simple_econtext_stack全局链表,按当前提交的子事务ID匹配,将对应的ExprContext释放掉。
    • 主事务提交:即顶层事务提交,这里会一次性的直接释放EState的内存es_query_cxt,跟随的EState和所有ExprContext都需要重建。
      Postgresql源码(119)PL/pgSQL中ExprContext的生命周期-LMLPHP

调用流程看PL中的ExprContext

  1. exec_stmt_block中执行前,会把ExprContext用栈变量记录下来,执行完了再恢复出来。
  2. 带exception的执行前,都会先起子事务在新申请一个和新子事务绑定的ExprContext,用完即毁。
  3. 注意这里有两类ExprContext
    • 第一类是函数进入时就申请的,跟着plpgsql_estate_setup生成,这类ExprContext在整个调用流程结束时,会被plpgsql_exec_function主动释放,所以这类ExprContext不能再内部提前清理,否则外面清理时就会有问题。
    • 第二类是有exception时在起完子事务,执行具体的stmt前申请的,和新子事务绑定,这类PL是不会主动释放的,都是跟随当前子事务销毁的。无论子事务提交还是回滚,这个ExprContext都会被释放。

Postgresql源码(119)PL/pgSQL中ExprContext的生命周期-LMLPHP

PL内执行commit时
Postgresql源码(119)PL/pgSQL中ExprContext的生命周期-LMLPHP
多层函数时PL内执行commit
Postgresql源码(119)PL/pgSQL中ExprContext的生命周期-LMLPHP

12-30 07:56