相关 《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