相关 《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_connect_ext申请两个上下文:
两个上下文会根据atomic挂在不同的context下面:
_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);
所以如果:
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;
$$;
调用函数transaction_test_noerror
begin;
INSERT INTO test1 (a) VALUES (100);
call transaction_test_noerror();
执行完就释放
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);
一句话总结:函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);因为内存是挂在TopTransactionContext下面的。等着事务提交后,一起释放(AtCleanup_Memory)。
调用函数transaction_test_error
begin;
INSERT INTO test1 (a) VALUES (100);
call transaction_test_error();
异常捕获分支释放
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时
最后清理:TopTransactionContext
exec_simple_query
finish_xact_command
CommitTransactionCommand
CleanupTransaction
AtCleanup_Memory
MemoryContextDelete(TopTransactionContext)
一句话总结:函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);子事务场景比较特别,报错 后会先释放PortalContext然后释放TopTransactionContext下的"SPI Proc"和"SPI Exec"。
事务块内调用transaction_test_error
begin;
INSERT INTO test1 (a) VALUES (100);
savepoint sp1;
call transaction_test_error();
释放位置:
PostgresMain
AbortCurrentTransaction
AbortSubTransaction
AtSubAbort_Portals
MemoryContextDeleteChildren(portal->portalContext) // 释放 PortalContext
AtEOSubXact_SPI
MemoryContextDelete(connection->execCxt) // 释放 "SPI Proc" (在TopTransactionContext下)
MemoryContextDelete(connection->procCxt) // 释放 "SPI Exec" (在TopTransactionContext下)
调用函数transaction_test_noerror
call transaction_test_noerror();
单条执行结束时释放
exec_simple_query
PortalDrop
MemoryContextDelete
一句话总结:函数报错后,SPI的内存不会释放,只会把_SPI_current置空(AtEOXact_SPI);内存是挂在TopPortalContext->PortalContext下面,AbortCurrentTransaction会继续清理Portal把PortalContext释放掉(PortalDrop)。
调用函数transaction_test_error
call transaction_test_error();
异常捕获分支释放
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