Postgresql源码(94)SPI模块拆解分析二:SPI内存生命周期分析
相关 《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下面:
_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内存释放场景分析
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
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);
2 【事务块】【atomic=true(TopTransactionContext)】【异常结束释放】→【AtCleanup_Memory】
一句话总结:函数报错后,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)
3 【事务块子事务】【atomic=true(TopTransactionContext)】【异常结束释放】→【AtEOSubXact_SPI】
一句话总结:函数报错后,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下)
4 【单条】【atomic=false(PortalContext)】【正常结束释放】→【PortalDrop】
调用函数transaction_test_noerror
call transaction_test_noerror();
单条执行结束时释放
exec_simple_query
PortalDrop
MemoryContextDelete
5 【单条】【atomic=false(PortalContext)】【异常结束释放】→【PortalDrop】
一句话总结:函数报错后,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
相关文章
- 【踩坑】Android 编译线程爆了, gradle 内存 OOM 解决之路
- 个人谈谈对ThreadLocal内存泄露的理解
- 【Windows 逆向】使用 Cheat Engine 工具进行指针扫描挖掘关键数据内存真实地址 ( 指针扫描 )
- 模拟linux内存占用脚本
- 优化实现PostgreSQL缓存优化,更高性能!(postgresql缓存)
- 体验PostgreSQL之精彩:驱动新体验(postgresql驱动)
- 什么是内存交换
- 三星Galaxy F42 5G现身GeekBench:联发科Dimensity 700+6GB内存
- 招聘PostgreSQL攻城狮:让你的技能大放异彩(postgresql招聘)
- 优势PostgreSQL归档:优势及其应用(postgresql归档)
- 解决PostgreSQL乱码问题(postgresql乱码)
- Postgresql数据库备份实践:简单而又必要(postgresql数据库备份)
- 查看Postgresql版本:一步一步指南(查看postgresql版本)
- 实用脚本:检查高 CPU / 内存消耗进程
- Linux内存被吃掉了,它去哪里了?
- PostgreSQL中文手册详解数据库操作技巧(postgresql中文手册)
- 深入浅出PostgreSQL(postgresql书籍)
- PostgreSQL论坛:聚焦数据库技术分享与交流!(postgresql论坛)
- PostgreSQL实例实战:了解数据库管理系统(postgresql实例)
- 管理PostgreSQL:优化内存管理提升数据库性能(postgresql内存)
- redis拥有无限的内存储存空间(内存很大redis)
- 访问php时提示内存位置访问无效的解决办法和思路分析