ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SAP HANA内存溢出警告:单SQL语句排查与优化实战

SAP HANA内存溢出警告:单SQL语句排查与优化实战 SAP单SQL语句导致Hana内存溢出警告90%的人第一步就查错了方向前段时间客户群里突然炸锅HANA数据库弹出内存溢出警告消息队列里全是红色报错。我接手一看问题核心不是数据库本身出了什么故障而是某条SQL语句在跑批时把整个HANA实例的内存抬到了99%紧接着触发了一系列连锁反应。这类“单SQL打爆内存”的场景在做SAP运维和HANA开发的同行手里几乎都出现过但很多人拿到警告后的第一反应是加内存、改参数结果第二天又复现症结其实全在那条SQL和数据访问路径上。这篇文章把这类问题的排查思路和落地方法完整捋一遍从HANA内存架构讲起到手把手分析SQL执行计划再到实际优化案例最后附上我踩过的几个坑。不管是做FICO的、做MM的还是专职HANA DBA只要你的系统跑在SAP S/4 HANA上这篇都值得花十分钟看完。1. 内存溢出的“警告”到底在警告什么1.1 HANA内存模型的基础认知HANA和传统数据库有一个非常大的区别它把数据尽可能常驻内存列式存储加上并行计算引擎让复杂分析查询能跑出极快速度。但代价就是内存变成一种极其珍贵的资源一旦某个查询要构建超大中间结果集直接就逼近物理内存上限。HANA的内存使用大体可以分成两块主内存表数据永久驻留的部分和会话内存执行SQL时临时构造的堆。单条SQL导致内存溢出大多数时候是会话内存被某个执行计划疯狂占用。你可以把它想象成厨房食材主数据一直放在冰箱里但你要做一顿大餐时台面会话内存上必须腾出足够的空间来切菜、摆盘如果这道菜的做法要求把所有食材一次性全放在台面上台面自然就不够用了。HANA对内存的管理并不是无限放任它在列存储层有Allocation Limit这个概念也就是每个服务可以申请物理内存的上限。当某条SQL在某个会话中一次性申请了太多内存超出可用的会话内存限制系统就会抛出内存分配失败同时在运行时产生内存警告。SAP HANA的警告并不是只停留在日志里它会通过系统告警框架上报在ST03N、HANA Studio、以及SAP EarlyWatch Alert里都能看到。1.2 “单条SQL”为什么能顶爆整个实例很多非专业DBA会困惑一条SQL而已怎么会把整个实例内存打满关键在HANA的并行执行模型。如果一个SQL计划选择了非常差的连接顺序或者产生了一个巨大的中间结果集HANA可能会在多个worker线程中同时构造和缓存这批数据。这里的罪魁祸首通常是三类操作笛卡尔积两张毫无关联的表直接在JOIN条件缺失或条件错误时互相交叉中间结果集呈乘法级膨胀。大IN列表SQL里写了几万个甚至几十万个ID生成了一个超大的临时表结构且在优化器阶段被反复探测。非必要全表扫描一张几亿行的财务凭证明细表因为过滤条件用错了索引列或隐式转换导致极端情况下要把整张表展开。我遇到过最经典的案例是有人在报表程序里用SELECT *扫了ACDOCAUniversal Journal全表再和BKPF做连接最后还加了一个NOT IN子查询。这条SQL在Oracle时代可能只是慢在HANA上就直接把64GB内存干穿了。根本原因是HANA属于内存计算中间结果集的物理载体就是RAM同时HANA的优化器对复杂子查询更倾向于物化执行而不是相关子查询逐行执行。所以从定位角度看内存警告只是表象真正的病灶是某一条SQL的执行计划或数据量估算脱离了预期。2. 从“警告出现”到“锁定SQL”的排查路径2.1 第一时间该去哪个视图找真凶HANA暴露了很多内存和会话相关的监控视图但第一步不能大海捞针。我拿到内存溢出警告后的标准动作是连接到HANA系统通常是用SYSTEM账号或监控账号先看实时SQL执行状态。SELECT STATEMENT_HASH, SUBSTRING(STATEMENT_STRING, 1, 200) AS SQL_TEXT, MEMORY_SIZE, DURATION_MICROSEC, START_TIME, SESSION_ID, USER_NAME, PLAN_ID FROM PUBLIC.M_ACTIVE_STATEMENTS ORDER BY MEMORY_SIZE DESC LIMIT 20;这个视图会直接列出当前正在执行或刚执行完的SQL语句并按照内存占用排序。如果内存溢出警告发生时系统还没有恢复这个查询基本能瞬间定位是哪一条SQL。如果警告发生之后SQL已经跑完那就要靠历史分析。HANA提供M_EXECUTION_STATISTICS记录的是执行过的语句统计信息但注意它默认保留的时间可能不长且很多字段是聚合过的看不到完整SQL文本。因此更有效的方式是开启Statement Level Analysis或者使用EXPLAIN PLAN来复现。2.2 使用PlanViz精准还原执行计划HANA Studio或HANA Database Explorer里选中那条SQL点击Visualize Plan生成执行计划的可视化图这是所有操作里最实用的一步。PlanViz可以看到每个操作符消耗的内存、行数估算、实际返回行数以及连接顺序。我在实操时习惯重点关注三个东西Join类型是Hash Join还是Nested Loop Join。如果是大表和小表做Nested Loop小表被循环驱动含义就是大表每一行都去匹配小表内存和CPU都会爆炸。物化节点Tree里如果有Materialize、Column Search、Group By节点且估算行数和实际行数差距超过100倍说明统计信息已经严重失真。内存消耗最大的操作符PlanViz里每个操作符旁边会显示Memory Size这个字段不是在总览页而是需要展开高级属性查看。一旦锁定了内存消耗最大的节点就直接把SQL文本和Plan截图一起给开发人员要求他们改SQL逻辑或者添加更合理的过滤条件。注意HANA的PlanViz在某些版本里默认只展示逻辑运算符要切换到“物理运算符”模式才能看到底层内存申请情况这个检查项一定不要漏。2.3 遇到“SQL已结束”但是内存没释放怎么办内存警告和SQL运行结束是两个不同概念有时候SQL明明已经跑完但内存占用依然居高不下这是另一个隐蔽场景。HANA里有个Memory Management机制当一个会话申请了大量内存后内存不会立刻返回给操作系统而是留在HANA进程的堆里复用。这时要用M_HOST_MEMORY和M_SERVICE_MEMORY来查看实际分配情况SELECT SERVICE_NAME, PHYSICAL_MEMORY_SIZE, ALLOCATION_LIMIT, EFFECTIVE_ALLOCATION_LIMIT, TOTAL_MEMORY_ALLOCATED, USED_MEMORY, HEAP_MEMORY_ALLOCATED, FREE_PHYSICAL_MEMORY FROM PUBLIC.M_HOST_MEMORY UNION ALL SELECT SERVICE_NAME, NULL, NULL, NULL, TOTAL_MEMORY_ALLOCATED, USED_MEMORY, HEAP_MEMORY_ALLOCATED, FREE_PHYSICAL_MEMORY FROM PUBLIC.M_SERVICE_MEMORY;如果TOTAL_MEMORY_ALLOCATED很大但是USED_MEMORY并不高说明只是堆内存长期驻留没有释放和SQL峰值内存是两个概念。前者通常不需要过度关注调低allocation limit反而会引起性能问题。2.4 检查SAP应用程序层的ABAP痕迹因为SAP的绝大多数SQL不是直接手写进HANA Studio的而是通过ABAP程序里的SELECT语句生成。所以如果HANA层已经定位到SQL还需要回到ABAP层找源头。在SE80或SE24里根据HANA定位到的SQL文本对应到ABAP程序名和事件块比如START-OF-SELECTION。很多时候问题出在Open SQL的FOR ALL ENTRIES IN这个语法是SAP ABAP开发非常常用的批量读取方式它在HANA里会展开成大量WHERE条件如果没有判断内部表为空就执行或者内部表数据量过大生成的SQL可能几MB甚至几十MB。我在SAP S/4 HANA项目里就碰到过一个典型的例子物料凭证报表内表LT_MSEG含有20万条记录开发直接用了SELECT * FROM MSEG FOR ALL ENTRIES IN LT_MSEG WHERE MBLNR LT_MSEG-MBLNR翻译到HANA就是一个带20万个OR条件的大SQL内存瞬间飙升。正确做法是先按物料凭证号分组、分批次读取或者把条件改为范围表range table加Join让HANA能走Hash Join而非OR展开。3. 单SQL内存问题的通用优化手段实测3.1 精准命中行的过滤条件优化优化SQL内存消耗我遵循一个原则让HANA尽量少拿数据。某项目里有一条查询未清供应商发票的报表原SQL大约是SELECT A.UMSKZ, A.BUKRS, A.LIFNR, A.BELNR, A.BUDAT, A.DMBTR FROM BSEG AS A WHERE A.KOART K AND A.AUGDT 00000000 AND A.UMSKZ P看起来过滤条件已经写了但AUGDT 00000000这一条件的区分度非常低。如果过去年份一直没有清账这张表里可能有上亿行未清数据。我在优化时让它改成了通过供应商主数据表先缩小范围即先取LFA1里最近有业务往来的供应商清单再join到BSEG内存直接下降了70%SELECT A.UMSKZ, A.BUKRS, A.LIFNR, A.BELNR, A.BUDAT, A.DMBTR FROM BSEG AS A INNER JOIN ( SELECT DISTINCT LIFNR FROM LFA1 WHERE SPERRQ IS NULL AND ERDAT 20230101 ) AS B ON A.LIFNR B.LIFNR WHERE A.KOART K AND A.AUGDT 00000000 AND A.UMSKZ P这种“先缩范围再join”的思路在HANA上比任何参数调整都有效。HANA是列式存储它的优势在于扫描列劣势恰恰在于逐行物化大量列并构建哈希表。3.2 分批处理避免单SQL内存峰值如果业务上确实避免不了大批量数据处理那就从应用层拆分。我在处理财务月结节点时经常这样做ABAP程序里不再用一个lt_result SELECT * FROM ...一次取全量而是增加分页逻辑每5000条一页处理完释放对象引用。有些开发疑虑分页会导致数据不一致其实只要在程序外层加好事务控制比如按时间段拆或按公司代码拆完全不会影响最终结果。在HANA层面还有一种做法是把大集合拆到临时表里分批聚合然后UNION ALL结果。比如SELECT BUKRS, BELNR, DMBTR FROM BSEG WHERE BUKRS 1000 AND BUDAT 20240101 AND BUDAT 20240201 UNION ALL SELECT BUKRS, BELNR, DMBTR FROM BSEG WHERE BUKRS 1000 AND BUDAT 20240201 AND BUDAT 20240301 ...表面上看SQL变长了但每一个分段的中间结果集都大幅缩小HANA不再需要把全年的数据一次性装进内存做排序分组整体内存曲线会平滑得多。3.3 避免“SELECT *”和隐式转换SAP HANA里SELECT *是大忌不止是内存问题还会导致网络传输和SQL计划变大。尤其是当表中有一堆CLOB、NCLOB或者大字符串字段时SELECT *会把整列数据物化到结果集即使上层界面只显示前几个字段。另外一个更隐蔽的坑是隐式转换。比如BSEG的BELNR字段类型是CHAR(10)但查询条件传了一个NUMC(10)类型变量。ABAP层常常不自知把变量直接传给Open SQLHANA在解析时无法直接走索引可能会把整列做转换后再比较形成一次全表扫描。排查时可以看WHERE条件的谓词使用情况如果发现计划里出现TABLESCAN而不是SEEK或RANGE SCAN就要去查字段转换函数是否包在列上。优化方式是统一变量类型在ABAP层先CONV或赋值给同类型变量再传入SQL。3.4 对大IN列表做临时表代替SAP系统里很多自定义报表会从前端收集一批筛选值比如用户勾了一万个物料号代码直接拼进SQLSELECT * FROM MSEG WHERE MATNR IN (10000001, 10000002, ...)如果这个列表超过几千个HANA优化器会把它物化成一个大集合并且每个值都参与连接判断。我的经验是超过500个值就不要再硬拼了改成先把筛选值插入到HANA临时表再和业务表INNER JOIN。在ABAP里可以用CREATE GLOBAL TEMPORARY TABLE或者直接使用HANA的SESSION_CONTEXT但最实用的还是用FOR ALL ENTRIES加上分块逻辑每一块处理500个值循环多次。如果实在要在一条SQL里完成也有个技巧把IN列表转换成表值构造函数。例如SELECT * FROM MSEG AS M JOIN ( SELECT 10000001 AS MATNR FROM DUMMY UNION ALL SELECT 10000002 FROM DUMMY UNION ALL SELECT 10000003 FROM DUMMY ) AS I ON M.MATNR I.MATNR这种写法让HANA优化器走Hash Join而不是在谓词展开中消耗大量内存。实测同样的1万个IN值内存占用能降一半以上。3.5 给执行计划“喂”对的统计信息HANA有一套自动统计信息更新机制但有时候因为大数据量变更或大量删除统计信息会过期。此时优化器会做出错误估算为一条小结果集SQL设计出巨大内存方案。手动收集统计信息可以在命令行或用SQL执行CALL UPDATE_STATISTICS(BSEG, NULL, 100);100代表采样比例是100%也就是全量统计。对于大表全量统计耗时会比较长建议在业务低峰期执行。日常运维可以在每天凌晨跑一批关键表的统计信息更新防止单SQL因估算失真而内存爆掉。我在多个项目里验证过很多“某条SQL这段时间突然内存暴涨”的案例既不是SQL改了也不是数据量突增而是统计信息过期导致执行计划从索引扫描退化成了全表扫描。刷新统计后一切恢复如初。4. 常见问题与排查技巧实录4.1 内存警告但找不到大SQL有朋友遇到过HANA告警里报内存溢出但查M_ACTIVE_STATEMENTS和M_EXECUTION_STATISTICS都没有明显的“大胃王”SQL。这时候不要只在SQL层面找要检查是否有列存储表的合并操作Delta Merge正在执行。HANA列存储表的写入是先到Delta存储再定期合并到Main存储。当Delta很大时合并操作会申请大量内存。这个过程由后台线程执行并不对应某条应用SQL。处理方式是错峰执行合并或调整合并参数。可以通过ALTER SYSTEM RECLAIM DELTA来主动控制合并时机。4.2 改大内存参数到底有没有用global.ini中有不少内存限制参数比如allocationlimit。有同行一看到内存警告就去找SAP NOTE把allocationlimit调高让数据库能申请更多内存。短期看机器内存还没用完确实能缓解但长期看这是在掩盖SQL问题副作用明显进程不断增加物理内存耗尽后系统会触发OOM数据库直接宕机损失更大。我个人的建议是allocationlimit保持SAP默认或按物理内存的90%设置千万别为了跑通一条烂SQL而无脑调高。真正该做的是优化SQL本身。4.3 HANA Studio和ABAP里看到的SQL文本不一样HANA层看到的SQL尤其是有FOR ALL ENTRIES或参数化语句时运行时变量已经被替换成实际值文本和ABAP代码里的Open SQL完全不一样。不要试图在HANA层反推ABAP代码里的变量名而是要根据实际的表名和WHERE条件组合再到ABAP程序里去查找对应的读取逻辑。通常我会在SE80里使用Where Used List搜索HANA SQL里出现的表名和关键字段快速定位到ABAP程序段。如果项目里有代码审计工具直接把HANA捕获的SQL文本丢进去匹配也可以。4.4 排查工具和脚本清单以下是我日常排查HANA内存问题最常用的一组SQL组合使用效果很好-- 查看当前活动语句内存排行 SELECT TOP 10 STATEMENT_HASH, MEMORY_SIZE / 1024 / 1024 AS MEM_MB, START_TIME, DURATION_MICROSEC / 1000000 AS DURA_SEC, SUBSTRING(STATEMENT_STRING, 1, 100) AS SQL_TEXT FROM M_ACTIVE_STATEMENTS ORDER BY MEMORY_SIZE DESC; -- 查看近期执行时间较长且内存较高的语句 SELECT TOP 20 STATEMENT_HASH, MAX(MEMORY_SIZE) / 1024 / 1024 AS MAX_MEM_MB, SUM(DURATION_MICROSEC) / 1000000 AS TOTAL_SEC, COUNT(*) AS EXEC_CNT, SUBSTRING(MAX(STATEMENT_STRING), 1, 100) AS SAMPLE_SQL FROM M_EXECUTION_STATISTICS GROUP BY STATEMENT_HASH ORDER BY MAX_MEM_MB DESC;这两条SQL对快速定位是哪类SQL在消耗内存非常有帮助。如果一条SQL反复执行且每次内存都不小那它在统计视图中会非常醒目。4.5 优化后如何确认内存不再吃紧改完SQL后不能只靠肉眼观察。我一般在关键月结/日结跑批时开启HANA的语句级内存采集。ALTER SYSTEM ALTER CONFIGURATION (global.ini, SYSTEM) SET (sql_plan_cache, enable_plan_analysis) true WITH RECONFIGURE;然后在HANA Studio里打开SQL Plan Cache的分析功能设置定时刷新观察内存占用曲线。优化后的目标不只是“不报警告”而是让单条SQL的峰值内存低于全系统可用会话内存的10%。如果某条SQL能稳定在几MB到几十MB级别基本不会再成为内存炸弹。5. 一些容易被忽略但影响巨大的细节5.1 系统复制和备份任务也会占用内存如果内存警告发生在系统复制或备份窗口要先去确认hdbbackint或system replication进程是否在跑。它们会直接调用HANA的内部服务占用大量内存和I/O。有一个案例特别典型客户每天凌晨3点做全量备份同时财务对账程序也在3点跑两者相互叠加把内存顶到100%。这个其实和SQL语句无关但SAP的警告监控里一样会显示内存使用率过高。遇到这种情况把批处理时间错开即可。备份可以设置限流SAP里可以用SCHEDULER调整后台JOB把对账程序挪到备份结束后的窗口。5.2 开发环境和生产环境的统计信息差异开发系统里跑得好好的SQL上到生产系统内存爆掉原因多半是生产数据量和分布完全不同。开发库里可能只有几千条数据全表扫描都没什么压力生产库里几亿条数据同样的执行计划完全不是一回事。这提醒我们在做代码迁移时一定要把HANA的执行计划对比纳入测试范围不要只看查询结果是否正确。5.3 别忽略了“内存溢出”和“内存截取”的根本区别有个热搜词叫“溢出和内存截取的区别”在这个场景里也容易混淆。HANA警告里的内存溢出是内存分配失败或请求超过阈值本质是资源不足而“内存截取”是函数在拷贝字符串或二进制数据时截断导致的字符错误是逻辑层的bug。遇到HANA告警时先分清是资源层面还是应用逻辑层面否则排查方向会彻底跑偏。6. 针对SAP常见模块的SQL内存隐患6.1 FICO模块的常见大SQL场景FICO里最容易踩内存坑的是资产折旧、未清项管理、以及科目余额汇总。例如固定资产折旧使用SAP KO88过账时后台程序会把大量资产行的金额汇总插入会计凭证表。如果资产主数据和旧年度折旧数据没有被正确归档一次折旧过账就可能把几年的数据全部捞起来处理。相关表如ANEP、ANLC、BSEG都要重点监控。对于这类问题最好的方式是定期归档历史数据保持表数据的“年轮”在可控范围内。另外在跑KO88之前先用S_ALR_87012011等报表检查资产数据量评估是否需要分成本中心、资产编号段分批过账。6.2 MM模块的物料凭证和库存报表SAP MD07是物料需求计划里的一个经典报表事务码它要合并多个工厂、多个库存地点的物料需求。如果物料主数据庞大且未做MRP范围限制这条报表的SQL会在后台构造一个特别大的中间结果集。有项目通过在HANA侧的SQL计划分析中发现该报表一个查询就吃掉了几十GB内存。MM模块的物料凭证表MSEG和会计凭证表BSEG、ACDOCA都是超高数据量表格任何对这些表不加严格时间/工厂过滤条件的查询都要小心。我在做开发评审时见到来自MSEG的查询条件里连MBLNR范围都没有基本直接要求退回。6.3 序列号管理带来的隐藏爆炸点SAP序列号管理模块里有一张核心表EQUI、SER01相关表结构在HANA里通过多次JOIN会产生非常宽的中间结果。如果查询直接串联序列号主数据和物料凭证很容易把两个大表交叉起来。这类问题的优化思路是提前聚合先按物料凭证把序列号清单压缩成一条一条的明细再去连主数据表。有一次优化前SQL跑了5分钟内存报错优化后同样的结果集跑了30秒内存峰值降到原来的15%效果就是这么明显。7. 长期监控和预防机制建议7.1 配置好HANA告警阈值预防性强于事后救火。HANA默认的告警阈值可能对某些系统来说太高当收到默认告警时往往内存已经接近物理极限SQL已经跑挂了。我一般建议把Memory Used的黄色告警阈值设为总内存的70%红色设为85%并启用每周的用量趋势报告。具体配置在SAP HANA cockpit的Alert配置里可以调或者在global.ini中设置内存相关参数。7.2 学会使用SAP HANA Data Lake和归档很多内存问题的根源是数据只进不出。对于S/4 HANA系统历史数据全放在行式或列表内对内存的消耗是持续的。现在官方的推荐方案是使用HANA Data Lake或定期归档到BW/4HANA或第三方数据湖中主系统只保留必要的活动数据和近几年的凭证数据。做了归档之后不仅查询变快内存占用也会显著下降。SAP有标准的SAP Archiving工具比如FI_DOCUMNT对象用来归档会计凭证MM_MATBEL归档物料凭证。月结后把上上年的凭证归档一次效果立竿见影。7.3 建设SQL性能基线建议每个SAP系统都建立一个SQL性能基线库。每次发布新程序或增强时在测试环境记录该程序的核心SQL文本、执行时间、内存占用上线前三天的值留痕定期对比。其实就是一张Excel表维护几列关键数据程序名、事务码、SQL hash、执行时长、内存峰值、启用日期。坚持几个月后你就能提前发现哪些SQL正在“悄悄变胖”而不是等内存警告出来了才开始查。从我这几年的运维和项目经验来说HANA的内存问题九成以上不是靠加内存解决的而是靠“让SQL少拿数据”解决的。每个系统都有那么几条“骨架级SQL”它们性能稳定整个系统就稳定它们一旦失控内存和CPU就会跟着失控。你在排查内存警告的时候最好记住这个顺序先抓现场再回放执行计划再回ABAP层找代码逻辑最后考虑参数调整。顺序反了很大概率会白忙活一场。这次分享的内容基本覆盖了我处理单SQL导致Hana内存溢出警告的完整路径。如果你也在SAP S/4 HANA上遇到过类似的告警建议按上面提到的M_ACTIVE_STATEMENTS思路第一时间抓一条带内存大小的SQL然后回来对照PlanViz一步步分析。不要一上来就 ban 掉所有大查询而是追根溯源找到那个中间结果集膨胀的节点针对性优化问题才能真正了结。
返回列表