☰
ABAP慢SQL定位到HANA执行计划优化的实战指南
2026/10/9 1:39:19 网站建设 项目流程

生产系统上飘过一个红色报警,SAP 事务代码 ST05 里拉出一段 SQL,执行时间 4 万毫秒,数据库端 CPU 直接顶满。这种场景做过 ABAP 开发的多少都见过,尤其是系统跑在 HANA 上之后,逻辑下推到数据库,一条烂 SQL 能把整层内存数据库拖下水。很多人一上来就盯着 HANA 的 SQL 语句改来改去,结果发现 ABAP 程序里明明只写了几行 Open SQL,HANA 侧却生成了一坨几百行的执行计划——这就是典型的“ABAP 逻辑层”和“HANA 物理执行层”脱节。

这篇文章想聊的就是怎么把慢 SQL 从 ABAP 环境里系统性“捞”出来。所谓捞,不是靠运气翻代码,而是靠工具链一层一层定位:先用 ABAP 侧的工具找出是哪条语句在拖时间,再切到 HANA 侧执行计划确认瓶颈算子,最后回到 ABAP 代码层面做针对性改造。这条路走顺了,一条 SQL 从定位到优化往往只需要半天。内容适合正在做 S/4 HANA 迁移的 ABAP 开发、负责性能优化的 BASIS 顾问,以及刚接触 HANA 执行计划但被一堆操作符搞晕的人。

1. 先搞清楚一条慢 SQL 到底慢在哪一层

很多新手拿到一条慢 SQL 第一反应就是复制到 HANA Studio 里跑一遍,看执行时间多少,然后开始调索引。这个思路不能说错,但漏掉了 ABAP 环境里最关键的一层:Open SQL 并不是最终发给 HANA 的语句,它只是逻辑描述。SAP 应用服务器会把 Open SQL 转换成数据库原生 SQL,加上一堆隐式条件、权限过滤、客户端过滤,再交给 HANA 执行。所以你在 ABAP 里看似简单的 SELECT,落到 HANA 侧可能就是十几个算子的复杂执行计划。

这也是为什么我一直强调:分析慢 SQL 必须两条腿走路,一条在 ABAP 层,一条在 HANA 层。

1.1 ABAP 层能看到什么

ABAP 层能看到的工具大致有这么几类:ST05(SQL 追踪)、SAT(ABAP SQL 追踪增强版)、SE30/SE80 运行时分析、STAD 与 ST03N 的统计记录。ST05 是老牌工具,适合在开发或质量系统里针对某个事务代码做定向追踪。操作方法不复杂,进入 ST05 后激活“SQL Trace”,然后在另一个会话执行目标事务,结束后回到 ST05 停止追踪,列表里会按时间顺序显示所有数据库请求。

这个列表最值得看的不是每条 SQL 的执行时间,而是总等待时间占比和调用次数。我见过很多报表慢,不是单条 SQL 慢,而是同一条 SQL 被循环调用了上千次,单次 10 毫秒,累计却要十几秒。ST05 里能看到每次调用的时间戳和调用栈,如果发现大量重复的 SELECT 且记录数很小,那基本可以断定是 ABAP 代码里出现了 N+1 查询问题——即主循环里嵌套了子查询。这就是典型的“程序写得烂”而不是“数据库调得差”。

另一个容易被忽略的点是 SAT 里的“DB 请求时间”和“CPU 时间”两个字段。前者是数据库端实际处理时长,后者是应用服务器解析 Open SQL、传输数据包消耗的 CPU。如果 CPU 时间远大于 DB 请求时间,说明瓶颈在应用层,可能是 SELECT 把整表数据拉到 ABAP 内存再循环过滤,此时去 HANA 调索引毫无意义,应该先把 ABAP 层的取数逻辑改掉,让数据库做过滤。

1.2 HANA 层能看到执行计划

ABAP 层定位到具体语句后,就要到 HANA 侧验证这条语句的真实执行行为。HANA Studio 里打开一个 SQL 控制台,执行EXPLAIN PLAN FOR SELECT ...,然后看执行计划树。这里的核心概念是“操作符”。每个执行计划都是由操作符组成的树状结构,常见的包括 COLUMN SEARCH(列搜索)、JOIN(连接)、AGGR(聚合)、PROJECT(投影)、TABLE SCAN(表扫描)等。

需要特别留意:EXPLAIN PLAN 给出的是估算代价,不是实际执行代价。生产环境的慢 SQL 分析,如果条件允许,应该用 HANA 的 PlanViz 真实执行跟踪(CREATE PLAN配合ALTER SYSTEM开启 Plan Trace)来看实际操作符的实际耗时。两者的差别很大:估算代价基于统计信息,而统计信息可能过时;实际跟踪则告诉你到底哪一步花了时间。这就是“捞 SQL”的核心动作——捞到的不只是 SQL 文本,还要捞到它的执行计划。

