前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(94)SPI模块拆解分析二:SPI内存生命周期分析

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

作者头像
mingjie
发布2022-12-15 14:58:44
3100
发布2022-12-15 14:58:44
举报

相关 《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
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-12-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、总结
  • 二、SPI内存申请
  • 三、SPI内存释放场景分析
    • 1 【事务块】【atomic=true(TopTransactionContext)】【正常执行结束释放】→【SPI_finish】
      • 2 【事务块】【atomic=true(TopTransactionContext)】【异常结束释放】→【AtCleanup_Memory】
        • 3 【事务块子事务】【atomic=true(TopTransactionContext)】【异常结束释放】→【AtEOSubXact_SPI】
          • 4 【单条】【atomic=false(PortalContext)】【正常结束释放】→【PortalDrop】
            • 5 【单条】【atomic=false(PortalContext)】【异常结束释放】→【PortalDrop】
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档