前言
在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
- 例如在三层函数调用下,会产生三层PLpgSQL_execstate结构,代表PL的运行时。
- 无论几层函数,每层的PLpgSQL_execstate都会公用一个EState,EState会申请上下文es_query_cxt挂在事务上下文下,随顶层事务释放。所以在PL中执行COMMI后,所有子事务、顶层事务都会重建,EState上下文也会跟随释放,也需要重建。
- 每层函数的PLpgSQL_execstate都会自带ExprContext,这些ExprContext统一申请在ecxt_per_tuple_memory中,而ecxt_per_tuple_memory统一挂在同一个共享的es_query_cxt下。
- PL中事务提交分两种情况
- 子事务提交:即exception子事务提交,这时顶层EState无需释放,只需要释放各层的ExprContext,这里通过simple_econtext_stack全局链表,按当前提交的子事务ID匹配,将对应的ExprContext释放掉。
- 主事务提交:即顶层事务提交,这里会一次性的直接释放EState的内存es_query_cxt,跟随的EState和所有ExprContext都需要重建。
调用流程看PL中的ExprContext
- exec_stmt_block中执行前,会把ExprContext用栈变量记录下来,执行完了再恢复出来。
- 带exception的执行前,都会先起子事务在新申请一个和新子事务绑定的ExprContext,用完即毁。
- 注意这里有两类ExprContext
- 第一类是函数进入时就申请的,跟着plpgsql_estate_setup生成,这类ExprContext在整个调用流程结束时,会被plpgsql_exec_function主动释放,所以这类ExprContext不能再内部提前清理,否则外面清理时就会有问题。
- 第二类是有exception时在起完子事务,执行具体的stmt前申请的,和新子事务绑定,这类PL是不会主动释放的,都是跟随当前子事务销毁的。无论子事务提交还是回滚,这个ExprContext都会被释放。
PL内执行commit时
多层函数时PL内执行commit