ARTICLE DETAIL

资讯详情

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

SAP单SQL语句导致HANA内存溢出:从告警到修复的完整排查实战

SAP单SQL语句导致HANA内存溢出:从告警到修复的完整排查实战 SAP单SQL语句导致Hana内存溢出警告从告警到定位再到修复的完整实战手记上周值班时SAP生产系统突然弹出HANA内存溢出警告后台任务大面积挂起财务用户登录都开始卡顿。打开HANA Studio一看一个异常SQL把整个实例的内存几乎吃空。这种事在SAPHANA的架构下并不少见尤其是FICO、MM、PP这些模块跑月结、MRP、折旧批量作业的时候一条写得不严谨的SQL就能让内存数据库直接呛住。本文想完整还原一次SAP单SQL语句导致Hana内存溢出警告的排查和修复过程重点说清楚HANA侧、SAP侧分别怎么定位执行计划怎么看以及哪些参数能救急、哪些参数千万别乱动。适合ABAP开发、HANA DBA、SAP Basis以及做ERP运维的同行参考新人看一遍也能知道遇到这个问题该从哪里下手。1. 问题现象与排查层级判断1.1 一次典型的HANA内存警告现场当天上午十点多监控平台报警提示HANA实例内存使用率超过95%紧接着SAP系统里大量RFC调用超时业务端开始反馈MIRO过账转圈、MIGO收货报错、FBL3N查询半天不出数据。登录HANA Studio后看到内存视图整体飘红数据库服务所在的物理节点可用内存在十几分钟内从40%冲到告警线。这类现象有个明显特征内存曲线不是缓慢爬坡而是断崖式暴涨。普通业务高峰期内存使用是逐步抬升的而单条SQL导致的内存溢出往往是几分钟内就冲到顶随后HANA开始拒绝新的内存分配请求已运行的大查询也会被系统强制终止。如果此时你去看HANA的“事件”标签页通常能找到一条类似“Out-of-memory”或“Allocation limit reached”的记录。1.2 HANA内存超限的warning与critical有什么区别HANA的内存管理走的是“先警告、后终止”的路线理解这一点对判断故障等级很重要。HANA在global.ini的memorymanager段里维护了一套分配阈值。默认情况下数据库允许使用的总内存上限由global_allocation_limit控制通常是物理内存扣掉系统保留后的一定比例。当你接近这条线时HANA会生成内存分配警告进入一个紧张状态此时系统还能运行但新的大查询可能被挂起或降速表现出来就是业务变慢、偶发超时。如果再往上冲突破了另一个更刚性的临界线HANA就会直接选择终止最耗内存的会话具体哪个会话倒霉取决于系统内部的内存优先级评估这时用户看到的可能就是“Connection terminated”或“Statement execution canceled”。很多刚接触HANA的同行会把这两个阈值混为一谈以为看到警告就要立刻加内存、改参数。实际上warning阶段的含义是“系统正在挣扎但还能撑住”这个窗口恰恰是最宝贵的定位时间。我个人的习惯是任何内存警告出现后先别动配置花十分钟找出占内存最大的SQL绝大多数情况下问题在这条语句上而不是在系统容量上。1.3 第一步先分清是应用层内存还是数据库层内存这里有一个容易踩坑的地方。SAP系统的内存问题并不一定是HANA数据库的内存问题还可能出在应用服务器上。ABAP工作进程有自己独立的内存配额当某个事务码或报表在应用层一次性捞取几十万行数据到内表时堆内存暴涨同样会让用户感觉“系统卡死”但HANA侧内存曲线可能非常平缓。所以收到“内存溢出警告”后我的排查顺序是先在操作系统层看是哪个进程占用内存飙升。如果是hdbnameserver、hdbindexserver等HANA进程吃满基本就是数据库层问题如果是sapstartsrv或dispwork占了大量内存就要去ABAP层查程序。再进HANA Cockpit或Studio看实例内存概览确认是单节点还是全实例耗尽。最后才去看是哪个SQL、哪个程序引发的。这套顺序能帮助你快速选择排查工具。如果错误地拿着ABAP程序员视角去查报表代码而问题根本不在应用层会浪费大量时间。反过来也一样数据库内存正常但ABAP工作进程OOM你去HANA里面翻执行计划基本一无所获。2. 用HANA侧工具锁定“吃内存”的那条SQL2.1 先翻M_EVENTS确认告警的真实来源接到内存报警时第一步不是直接去看SQL而是先确认HANA内部到底记录了什么事件。HANA的系统视图M_EVENTS保存了实例级的关键事件记录包括OOMOut-of-Memory相关的条目。可以执行如下SQL去查SELECT EVENT_TIME, EVENT_TYPE, DETAIL, USER_NAME, HOST, PORT FROM SYS.M_EVENTS WHERE EVENT_TIME ADD_SECONDS(CURRENT_TIMESTAMP, -3600) ORDER BY EVENT_TIME DESC;如果记录里出现事件类型为“OOM”或“Memory Allocation Failure”的条目说明确实发生了内存分配失败此时继续往下抓SQL才有意义。顺便说一句M_EVENTS的DETAIL字段经常包含被终止会话的详细信息例如Statement memory limit of 209715200 bytes reached for statement ...如果能看到“Statement memory limit”字样说明系统已经配置了单语句内存上限并且确实有语句撞墙了。如果你的HANA环境中这条事件里只有“global allocation limit”相关描述说明没配单语句内存限制全局内存被某个会话一步步拖垮此时你面对的问题通常更棘手。2.2 SQL Plan Cache里按内存占用排序找出最贵的语句HANA会缓存运行过的SQL执行计划视图M_SQL_PLAN_CACHE就是排查高内存SQL的首选入口。我常用的查询是这样的SELECT STATEMENT_HASH, STATEMENT_STRING, EXECUTIONS, MAX_MEMORY_SIZE, TOTAL_MEMORY_SIZE, TOTAL_DURATION, LAST_EXECUTION_TIMESTAMP FROM SYS.M_SQL_PLAN_CACHE WHERE EXECUTIONS 0 ORDER BY MAX_MEMORY_SIZE DESC LIMIT 50;重点看两个字段MAX_MEMORY_SIZE某条语句单次执行消耗的最大内存单位是KB。如果这个值达到几百GB甚至接近TB级基本就是罪魁。TOTAL_MEMORY_SIZE这条语句累计消耗的内存总量。如果单次最大内存不大但累计值很大说明高频执行但单次开销不高的SQL也在汇聚内存压力这种属于“温水煮青蛙”式问题。还有一个实用技巧用LAST_EXECUTION_TIMESTAMP和告警时间做交叉比对。如果一条SQL的最近执行时间正好落在内存告警发生的时间窗口内并且MAX_MEMORY_SIZE甩开其他语句一个数量级那几乎可以锁定它就是肇事者。拿到SQL文本后可以通过STATEMENT_HASH在SAP侧反查也可以直接看文本内容判断属于哪个业务模块。有时候SQL文本带有注释片段比如“select from MARA where ...”基本能猜到是物料主数据相关。2.3 开启Expensive Statement Trace盯梢抓现场最直接的证据如果内存告警还在持续或者你希望在下一次告警发生时自动留下证据就开启HANA的Expensive Statement Trace。在HANA Studio中进入“Administration → Trace Configuration”找到Expensive Statement Trace设置一个合理的阈值。比如将执行时间超过10秒或内存超过1GB的语句都记下来。也可以直接用ALTER SYSTEM命令设置ALTER SYSTEM ALTER CONFIGURATION (global.ini, SYSTEM) SET (expensive_statement, threshold_execution_time) 10000000; ALTER SYSTEM ALTER CONFIGURATION (global.ini, SYSTEM) SET (expensive_statement, threshold_memory) 1000000;这里threshold_execution_time单位是微秒threshold_memory单位是KB设置完成后可以查询M_EXPENSIVE_STATEMENTS视图观察记录。这个工具的价值在于它是“提前布置的监控”就像给系统装了一个行车记录仪。内存告警往往是突发的当你接到报警再去手工查询时那个肇事SQL可能已经被HANA终止了执行计划缓存里也未必能留下痕迹。这时候Expensive Statement Trace里可能早就默默地记录了每一分钟的高耗语句。3. 反查SAP端从SQL文本到业务程序3.1 用ST05把HANA语句映射回ABAP报表拿到HANA侧的SQL后问题并没有结束。SQL只是表征你真正需要知道的是哪个程序、哪个事务码、哪个业务操作发起了这条语句。SAP的事务码ST05SQL Trace是连接ABAP端和HANA端的关键桥梁。操作流程是让用户或后台Job重现问题操作。在ST05中激活SQL Trace选择跟踪类型通常勾选“SQL”和“Buffer”即可。问题场景结束后停止跟踪。在结果显示页面里查找对应的HANA SQL语句双击条目后可以看到ABAP程序名、include名、行号等信息。这里有个细节SAP系统在HANA数据库上执行SQL时Open SQL语句往往会被改写为大量列存储相关的底层SQL你在ST05里可能看到一堆带“#”号或内部函数名的复杂语句不仔细看会眼花。建议先按“操作耗时”或“记录数”排序找最夸张的那几条再逐条对应到ABAP语句上去。ST05给出的程序名定位已经足够精确但如果程序里SQL是动态拼接的依然要回到ABAP代码中去查是哪个动态SQL模板被填充成了这段文本。此时启动SAT事务码ABAP运行时分析重新重现业务场景可以拿到更完整的ABAP调用堆栈。3.2 用ST22和STAD判断当时发生了什么有时候问题已经发生现场已经被恢复用户也没有办法立刻重现操作。此时就要依靠SAP系统自身留下的运行痕迹。ST22是ABAP Dump查看器。如果ABAP端因为数据库返回超时或连接中断产生了短转储ST22里会留下完整的错误信息和调用栈。很多情况下虽然崩溃的是一条Open SQL指令但堆栈里能清楚看到调用它的函数模块、报表名以及用户当时的操作事务码。STAD系统日志则能查到某一个用户在某个时间执行过哪些事务码、哪些程序。比如财务用户在上午10点15分运行了FAGLL03总账行项目查询紧接着内存告警在10点18分出现时间线上吻合度就很有说服力了。类似地对于后台作业引发的问题用SM37去查询当时运行的后台Job列表重点看FICO月结如折旧、结算、MM的MRP运行MD01、MD07等大事务批处理任务的时间与告警时间是否重叠。3.3 一次容易误判的后台任务场景我处理过一个非常典型的案例。每天凌晨两点左右HANA定时出现内存告警白天完全正常。当时看M_EVENTS里被终止SQL的文本里面带有MSEG和MATDOC的联合查询看起来像是在查物料凭证。由于文本并没有直接暴露ABAP程序名一开始以为是无脑全表扫描的报表后来在SM37里一查发现凌晨两点运行的是固定资产折旧与物料分类账的集成后台程序该程序对物料凭证表做了大批量统计拼接出了一个巨大的动态SQL。这类问题最坑的地方在于夜批程序你不可能停掉业务要求又必须保留数据完整性。此时优先考虑的不是改写程序而是在HANA层确认表数据量、索引情况和统计信息实在不行再考虑给特定语句加优化Hint或者调整SAP端的程序配置参数。从这次之后我在排查内存类问题时多了一个习惯永远把SM37的后台作业清单和M_EVENTS的告警时间线放在一起看而不是单看某一条SQL。4. 根因剖析与SQL修复实战4.1 用EXPLAIN PLAN看执行计划找到内存黑洞锁定SQL文本后下一步是分析它为什么耗内存。HANA的EXPLAIN PLAN功能可以展示执行计划的底层算子让你看到哪些环节在“吃内存”。你可以在HANA Studio选中SQL执行Explain Plan也可以直接敲EXPLAIN PLAN FOR SELECT ... ;执行计划里最需要关注三类算子哈希连接Hash Join相关算子哈希连接的特点是把一侧全量数据构建成哈希表放到内存如果连接条件选择性差这个哈希表可能把内存暴涨。行缓存或中间表落盘的算子尤其是大表之间做UNION或排序时临时结果集过大。Tabular临时结果集相关的计算特别是包含大量DISTINCT、GROUP BY、窗口函数时。你还需要重点确认是否出现了笛卡尔积。HANA执行计划中如果两个表没有任何连接条件会产生一个乘法效应的中间结果集。比如A表200万行、B表50万行笛卡尔积理论上就是1亿行中间结果内存爆掉只需几个这样的算子叠加。4.2 案例一缺少连接条件导致的笛卡尔积“爆炸”某个客户系统的生产订单报工查询在周末集中进行时HANA内存告警。查M_SQL_PLAN_CACHE后发现一条语句MAX_MEMORY_SIZE超过600GB而业务正常查询通常只有几十MB。SQL经过格式化后大致是SELECT AUFK.AUFNR, AFPO.POSNR, MSEG.MATNR, MSEG.BUDAT FROM AUFK INNER JOIN AFPO ON AUFK.AUFNR AFPO.AUFNR LEFT JOIN MSEG ON MSEG.AUFNR AFPO.AUFNR WHERE AUFK.AUFNR IN (...)看起来没什么问题但执行计划里显示MSEG表是通过“Nested Loop Join”与AFPO逐条拼的。问题出在哪里呢MSEG表在HANA上按移动类型、过账日期设计了很多分区MSEG.AUFNR上没有合适的分区裁剪条件。当IN清单里的订单号过多时优化器放弃了分区裁剪扫描了整张MSEG表中间的LEFT JOIN结果集又被放大到了百万级别。在这个场景下最简单的修复是给MSEG增加一个针对AUFNR的二级索引并通过HANA计算视图固化常用的过滤条件避免每次扫描全表。4.3 案例二IN条件列表过大加全表扫描另一种常见场景出现在采购或库存分析类报表。ABAPer在程序中拼动态SQL按用户选择界面里的多个物料编号、工厂代码等生成条件硬生生拼出一个上千项的IN列表。SELECT * FROM MATDOC WHERE WERKS IN (1000, 1001, 1002, ...) AND BWART IN (101, 102, 201, ...) AND BUDAT 20250101这条SQL表面上有过滤条件实际上IN列表中的值多到接近全表。如果MATDOC是数亿行的日志型表优化器在统计数据时会认为“IN条件覆盖的扇区太多”干脆放弃索引直接全表扫描同时要把匹配结果送去做后续的GROUP BY内存被瞬间吃满。这种问题在ABAP端有几种解法将IN列表拆成多次查询分批取出数据后合并。改用内表与数据库表进行JOIN。如果必须保留原逻辑可以给MATDOC增加针对WERKS、BWART、BUDAT的复合列索引。需要提示的是SAP标准报表里也会有类似逻辑但标准程序通常有WHERE条件控制且经过优化出现问题的往往是客户自定义开发报表。遇到自定义报表我通常直接找开发团队按“一次取少量、循环处理”的思路重构比在HANA里硬调参数有效得多。4.4 案例三应用层取数过多导致的双重内存压力有个同行问过我一个问题一条SQL在HANA侧跑得并不慢执行计划也很健康但系统还是出现内存警告。排查后发现这条SQL通过Open SQL把整张表几千万行数据全部传到了ABAP应用服务器HANA侧只是把内存给了结果集应用服务器侧则把内存给了内表。两边一起涨压力叠加。这种问题的典型表现是HANA数据库内存使用率并不算极高但应用服务器出现重负载系统总内存告警。SQL本身在数据库端谈不上“有罪”真正的问题在于调用程序没有做好数据量控制。定位方法也很简单去ST05里看这条SQL返回的记录数如果返回记录数是几百万行甚至更高再对应到ABAP内表声明处基本就能判断程序是否存在全表载入的坏味道。这类SQL的修复方向是在ABAP里添加分页逻辑如使用UP TO n ROWS或分批取数。尽量把聚合计算下推到HANA而不是取回应用层用LOOP汇总。对于报表类程序入口限制用户必须输入足够的选择条件至少保证一个强过滤字段有值。4.5 补充建议HANA计算视图和物化视图的运用对于长期存在且业务上无法简单改代码的场景可以考虑用HANA计算视图或物化视图来固化复杂逻辑。计算视图的好处是把JOIN、聚合的负担放在数据库层由HANA引擎根据统计信息优化执行避免ABAP程序端动态拼SQL的不确定性。如果业务场景是“固定条件的月报、周报”可以进一步创建物化视图如果需要来提前算好结果。但这里要提醒一句计算视图并不是保险箱如果视图内部各个节点没有合理设置维度或者没有执行计划级的分区裁剪同样可能把内存吃光。创建视图后一定要用EXPLAIN PLAN检查整条链路的算子分布。5. 常见问题与排查技巧实录5.1 不要一上来就调global_allocation_limit这个坑我踩过。HANA内存告警出现后当时第一反应是“内存不够了把那几个大内存参数往上提一提”于是调大了global_allocation_limit。结果呢单条SQL的内存消耗本来就巨大给了更多配额后SQL不但没有优雅地降级反而把系统彻底拖死在更极端的状态整个HANA实例直接不可用被迫重启。现在的原则很明确global_allocation_limit是兜底保护是避免系统彻底僵死的最后一道闸不到万不得已绝对不动。如果内存真的不够用优先考虑增加物理内存而不是在参数层面放水。单条SQL的问题要从SQL本身去解决靠参数喂养只会养出更大的怪物。5.2 statement_memory_limit这个参数该怎么用如果你希望在做SQL优化之前先务实地保护一下系统建议关注的是statement_memory_limit而不是global_allocation_limit。这个参数可以限制单条语句能够分配的内存上限。当某条SQL超过这个阈值时HANA会直接终止它避免全系统被拖垮。你可以在global.ini中配置ALTER SYSTEM ALTER CONFIGURATION (global.ini, SYSTEM) SET (memorymanager, statement_memory_limit) 2000000;单位是MB例如上面这条就是把单条语句的内存上限设为2TB2024年后的版本单位略有差异需要根据版本确认。这里要注意不要设得太小否则正常的大报表也可能被误杀。我一般先观察这个系统里“正常业务下最复杂的报表”内存峰值然后在它的基础上上浮30%到50%作为statement_memory_limit。设置后当出现内存问题时系统会报“Statement memory limit reached”这样可以保留一条干净的终止信息同时不会影响其他会话。5.3 内存警告反复出现时先检查统计信息和数据分布有些时候SQL本身写得很简单、执行计划看着也“正常”但内存警告还是反复出现。这时候请先去查HANA表统计信息是否最新。HANA是列存储数据库优化器对谓词选择性的判断极度依赖表统计信息。如果某张表的ROW COUNT和实际数据量严重不符优化器可能判断某个过滤条件只能筛出一部分数据实际却匹配了海量数据从而选择错误的连接顺序或Join类型。刷新统计信息可以使用CALL UPDATE_TABLE_STATISTICS(SCHEMANAME, TABLENAME);如果是关键大表建议设置在夜间定时任务里周期执行或者用HANA的自动统计信息管理策略覆盖。另外对分区表的过滤条件要留意分区裁剪Partition Pruning是否生效。如果WHERE条件里的字段和分区键不匹配优化器会扫描所有分区内存压力自然成倍增加。这种问题光看SQL文本不一定能发现必须结合执行计划观察Accessed Partitions。5.4 告警时间与SQL执行时间对不上怎么办还有一种脑壳疼的情况M_SQL_PLAN_CACHE里找出来的高内存SQL执行时间是凌晨三点但内存告警是上午十点出现的完全对不上。这里有一个容易忽略的点大SQL执行完后HANA的内存并不一定会立刻释放给操作系统。列存储中的某些内存池在语句结束后仍然保留以备后续查询重用。因此从业务视角看告警峰值可能出现在上一波大任务结束后很久。如果遇到这种时间错位你要看的是M_EVENTS里实际记录的内存释放缓慢或内存池占用过大的信息同时结合全局内存趋势图观察而不是单独咬住某一条SQL不放。这种情况下排查重点要转向“系统是否存在内存泄漏、某个服务的内存池是否异常膨胀”可以在HANA Cockpit的“Memory Overview”中按Service查看各进程的已分配内存和峰值内存。5.5 SQL审核与预防性手段经历过一次HANA内存溢出警告之后我相信大部分团队都会意识到SQL规范的重要性。真正要长期解决这类问题靠的是把预防做在前面。常见的建议包括在SAP开发阶段对Open SQL做数量管控严格限制无WHERE条件的全表扫描。对自定义报表入口做强制选择条件比如日期范围必填、工厂必填。建立HANA侧的监控每日检查M_SQL_PLAN_CACHE中内存消耗TOP N的语句。对CBO模式下容易歪的执行计划用HANA的SQL Plan Hint或ABAP端的Open SQL Hint做固定的连接顺序优化。生产环境变更前用开发或测试HANA实例执行EXPLAIN PLAN确认执行计划没有出现哈希连接爆内存的算子。6. 最后再分享一点个人体会踩过这么多次坑之后我的感觉是SAP单SQL语句导致Hana内存溢出警告这件事绝大多数时候不是数据库不够强而是“SQL写得不够谦逊”。HANA是内存数据库内存就是它一切性能的根基任何无所顾忌的读法都会被放大成可见的故障。遇到这类问题保持冷静用M_EVENTS确认告警用M_SQL_PLAN_CACHE和Expensive Statement Trace抓到SQL再用ST05反查程序和业务场景最后回到执行计划去理解为什么内存会爆这整套流程下来通常是能在半小时内定下方向的。重点是一开始真的别去动那两个核心内存参数先让现场闭嘴再让开发把SQL改对。
返回列表