2. HANA 执行计划怎么看才不迷路

看 HANA 的执行计划,第一眼通常会被操作符树吓到,满屏的箭头、节点、数字。其实有一个很实用的观察顺序:先看操作符右侧的 Duration(耗时)占比,再看 Records(记录数)变化,最后看 Data Volume(数据量)。这三个字段能回答最关键的三个问题:时间花在哪、数据量在哪膨胀、哪里存在意外的记录数放大。

2.1 先盯 Duration 最长的节点

执行计划里每一个操作符都有它自己的耗时,通常以毫秒为单位。整个 SQL 的耗时不是简单相加,而是取关键路径上的累计值。我习惯的做法是先把每个节点的耗时按降序排,找出最长的那个节点,这个节点往往就是瓶颈。然后看这个节点的类型:如果是 JOIN,而且其中一个输入源是几百行的小结果集,另一个是大表,那八成是 ABAP 侧给了错误连接顺序,或者 HANA 优化器在动态剪枝时没有选到最佳计划;如果是 COLUMN SEARCH 里嵌了一个巨大的 IN 列表,那基本上是 FOR ALL ENTRIES 生成的条件太多了。

举个例子,我曾经处理过一个物料账报表,ST05 里抓到一条 SQL 要跑 30 秒。HANA 执行计划显示最耗时的节点是 TABLE SCAN,扫描了一张几千万行的凭证表,但最终只返回 200 行。原因很直接:WHERE 条件里对日期字段用了TO_DATS转换函数,导致 HANA 无法使用范围过滤,只能把整列数据全部读出来再做函数计算。这种问题在 SQL Server 上叫“索引失效”,在 HANA 上虽然列存储不像行存储那样对索引敏感,但函数包裹字段依然会导致无法做谓词下推和分区裁剪,扫描量成倍增加。改法也很简单,把转换逻辑放到常量一侧,或者直接改用 HANA 原生日期类型,执行时间从 30 秒降到 1.5 秒。

2.2 Records 爆炸与 Data Volume 剧增的陷阱

另一个常见问题是记录数在中间节点突然爆炸。比如一个 JOIN 操作符的输出记录数是输入记录数的几十倍,这通常意味着连接条件存在一对多匹配,ABAP 代码里又没有加 DISTINCT 或去重逻辑。此时就算执行时间短,后续节点也可能因为数据量膨胀而变慢,尤其是 AGGR 或 SORT 节点。

HANA 里处理这类问题的思路有两个:一是修改 SQL,在多表关联前先用子查询把明细结果集压缩,用小结果集再去连接主表;二是利用 HANA 的 SQL 窗口函数做去重,比如ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...),这比 GROUP BY 后取任意值更可控。ABAP 开发者可能对窗口函数不熟,但 HANA 完全支持标准 SQL 语法,在 AMDP 里写起来也没有障碍。这种优化思路在 MySQL、SQL Server 上也同样成立,底层原理都是减少中间结果集规模。

3. 实操:把一条 ABAP 慢 SQL 从定位到改造走完

纸上谈兵没有意义,下面把我常用的完整流程拆开讲,每一步都能直接照着做。假设场景是:生产系统有一套库存报表,用户在 SAP GUI 里点执行要等三分钟,SAP 支持人员已经通过 STAD 定位到程序 ZINV_REPORT,接下来要做的是找出具体是哪一条 SQL、为什么慢、怎么改。

3.1 第一步:用 SAT 做定向追踪,抓出真正的 SQL

SAT 是 ST05 的替代品,S/4 HANA 上推荐优先使用。进入事务码 SAT,创建一个变式:勾选“SQL trace”,输入程序名 ZINV_REPORT,指定追踪深度为“全部 SQL 语句”。然后让用户在测试环境重跑一次报表,结束后退出追踪。

SAT 结果界面会按执行时间排序,最上面那条往往就是元凶。这里有几个字段要对照看:SQL 语句、执行次数、总时间、最大单次时间、DB 记录数。如果你发现最大单次时间只有 200 毫秒,但执行次数是 5000 次,那结论就变成了“调用次数过多”,要去查 ABAP 代码里是不是在 LOOP 里写了 SELECT。如果你发现单次执行 25 秒,那就要进入下一步——看它生成的 HANA 原生 SQL。

