生产系统上飘过一个红色报警SAP 事务代码 ST05 里拉出一段 SQL执行时间 4 万毫秒数据库端 CPU 直接顶满。这种场景做过 ABAP 开发的多少都见过尤其是系统跑在 HANA 上之后逻辑下推到数据库一条烂 SQL 能把整层内存数据库拖下水。很多人一上来就盯着 HANA 的 SQL 语句改来改去结果发现 ABAP 程序里明明只写了几行 Open SQLHANA 侧却生成了一坨几百行的执行计划——这就是典型的“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 层能看到的工具大致有这么几类ST05SQL 追踪、SATABAP SQL 追踪增强版、SE30/SE80 运行时分析、STAD 与 ST03N 的统计记录。ST05 是老牌工具适合在开发或质量系统里针对某个事务代码做定向追踪。操作方法不复杂进入 ST05 后激活“SQL Trace”然后在另一个会话执行目标事务结束后回到 ST05 停止追踪列表里会按时间顺序显示所有数据库请求。这个列表最值得看的不是每条 SQL 的执行时间而是总等待时间占比和调用次数。我见过很多报表慢不是单条 SQL 慢而是同一条 SQL 被循环调用了上千次单次 10 毫秒累计却要十几秒。ST05 里能看到每次调用的时间戳和调用栈如果发现大量重复的 SELECT 且记录数很小那基本可以断定是 ABAP 代码里出现了 N1 查询问题——即主循环里嵌套了子查询。这就是典型的“程序写得烂”而不是“数据库调得差”。另一个容易被忽略的点是 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 做定向追踪抓出真正的 SQLSAT 是 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。如果上面两步改造后依然不理想可以考虑把部分逻辑用 AMDPABAP Managed Database Procedures写成数据库过程在 HANA 侧直接执行。AMDP 的本质是让你在 ABAP 类的方法里写原生 SQLScript直接操作 HANA 表。它的好处是逻辑下推彻底不再经过 Open SQL 的语义转换执行计划更可控。举个例子以前在 ABAP 里先取主表再 LOOP 取子表最后合并成内表改成 AMDP 后三张表直接在 HANA 里做 JOIN、聚合一次返回最终结果集。数据从千万级压缩到几百行才传输到应用服务器网络开销和内存占用都小得多。代价是调试不如 ABAP 方便所以我的原则是“能用 SQL 解决的尽量不改 AMDPOpen 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 代码里看看这段数据是干嘛用的。很多时候真正的问题藏在业务逻辑里而非数据库技术本身。