Postgresql源码(94)SPI模块拆解分析二:SPI内存生命周期分析

2022-12-15 14:58:44 浏览数 (1)

相关 《Postgresql源码(82)SPI模块拆解分析一:执行简单SQL获取结果》 《Postgresql源码(94)SPI模块拆解分析二:SPI内存生命周期分析》

一、总结

SPI内存的5种释放位置:

SQL

事务块内/子事务

SPI parent context

正常释放

call异常释放

begin;INSERT INTO test1 (a) VALUES (100);call transaction_test_noerror();

事务块内无子事务

TopTransactionContext

plpgsql_call_handler→SPI_finish释放

begin;INSERT INTO test1 (a) VALUES (100);call transaction_test_error();

事务块内无子事务

TopTransactionContext

函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);因为内存是挂在TopTransactionContext下面的。等着事务提交后,一起释放(AtCleanup_Memory)。

begin;INSERT INTO test1 (a) VALUES (100);savepoint sp1;call transaction_test_error();

事务块内有子事务

TopTransactionContext

函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);子事务场景比较特别,报错 后会先释放PortalContext然后释放TopTransactionContext下的"SPI Proc"和"SPI Exec"。

call transaction_test_noerror();

单条SQL

PortalContext

exec_simple_query→PortalDrop执行结束正常释放

call transaction_test_error();

单条SQL

PortalContext

函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);内存是挂在TopPortalContext->PortalContext下面,AbortCurrentTransaction会继续清理Portal把PortalContext释放掉(PortalDrop)。

二、SPI内存申请

SPI在使用后,会在SPI_connect_ext申请两个上下文:

  • procCxt
  • execCxt

两个上下文会根据atomic挂在不同的context下面:

代码语言:javascript复制
_SPI_current->procCxt = AllocSetContextCreate(
													_SPI_current->atomic ? TopTransactionContext : PortalContext,
												  "SPI Proc",
												  ALLOCSET_DEFAULT_SIZES);
_SPI_current->execCxt = AllocSetContextCreate(
													_SPI_current->atomic ? TopTransactionContext : _SPI_current->procCxt,
												  "SPI Exec",
												  ALLOCSET_DEFAULT_SIZES);

所以如果:

  • 调用的是procedure、且procedure不在事务块内,atomic=false
    • procCxt和execCxt都挂在PortalContext下面,和Portal的生命周期保持一致
  • 调用的是function、或在事务块内部调用的function/procedure。atomic=true
    • procCxt和execCxt都挂在TopTransactionContext下面,和事务的声明周期保持一致

三、SPI内存释放场景分析

代码语言:javascript复制
drop table test1;
create table test1(a int);

CREATE or replace PROCEDURE transaction_test_noerror()
LANGUAGE plpgsql
AS $$
DECLARE
    i int;
BEGIN
    i := 1/1;
END;
$$;

CREATE or replace PROCEDURE transaction_test_error()
LANGUAGE plpgsql
AS $$
DECLARE
    i int;
BEGIN
    i := 1/0;
END;
$$;

1 【事务块】【atomic=true(TopTransactionContext)】【正常执行结束释放】→【SPI_finish】

调用函数transaction_test_noerror

代码语言:javascript复制
begin;
INSERT INTO test1 (a) VALUES (100);
call transaction_test_noerror();

执行完就释放

代码语言:javascript复制
ProcessUtility
  standard_ProcessUtility
    ExecuteCallStmt
      plpgsql_call_handler
        PG_TRY()
          plpgsql_exec_function
        PG_FINALLY()
        PG_END_TRY()

        SPI_finish
          MemoryContextDelete(_SPI_current->execCxt);
          MemoryContextDelete(_SPI_current->procCxt);

2 【事务块】【atomic=true(TopTransactionContext)】【异常结束释放】→【AtCleanup_Memory】