在 SAT 结果里选中这条 SQL,点查看详情,可以看到 ABAP 层显示的逻辑 SQL。但这里有个关键动作:不能直接复制这个逻辑 SQL 去 HANA 里跑,因为 Open SQL 中间层可能会在运行时补充条件。你需要通过 SAT 的“技术信息”或直接在 HANA 侧查计划缓存,找到真正发送到数据库的物理 SQL。这也是很多新手第一步就走偏的地方。

3.2 第二步:在 HANA Plan Cache 里捞真实 SQL 与执行计划

生产系统不建议直接在 ABAP 程序运行时打开 Plan Trace,毕竟会影响性能。更稳妥的做法是用 HANA 的系统视图M_SQL_PLAN_CACHE查找已经被数据库缓存的真实 SQL 语句。SQL 大概这样写:

SELECT STATEMENT_STRING, EXECUTION_COUNT, MAX_EXECUTION_TIME, TOTAL_EXECUTION_TIME, LAST_EXECUTION_TIMESTAMP FROM SYS.M_SQL_PLAN_CACHE WHERE STATEMENT_STRING LIKE '%ZINV_REPORT%' OR STATEMENT_STRING LIKE '%MSEG%' ORDER BY MAX_EXECUTION_TIME DESC LIMIT 20;

注意 LIKE 匹配条件要尽量选程序专属的关键词或主表名,避免把系统里所有类似的 SQL 全捞出来。如果查出来的 SQL 文本很长,可以直接查看PLAN_ID,然后用 HANA 的EXPLAIN PLAN或 PlanViz 加载对应的执行计划。这里看执行计划时我强烈建议把显示模式切到“Operator Details”,把每个操作符的 Records 和 Duration 都展开,不要只看概览图。

3.3 第三步:结合执行计划给出 ABAP 侧改造方案

一旦在 HANA 执行计划里找到了瓶颈算子,改造方向就清晰了。下面用一个最典型的例子说明。

假设执行计划显示:瓶颈是TABLE SCAN MSEG,扫描了 2000 万行,但只返回 3000 行。WHERE 条件是:

SELECT * FROM mseg WHERE mblnr = lv_mblnr AND mjahr = lv_mjahr AND werks IN s_werks.

这个写法本身没问题,但如果s_werks范围值特别多,Open SQL 会展开成一个大 IN 列表,加上凭证号、年份、工厂三个字段的组合区分度不够,HANA 优化器可能选择全表扫描。此时两个改造方向:

一是把 ABAP 层的取数裁剪得更狠。不要在 SELECT * 之后再通过 LOOP 过滤,而是先确定最终需要的字段,只 SELECT 必要的列。列存储数据库对列裁剪极其敏感,查询 5 列和查询 50 列的性能差距可能有三到五倍。代码改成明确列出字段列表,并加上UP TO 1000 ROWS这类守规矩的限制(如果业务允许)。

二是利用 HANA 的分区裁剪。如果 MSEG 表按MJAHR做了范围分区,WHERE 里带上年度字段后,优化器可以直接跳过非相关分区。这在执行计划里会表现为TABLE SCAN的操作符节点下方出现PARTITION信息。如果 ABAP 代码里因为某些原因没有传年度,HANA 就只能扫全表,此时要在程序入口校验年度参数是否必输,从源头避免这种情况。

3.4 第四步:用 AMDP 做复杂逻辑下推

ABAP 到 HANA 之后,遇到非常复杂的取数逻辑,纯 Open SQL 会生成冗长、难以优化的 SQL。如果上面两步改造后依然不理想,可以考虑把部分逻辑用 AMDP(ABAP Managed Database Procedures)写成数据库过程,在 HANA 侧直接执行。

AMDP 的本质是让你在 ABAP 类的方法里写原生 SQLScript,直接操作 HANA 表。它的好处是逻辑下推彻底,不再经过 Open SQL 的语义转换,执行计划更可控。举个例子:以前在 ABAP 里先取主表,再 LOOP 取子表,最后合并成内表;改成 AMDP 后,三张表直接在 HANA 里做 JOIN、聚合,一次返回最终结果集。数据从千万级压缩到几百行才传输到应用服务器,网络开销和内存占用都小得多。代价是调试不如 ABAP 方便,所以我的原则是“能用 SQL 解决的尽量不改 AMDP,Open SQL 优化不了再上 AMDP”。

4. 常见问题与排查技巧实录

慢 SQL 排查这个事,做得越多越会发现,真正难的不是看执行计划,而是在一堆表象里找到真正的原因。下面这些坑都是我在项目里实际踩过的,列成速查表,再补几个独门技巧。

4.1 FOR ALL ENTRIES 的隐藏全表扫描

