企业小型网站要多少钱,线上网站设计,微信运营有前途吗,都江堰网站建设【GaussDB】诊断一起内存临时不可用的问题 #x1f4cb; 背景
在客户测试环境中#xff08;GaussDB 506.0 SPC0100 集中式#xff09;#xff0c;一个重度使用存储过程的系统#xff0c;频繁出现内存临时不可用的问题(ERROR: memory is temporarily unavailable)。令人困…【GaussDB】诊断一起内存临时不可用的问题 背景
在客户测试环境中GaussDB 506.0 SPC0100 集中式一个重度使用存储过程的系统频繁出现内存临时不可用的问题(ERROR: memory is temporarily unavailable)。令人困惑的是这个环境配置的内存大小已经数十倍于迁移前的Oracle。 GaussDB提供的观测方法
GaussDB官方产品文档中有一个《内存过载》章节详细描述了在出现内存过载情况下如何定位但似乎这个章节并没有在官方网站上公开。不过GaussDB松鼠会倒是把这个章节的内容发成一篇文章了《GaussDB内存过载分析》。
下面按照本次分析过程进行描述。 内存监控视图
GaussDB提供了几个重要的内存视图
gs_total_memory_detail
显示当前数据库节点的内存使用情况单位为MB。当GUC参数enable_memory_limit的值为off时本视图不可用。
gs_session_memory_detail
显示会话的内存使用情况以MemoryContext节点来统计。当开启线程池enable_thread_pool on时该视图包含所有线程和会话的内存使用情况。当GUC参数enable_memory_limit的值为off时本视图不可用。
gs_shared_memory_detail
查询当前节点所有已产生的共享内存上下文的使用信息。 内存使用情况示例
SELECT * FROM gs_total_memory_detail;nodenamememorytypememorymbytesprimarymax_process_memory24576primaryprocess_used_memory7847primarymax_dynamic_memory16651primarydynamic_used_memory1843primarydynamic_peak_memory2942primarydynamic_used_shrctx1386primarydynamic_peak_shrctx1387primarymax_backend_memory1360primarybackend_used_memory1primaryhigh_backend_thread_memory20primaryhigh_backend_thread_max_memory768primarymedium_backend_thread_memory113primarymedium_backend_thread_max_memory1536primarylow_backend_thread_memory0primarylow_backend_thread_max_memory768primarymax_shared_memory6548primaryshared_used_memory5402primarymax_cstore_memory16primarycstore_used_memory0primarymax_sctpcomm_memory0primarysctpcomm_used_memory0primarysctpcomm_peak_memory0primaryother_used_memory527primarygpu_max_dynamic_memory0primarygpu_dynamic_used_memory0primarygpu_dynamic_peak_memory0primarypooler_conn_memory0primarypooler_freeconn_memory0primarystorage_compress_memory0primaryudf_reserved_memory0primaryllvm_used_memory0primarymax_htap_memory0primaryhtap_used_memory0内存类型理解指南
要真实理解gs_total_memory_detail里这些内存类型的关系并且理解gs_session_memory_detail和gs_shared_memory_detail记录的内存对应到gs_total_memory_detail的映射关系需要明确以下几点 内存指标含义
max 表示最大可以为多少超过就会报内存不足used 表示当前已经申请了多少peak 表示本次数据库启动以来的历史used峰值 内存分配关系
max_process_memory 是通过参数设置的max_shared_memory 主要为shared_buffers但是这个值的大小可能会比shared_buffers要大max_dynamic_memory 是 max_process_memory - max_shared_memory - max_cstore_memory - max_backend_memory - ... 得到的
dynamic_used_memory 包括了
动态会话内存gs_total_memory_detail里没列出来dynamic_used_shrctxhigh_backend_thread_memorymedium_backend_thread_memorylow_backend_thread_memory 注意后三个high/medium/low暂时不用关心这是新版本中引入的特性为后台线程专门分出来的内存。 ️ GaussDB内存区域划分
在GaussDB中主要的内存区域分为 共享内存主要为shared_buffers动态内存dynamic_memory├── 动态会话内存 (gs_session_memory_detail)├── 动态共享内存 (dynamic_used_shrctx) (gs_shared_memory_detail)└── 动态后台内存⚠️ 重要提醒这里出现两个共享内存非常容易产生理解上的歧义。比如gs_shared_memory_detail这个视图查的就不是共享内存而是动态共享内存。 可以这么理解
shared_buffers主要是data buffer即表和索引的数据GaussDB实现的global plan cache/global plsql cache/unique sql hash table等全局缓存并不属于shared_buffers而是在dynamic_used_shrctx里 内存追踪配置
内存不足的报错经常是一个瞬间冲高回头再来看这些视图时已经找不到对应的内存上下文因此GaussDB在出现内存不足的报错时会自动把当时的内存上下文情况生成到单独的日志文件中。
memory_trace_level 参数
参数说明动态内存使用超过最大动态内存的90%后记录内存申请信息的管控等级。该参数仅在GUC参数use_workload_manager和enable_memory_limit打开时生效。
参数类型枚举类型 参数单位无
取值范围
级别说明none表示不记录内存申请信息level1动态内存使用超过最大动态内存的90%后会记录以下信息并将记录的内存信息保存在$GAUSSLOG/mem_log目录下• 全局内存概况• instancesessionthread三种类型的所有内存上下文中内存占用前20的内存上下文的内存使用情况• 每个内存上下文的totalsize、freesize字段level2在level1的基础上还会记录每个内存上下文上所有内存申请的详细信息包含申请内存所在的文件行号和大小
默认值level1 ⚠️ 设置建议该参数设置为level2后会记录每个内存上下文的内存申请详情filelinesize字段会对性能影响较大需慎重设置。 相关功能
记录的内存快照信息可以通过系统函数gs_get_history_memory_detail(cstring)查询记录的内存上下文是经过将同一类型所有重名的内存上下文进行汇总之后得到的 现场分析
️ 初步发现
由于默认是level1没有记录申请内存的源码文件和行号因此后面现场调成了level2。
当时的mem_log中有记录两个比较可疑的内存上下文
位置上下文名称数量大小shared_memoryunique sql hash table1个~8Gsession_memorySRF multi-call context1个~8G
然后回头查gs_shared_memory_detail、gs_session_memory_detail这两个视图发现的确可以查到unique sql hash table占了8G但是查不到SRF multi-call context。 监控脚本
客户DBA写了个监控脚本每隔20秒采集一下各个内存视图的数据。
但几天后仍然没有采集到SRF multi-call context而数据库日志中仍然会时不时报错内存临时不可用。 关键发现
在一次偶然的连续查询gs_total_memory_detail时发现已使用的动态内存上下波动幅度高达8GB。
于是猜想可能是一个非常短的时间产生了SRF multi-call context这个上下文执行完后就释放了所以间隔20秒采一次能采到的概率非常低。 深入分析
数据库大厂的内核研发往往都只负责各自一小块领域华为内核研发远程查看日志并没有发现什么可疑的点而且还拉了好几个不同领域的内核研发都没有找到原因。
但是在远程会议中客户快速一页一页翻日志时我发现一个现象 在出现内存临时不可用报错的前面一段会频繁打印相同格式的日志而且中间有个数字一直在涨从1XXXXXXXXX一直涨到7XXXXXXXXX然后就没有这个重复日志了。 直觉告诉我很可能与这个有关。于是让客户回到这段日志前面然后发现了类似下面的这么一行
2025-07-25 10:00:01.523 primary RdsAdmin postgres xxx.xxx.xxx.xxx 140216907527936 12[0:0#0] 0 gsql 00000 31806672368306287 [BACKEND] LOG: 00000: unique_sql_id3740540431, Current SQL is consuming about 1099 MB, allocating 102921000 bytes, memctx:SRF multi-call context, appname:, user_id: 16616, current query is: select usename,count(*) from dbe_perf.statement group by usename问题串联
很明显这里就出现了SRF multi-call context然后这段日志下面就是不断地在申请内存直到申请了8G后面很快就有一个业务调用存储过程报错内存不足了。
也就是说在这个环境中查询一次dbe_perf.statement这个视图就会申请8G内存。这个查询是TPOPS端定时采集的SQL。
dbe_perf.statement这个视图里实际上是一个查询特定内存数据的返回多行的函数查询的就是unique sql hash table里的内容。 问题链条分析
分析到这里感觉大部分疑点都串起来了
⚡ 由于某种原因归一化SQL的内存达到了8G 然后由于TPOPS定时查询dbe_perf.statement导致内存瞬间又消耗了8GB 光这两个动态内存上下文就占了16GB 由于这是个重度存储过程的系统GLOBAL PLSQL CACHE又占了几个GB❌ 动态内存全用完了导致业务会话报错
❓ 进一步探究
归一化内存为什么会达到8GB
我在很早就问过华为内核研发track_activity_query_size40960这么设置如果SQL没达到这个长度占用的内存是SQL实际的长度还是40960
我一直记得在openGauss系数据库中这个内存一定是按设置的这个大小申请不会根据SQL实际长度变化但是华为内核研发说在GaussDB中内存占用只会按实际SQL长度来除非SQL长度达到或者超过这个数才会申请这么多内存。
由于GaussDB并没有开源所以当时我只能相信华为说的可能GaussDB做了优化。 巧合的乘积
可是客户现场出现这个现象时我直接计算了下
40,960 × 200,000 8,192,000,000刚好差不多8G这么巧合
然后客户也查了实际在dbe_perf.statement里query的汇总字节数也就只有一百多MB。
但是华为内核研发说他看了那段代码说的确是按实际长度来的但是他又无法解释这8GB到哪里去了因为算上其他属性的长度每行最多就再加几百KB。 问问万能的AI
GaussDB没开源但是openGauss是开源的本来想用Cursor来分析一下奈何被断供了只能先用vscode的Copilot了。
打开openGauss源码目录切换Copilot到agent模式开问
第一轮提问 Dark-Athena: 看下SRF multi-call context 这个上下文在什么时候会产生 GitHub Copilot 我来帮你分析 “SRF multi-call context” 这个上下文在什么时候会产生。让我先搜索相关代码来了解这个上下文的创建和使用场景。 现在让我搜索更多关于 SRFSet-Returning Functions和多次调用上下文的信息 让我查看一些使用这些函数的实际例子 现在让我查看一个具体的SRF函数实现例子来理解上下文的产生时机 现在让我查看头文件中的宏定义和更多的注释说明 现在让我查看一下具体的内存上下文创建代码 让我再查看一些具体的SRF函数使用例子 让我查找更多的SRF函数实现例子 让我查找一些实际的SRF函数实现 现在让我查看一个具体的pg_stat函数来理解SRF上下文的产生 根据对代码的分析我现在可以详细回答你的问题 SRF multi-call context 产生的时机和原因 “SRF multi-call context” 是 Set-Returning Functions集合返回函数的多次调用上下文它在以下情况下产生 1. 什么是SRF SRFSet-Returning Functions是能够返回多行数据的函数比如 pg_stat_get_activity() - 返回所有活动会话信息unnest() - 展开数组为多行generate_series() - 生成数字序列各种系统统计函数 2. 上下文产生的具体时机 SRF multi-call context 在以下时机被创建 首次调用时当SRF函数第一次被调用时通过 SRF_FIRSTCALL_INIT() 宏实际调用 init_MultiFuncCall()创建 if (SRF_IS_FIRSTCALL()) {funcctx SRF_FIRSTCALL_INIT(); // 这里创建 SRF multi-call context// 初始化操作...
}内存上下文创建位置在 init_MultiFuncCall() 函数中 multi_call_ctx AllocSetContextCreate(fcinfo-flinfo-fn_mcxt,SRF multi-call context, // 这就是你看到的上下文名称ALLOCSET_SMALL_MINSIZE,ALLOCSET_SMALL_INITSIZE,ALLOCSET_SMALL_MAXSIZE);3. 为什么需要这个上下文 跨调用数据保存SRF函数需要在多次调用之间保持状态信息内存管理为跨调用的数据分配长期有效的内存性能优化避免每次调用都重新计算所有结果 4. 使用场景举例 以 pg_stat_get_activity() 为例 第1次调用创建上下文获取所有会话信息返回第1行第2次调用使用已有上下文返回第2行第N次调用返回第N行最后一次清理上下文结束返回 5. 生命周期 // 第一次调用 - 创建上下文
funcctx SRF_FIRSTCALL_INIT();// 后续调用 - 使用现有上下文
funcctx SRF_PERCALL_SETUP();// 最后 - 清理上下文
SRF_RETURN_DONE(funcctx); // 调用 end_MultiFuncCall()6. 典型的SRF函数模式 Datum my_srf_function(PG_FUNCTION_ARGS) {FuncCallContext *funcctx;if (SRF_IS_FIRSTCALL()) {// 创建 SRF multi-call contextfuncctx SRF_FIRSTCALL_INIT();// 在这个上下文中分配跨调用的数据MemoryContext oldcontext MemoryContextSwitchTo(funcctx-multi_call_memory_ctx);// ... 初始化操作MemoryContextSwitchTo(oldcontext);}funcctx SRF_PERCALL_SETUP();if (funcctx-call_cntr funcctx-max_calls) {// 返回下一行数据SRF_RETURN_NEXT(funcctx, result);} else {// 清理上下文并结束SRF_RETURN_DONE(funcctx);}
}总结SRF multi-call context 在任何集合返回函数第一次被调用时产生用于在函数的多次调用之间保持状态和数据直到函数返回完所有结果后被清理。 第二轮提问 Dark-Athena: 分析下执行get_instr_unique_sql这个函数时申请的SRF multi-call context占用大小与什么有关 GitHub Copilot: 我来分析 get_instr_unique_sql 函数执行时 “SRF multi-call context” 内存占用的情况。首先让我搜索这个函数的实现。 让我搜索这个函数的具体实现 现在让我找到这个函数的具体实现 让我搜索这个函数的具体实现文件 现在让我查看这个函数的具体实现 现在让我查看 GetUniqueSQLStat 函数这是关键的数据获取函数 现在让我查看内存分配的关键部分和数据结构定义 现在让我查找相关的宏定义和常量 现在让我查看 UniqueSQL 数据结构定义 让我查找 UniqueSQL 结构体的定义 基于对代码的深入分析我现在可以详细回答你关于 get_instr_unique_sql 函数执行时 “SRF multi-call context” 内存占用与什么相关的问题 SRF multi-call context 在 get_instr_unique_sql 中的内存占用分析 1. 内存占用的核心影响因素 SRF multi-call context 的内存占用主要与以下几个因素密切相关 1.1 Unique SQL 条目数量 *num hash_get_num_entries(g_instance.stat_cxt.UniqueSQLHashtbl);哈希表中存储的 unique SQL 条目总数是最主要的影响因素条目数量越多需要分配的内存越大 1.2 每个条目的大小 int malloc_size (int)MAX_MEM_UNIQUE_SQL_ENTRY_COUNT * (sizeof(UniqueSQL) unique_sql_str_len);单个条目大小包括 UniqueSQL 结构体约 200 字节包含统计信息、时间信息、网络信息等SQL 字符串长度UNIQUE_SQL_MAX_LEN g_instance.attr.attr_common.pgstat_track_activity_query_size 1 1.3 节点类型和配置 int unique_sql_str_len (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) ? UNIQUE_SQL_MAX_LEN : 0;CN 节点或单机需要存储完整的 SQL 字符串内存占用更大DN 节点不存储 SQL 字符串内存占用相对较小 2. 内存分配策略 2.1 批次分配机制 #define MAX_MEM_UNIQUE_SQL_ENTRY_COUNT 1000.0for (int j 0; j ceil(*num / MAX_MEM_UNIQUE_SQL_ENTRY_COUNT); j) {int malloc_size (int)MAX_MEM_UNIQUE_SQL_ENTRY_COUNT * (sizeof(UniqueSQL) unique_sql_str_len);unique_sql_array (UniqueSQL*)palloc0_noexcept(malloc_size);
}分批处理每批最多处理 1000 个条目避免大块内存分配防止因条目过多导致的内存分配失败 2.2 内存布局 /* memory format: [entry_1] [entry_2] [entry_3] ... | [sql_1] [sql_2] [sql_3] ... */结构体数组 SQL 字符串数组的连续内存布局提高内存访问效率 3. 具体内存计算公式 3.1 CN 节点或单机环境 总内存 批次数 × 每批内存大小
每批内存大小 1000 × (sizeof(UniqueSQL) pgstat_track_activity_query_size 1)3.2 DN 节点环境 总内存 批次数 × 每批内存大小
每批内存大小 1000 × sizeof(UniqueSQL)4. 主要配置参数影响 4.1 instr_unique_sql_count 直接影响控制系统中最多能存储多少个 unique SQL 条目内存影响条目数越多SRF 上下文需要的内存越大 4.2 pgstat_track_activity_query_size 直接影响控制每个 SQL 字符串的最大长度在 CN 节点上影响巨大该值越大每个条目占用的内存越多 5. 内存占用估算示例 假设配置如下 instr_unique_sql_count 5000 (5000个条目)pgstat_track_activity_query_size 4096 (4KB SQL字符串)sizeof(UniqueSQL) ≈ 200 字节 CN 节点内存占用 批次数 ceil(5000 / 1000) 5 批
每批内存 1000 × (200 4096 1) 4,297,000 字节 ≈ 4.3 MB
总内存 ≈ 5 × 4.3 MB 21.5 MBDN 节点内存占用 批次数 5 批
每批内存 1000 × 200 200,000 字节 ≈ 200 KB
总内存 ≈ 5 × 200 KB 1 MB6. 内存优化和保护机制 6.1 错误处理 if (unique_sql_array NULL) {ereport(ERROR, (errmsg([UniqueSQL] palloc0 error when querying unique sql stat!)));
}6.2 内存清理 if (funcctx-user_fctx) {list_free_deep(((UniqueSQLResults *)(funcctx-user_fctx))-batch_list);pfree_ext(funcctx-user_fctx);
}总结 SRF multi-call context 在 get_instr_unique_sql 函数中的内存占用主要取决于 系统中 unique SQL 条目的总数量最关键因素节点类型CN/单机 vs DNSQL 字符串的最大长度配置UniqueSQL 结构体的大小 在生产环境中建议根据实际的 SQL 多样性和系统内存容量合理配置 instr_unique_sql_count 和 pgstat_track_activity_query_size 参数以平衡监控精度和内存占用。 第三轮关键提问 Dark-Athena: 如果我一条SQL的长度没达到pgstat_track_activity_query_size也会申请pgstat_track_activity_query_size这个值大小的内存么 GitHub Copilot: Summarized conversation history我来帮你分析一下在openGauss中当SQL长度没达到pgstat_track_activity_query_size时的内存申请情况。 让我再搜索更具体的信息关于SQL字符串的内存分配机制。 让我重新搜索更直接相关的代码 现在让我搜索关于st_activity缓冲区分配的代码 基于对代码的分析我可以回答你的问题了 回答是的即使SQL的实际长度没有达到pgstat_track_activity_query_size系统也会为每个后端进程预先分配pgstat_track_activity_query_size大小的内存空间。 详细分析 1. 内存预分配机制 在pgstat.cpp的BackendStatusShmemInit函数中可以看到 /* Create or attach to the shared activity buffer */
t_thrd.shemem_ptr_cxt.BackendActivityBufferSize mul_size(g_instance.attr.attr_common.pgstat_track_activity_query_size, BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendActivityBuffer (char*)ShmemInitStruct(Backend Activity Buffer, t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, found);if (!found) {/* Initialize st_activity pointers. */buffer t_thrd.shemem_ptr_cxt.BackendActivityBuffer;for (i 0; i BackendStatusArray_size; i) {t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_activity buffer;buffer g_instance.attr.attr_common.pgstat_track_activity_query_size; // 每个后端进程分配固定大小}
}2. SQL写入机制 在pgstat_report_activity函数中当记录SQL活动时 if (cmd_str ! NULL) {len pg_mbcliplen(cmd_str, strlen(cmd_str), g_instance.attr.attr_common.pgstat_track_activity_query_size - 1);
}// ...
rc memcpy_s((char*)beentry-st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size,cmd_str, len);
beentry-st_activity[len] \0;这里的关键点是 len是实际要复制的SQL长度经过截断处理但是memcpy_s的目标缓冲区大小始终是pgstat_track_activity_query_size只有实际使用的长度是变化的但预分配的空间是固定的 3. 内存占用情况 预分配空间每个后端进程都会分配pgstat_track_activity_query_size字节的空间实际使用只使用SQL实际长度 1null终止符的空间浪费空间pgstat_track_activity_query_size - (实际SQL长度 1)字节 4. 设计原因 这种预分配设计的原因 性能考虑避免动态内存分配/释放的开销并发安全共享内存中的固定布局避免竞争条件简化管理统一的内存布局便于管理和访问 所以答案是肯定的即使你的SQL很短比如SELECT 1;系统仍然会为每个后端进程的st_activity字段分配完整的pgstat_track_activity_query_size大小的内存空间。 结论分析
结合AI的分析可以得出以下结论 核心发现 固定内存分配 track_activity_query_size是固定会申请的内存大小占用的内存大小不会由于SQL比这个值小而减小实际单个unique sql所占用的空间会比track_activity_query_size还要大一些当instr_unique_sql_count为GaussDB默认的200000track_activity_query_size为40960时unique sql hash table能达到8GB SRF函数特性 SRF函数就是返回多行的函数dbe_perf.statement里就是一个SRF函数为了在返回多行时保持数据稳定性就会把查询结果全存到内存里直到查询完才释放 内存复制机制 dbe_perf.statement查的就是unique sql hash table的内容SRF为了数据稳定性几乎把这8GB的内存复制了一份放到了SRF multi-call context里 深层问题探究
看上去这就是个非常低级的问题但是问题还没完
为什么unique sql会那么快达到200000个
这个环境才刚部署没几天而且都是跑的存储过程没有什么拼接SQL理论上SQL数是稳定有限的。
通过截取statement.query前200个字符进行group by计数排序发现很大一部分SQL都是一个declare开头的自治事务匿名块。 根本原因
由于我之前和openGauss社区的研发一起分析过plsql编译依赖这个功能的问题当时为了解决编译时数据字典表锁释放的问题我提出了可以起个自治事务去编译这样能避免长时间持有元数据锁的问题。而GaussDB也是用的这个方案巧合。
所以我一眼就看出来GaussDB里记录的这些SQL是在做存储过程编译但是GaussDB把整个存储过程的内容都放到这个匿名块里了而且还有一些oid信息因此必然是不同的SQL。
这个匿名块还会做一些数据字典的DML操作这些DML的SQL是固定不变的大概有6~7条。再来就是dbe_perf.statement里对于parent_unique_sql_id不同的同一个unique_sql_id会视为不同的记录。
⚡ 问题链条完整梳理
存储过程编译每次改一个存储过程就在dbe_perf.statement里生成了约10条的记录重复生成反复改就反复生成有时候全量存储过程重建数字唰唰唰就涨上去了资源耗尽200000也经不起这么耗真正的业务SQL可能还会计不进去了内存占用全是存储过程编译时自动产生的这些SQL
目前客户这边只能先调小track_activity_query_size到4K让dbe_perf.statement的内存占得小些至少让业务测试能继续进行下去。 复现方法
诊断了这么多天的问题弄清楚原理后非常容易复现unique sql hash table和SRF multi-call context申请大量内存的现象 环境配置
-- 这个是单个会话的unique sql上限由于模拟测试都在一个会话里所以要调大
gs_guc reload -c track_stmt_session_slot200000-- 直接设置到最大值内存涨得最快
gs_guc reload -c track_activity_query_size102400-- 重启释放 unique sql 内存
gs_ctl restart测试脚本
-- 构造不同的SQL
-- 注意GaussDB对于表别名或字段别名不同、常量不同的也会进行归一化
-- 所以这里直接用不同的对象名
DECLAREx_sql1 text;x_sql2 text;i int;
BEGINFOR i IN 1..5000 LOOPx_sql1 : CREATE TABLE t_abcd||i||(a int);;x_sql2 : DROP TABLE t_abcd||i;EXECUTE IMMEDIATE x_sql1;EXECUTE IMMEDIATE x_sql2;END LOOP;RAISE NOTICE %, x_sql1;
END;
/验证结果
-- 查询动态共享内存里unique sql hash table的大小
SELECT contextname,count(1),ROUND(sum(totalsize)/1024/1024,2) AS TOTALSIZE_MB,ROUND(Sum(usedsize)/1024/1024) AS usedsize_MB
FROM GS_shared_MEMORY_DETAIL
GROUP BY contextname
ORDER BY 3 DESC
LIMIT 10;contextnamecounttotalsize_mbusedsize_mbunique sql hash table11468.751469DefaultTopMemoryContext1172.5760ASP unique sql hash table182.1182…………
unique sql hash table占1个多GB
-- 查询SQL实际长度所占的空间和按track_activity_query_size的长度计算的空间
SELECT sum(lengthb(query))/1024/1024 AS actual_size_mb,(count(1)*current_setting(track_activity_query_size)::int)/1024/1024 AS allocated_size_mb
FROM dbe_perf.statement;actual_size_mballocated_size_mb0.381401.07
按track_activity_query_size来算也是1个多GB。 日志验证
执行查询后日志中出现SRF multi-call context
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor Script-22.sql 00000 31806672368306287 [BACKEND] LOG: 00000: unique_sql_id3740540431, Current SQL is consuming about 1099 MB, allocating 102921000 bytes, memctx:SRF multi-call context, appname:, user_id: 16616, current query is: select sum( lengthb(query) )/?/?,(count(?)*current_setting(?)::int)/?/? from dbe_perf.statement
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor Script-22.sql 00000 31806672368306287 [BACKEND] LOCATION: mmgr_memory_check_reach_one_gb, framework_memory_memctrl_util.cpp:241
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor Script-22.sql 00000 31806672368306287 [INSTR] LOG: 00000: [UniqueSQL] idx[10] - new memory allocated: 102921000
2025-07-25 10:00:01.523 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor Script-22.sql 00000 31806672368306287 [INSTR] LOCATION: unique_sql_log_result_mem, instr_unique_sql.cpp:1407
2025-07-25 10:00:01.524 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor Script-22.sql 00000 31806672368306287 [INSTR] LOG: 00000: [UniqueSQL] total memory allocated: 1132131000
2025-07-25 10:00:01.524 primary admin postgres 172.17.0.1 140216907527936 12[0:0#0] 0 Mogeaver 23.3.1 - SQLEditor Script-22.sql 00000 31806672368306287 [INSTR] LOCATION: unique_sql_log_result_mem, instr_unique_sql.cpp:1408总结 经验教训 经验的积累非常重要 国产数据库使用过程中肯定会发现很多新问题其中有一些综合性的问题原厂的内核研发都不一定能找到原因具备综合技能的DBA能从现场的蛛丝马迹结合长期的工作经验所产生的直觉来发现问题症结所在 知识的积累非常重要 如果连内存区域都分不清就算数据库已经提供了很多方法来进行诊断面对本文这个问题脑子里只能是一团浆糊 需要学会借助AI 现在的AI有了agent能自动且快速完成很多事本文中就让AI自动翻源码分析对问题进行了快速定位 本文作者 DarkAthena本文链接 https://www.darkathena.top/archives/GaussDB-Memory-Crisis-Diagnosing-Temporarily-Unavailable-Failures版权声明 本博客所有文章除特别声明外均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处