一句话总结:函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);因为内存是挂在TopTransactionContext下面的。等着事务提交后,一起释放(AtCleanup_Memory)。

调用函数transaction_test_error

代码语言:javascript复制
begin;
INSERT INTO test1 (a) VALUES (100);
call transaction_test_error();

异常捕获分支释放

代码语言:javascript复制
ProcessUtility
  standard_ProcessUtility
    ExecuteCallStmt
      plpgsql_call_handler
        PG_TRY()
          plpgsql_exec_function
        PG_FINALLY()       // 会直接rethrow!
        PG_END_TRY()
 
 jump到:
 exec_simple_query
   PortalRun
     PG_CATCH()
       PG_RE_THROW()
 
 jump到:
 PostgresMain
   AbortCurrentTransaction
     AbortTransaction
       AtEOXact_SPI
         // 注意这里不删除上下文,只把指针置空,现在内存挂在TopTransactionContext下面
         _SPI_current = NULL;

事务提交commit时

代码语言:javascript复制
 最后清理:TopTransactionContext
 
 exec_simple_query
   finish_xact_command
     CommitTransactionCommand
       CleanupTransaction
         AtCleanup_Memory
           MemoryContextDelete(TopTransactionContext)

3 【事务块子事务】【atomic=true(TopTransactionContext)】【异常结束释放】→【AtEOSubXact_SPI】

一句话总结:函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);子事务场景比较特别,报错 后会先释放PortalContext然后释放TopTransactionContext下的"SPI Proc"和"SPI Exec"。

事务块内调用transaction_test_error

代码语言:javascript复制
begin;
INSERT INTO test1 (a) VALUES (100);
savepoint sp1;
call transaction_test_error();

释放位置:

代码语言:javascript复制
PostgresMain
  AbortCurrentTransaction
    AbortSubTransaction
      AtSubAbort_Portals
        MemoryContextDeleteChildren(portal->portalContext)   // 释放 PortalContext
      
      AtEOSubXact_SPI
        MemoryContextDelete(connection->execCxt)  // 释放 "SPI Proc" (在TopTransactionContext下)
        MemoryContextDelete(connection->procCxt)  // 释放 "SPI Exec" (在TopTransactionContext下)

4 【单条】【atomic=false(PortalContext)】【正常结束释放】→【PortalDrop】

调用函数transaction_test_noerror

代码语言:javascript复制
call transaction_test_noerror();

单条执行结束时释放

代码语言:javascript复制
exec_simple_query
  PortalDrop
    MemoryContextDelete

5 【单条】【atomic=false(PortalContext)】【异常结束释放】→【PortalDrop】

一句话总结:函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);内存是挂在TopPortalContext->PortalContext下面,AbortCurrentTransaction会继续清理Portal把PortalContext释放掉(PortalDrop)。

调用函数transaction_test_error

代码语言:javascript复制
call transaction_test_error();

异常捕获分支释放

代码语言:javascript复制
ProcessUtility
  standard_ProcessUtility
    ExecuteCallStmt
      plpgsql_call_handler
        PG_TRY()
          plpgsql_exec_function
        PG_FINALLY()       // 会直接rethrow!
        PG_END_TRY()
 
 jump到:
 exec_simple_query
   PortalRun
     PG_CATCH()
       PG_RE_THROW()
 
 jump到:
 PostgresMain
   AbortCurrentTransaction
     AbortTransaction
       AtEOXact_SPI
         // 注意这里不删除上下文,只把指针置空,现在内存挂在PortalContext下面
         _SPI_current = NULL;
     
     // 继续清理
     CleanupTransaction
       AtCleanup_Portals
         PortalDrop
           MemoryContextDelete
             // 删除PortalContext   -------------
                                                 
                                                  |     
                                                  |
0x28860f0 TopPortalContext                        |
                 /                               /
0x2808a90 PortalContext   <<<-----删这个----------
               /
0x28fc4d0 SPI Proc

0 人点赞