ABAP 开发都知道FOR ALL ENTRIES可以用来替代嵌套 SELECT,但它有一个非常经典的坑:如果驱动内表为空,Open SQL 会直接返回全表数据,没有任何 WHERE 限制。这在 HANA 上尤其恐怖,等于一次全表扫描,把几千万行全部读回应用服务器,CPU、内存、网络全部被打满,报表直接卡死。

正确的写法是在调用 FOR ALL ENTRIES 之前显式判断内表是否为空,为空就直接 RETURN,不给 SQL 执行机会。另一个与 FOR ALL ENTRIES 相关的坑是它生成的 IN 列表有长度限制,条件过多时 HANA 报错,或执行计划里出现巨大的 IN 列表导致优化器不知道该走哪个索引。我的习惯是分批处理:把驱动内表按 1000 行一批切分,循环批次查询,再把结果 APPEND 到总内表,实测稳定性和性能都更好。

4.2 字段类型 LCHR 导致的 WHERE 条件失效

HANA 迁移项目里经常有人问:CDS 视图里明明定义了某个字段,但 WHERE 条件里一用就报错,提示“column cannot be used in SQL due to its type”。这通常发生在 LCHR 类型字段上。LCHR 是 ABAP 字典里的长字符串类型,在 HANA 里会被映射为特殊的二进制大对象存储,不能直接在 SQL 的 WHERE 条件里作为普通过滤字段参与比较运算。

遇到这种情况,处理方式有两个:一是在 CDS 视图里用cast(substring(...))之类的函数先把 LCHR 转换成普通字符串类型,再做过滤;二是干脆不在数据库层筛这个字段,把数据先取到 ABAP 层再做字符串处理。前者性能更好但实施复杂,后者代码改得快但数据量大时不推荐。这里没有银弹,必须具体问题具体分析。

4.3 执行计划看着正常但就是慢

偶尔会遇到一种诡异情况:EXPLAIN PLAN 出来的执行计划很漂亮,索引也有,扫描行数也很少,但运行时就是慢。大概率原因有两个:一是统计信息过期,HANA 优化器用了过时的行数估算,选择的 JOIN 策略不是最优的;二是列式存储的 Delta Merge 没有及时执行,导致读路径在 L1 Delta 和 Main 之间切换,读取放大明显。

针对统计信息问题,可以执行ALTER SYSTEM UPDATE TABLE STATISTICS对指定表刷新统计信息,注意要在业务低峰期做。针对 Delta Merge 问题,可以查M_TABLE_PERSISTENCE_STATISTICS里的 Main 和 Delta 占比,如果 Delta 行数占比过高,触发一次MERGE DELTA OF TABLE或等待后台合并任务执行完毕。这属于数据库运维层面的动作,ABAP 开发者需要和 BASIS 或 HANA 管理员配合。

4.4 安全侧提醒:原生 SQL 必须防注入

捞 SQL、改 SQL 的过程中,如果直接在 ABAP 里使用 Native SQL,一定要用参数化方式拼接语句,不要用字符串直接把用户输入拼进 SQL。ABAP 的 Open SQL 自带参数绑定,风险低;但一旦写了EXEC SQL原生 SQL,就相当于把安全边界交给了开发者。历史上出过不少恶意输入导致的数据越权事件,本质都是拼接不规范。基本原则:凡是用户输入,一律通过占位符传入,绝不做字符串拼接。这不是能不能跑的问题,而是最基本的职业底线。

5. 一点实操体会

SQL 性能分析这件事,工具学起来是快的,考验人的是思路和耐心。我在实操中最深的一点体会是:不要被一条 SQL 的表面耗时迷惑,先分清瓶颈在哪一层,再动手改。有时候 ABAP 层只要加一个内表排序、少一次循环调用,比在 HANA 里调半天索引有效得多;有时候 HANA 侧一个分区设置就能让查询量级式下降,ABAP 代码一行都不用动。

另外,排查慢 SQL 一定要养成“先留下证据,再优化”的习惯。优化前用 SAT 和 HANA Plan Cache 记录基线数据,优化后改完跑一遍同样的追踪做对比,把前后执行时间、扫描行数、CPU 消耗留档。这个习惯在面试、项目汇报甚至后期性能复盘时都极有价值——你拿出来的不是“我觉得快了”,而是一组可以追溯的对比数据。

过程中如果遇到某条 SQL 的执行计划实在看不懂,别硬啃,把最耗时的算子截个图,把表名和条件摘出来,回到 ABAP 代码里看看这段数据是干嘛用的。很多时候,真正的问题藏在业务逻辑里,而非数据库技术本身。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询