ABAP程序效率优化
业务层面的优化
前言
首先说一句,在 HANA 上开发程序也需要效率优化。其次是,提到效率优化的时候,很多人都觉得是开发顾问的事情,和业务层面有什么关系?但是,程序设计来源于业务需求,程序优化不但离不开业务,而且业务优化绝对是第一步!(这里不讨论业务层面优化、代码层面优化分别能给程序带来多少效率上的提升——这也因情况而异,我只是说明程序效率优化应该遵循的步骤)
我的 ABAP 程序效率优化系列,共分三部分:
1、业务层面的优化(全文字,但都是干货)
2、ABAP 代码(内容比较多,可能需要多篇)
3、标准程序优化后的代码分享
背景
企业实施 SAP 系统初期,业务数据量小,系统功能启用的相对较少,ABAP 程序的运行速度还都是闪电般的快。可随着系统应用逐渐深化,启用的功能越来越多,业务数据量越来越大,用户对系统的理解也日益深刻,对系统的开发需求也变的更多……这时候,如果 ABAP 程序结构设计的不好,程序执行效率就非常堪忧了。
策略
首先我们回顾一下在项目中程序开发的工作步骤,大概都是这样的:
需求产生;
业务顾问分析需求;
用户确认需求;
业务顾问根据需求进行功能设计;
开发顾问进行功能开发和基本测试;
业务顾问测试;
用户测试;
需求完成;
对于 SAP 的标准功能开发,甚至是其他软件的功能开发,大致上也是遵循这个工作流程的。而这个流程表述了这样一个概念:所有的功能都是以业务需求为基础的(即便业务需求是潜在需求,功能设计也是以假想需求为基础)。所以功能都是离不开需求的,在进行程序效率优化时,我们首先要进行的就是业务层面的优化。接下来,为了进一步说明这样做的必要性,我们从反面来举几个例子谈谈原因:
例 1:已经有一个开发好的报表,是查询销售订单、生产订单及其相关的成本、收入等信息,之前的查询条件包括销售组织、工厂、物料、销售订单、生产订单等字段。
现在用户提出一个需求,期望增加一个 WBS 字段的查询条件和展示字段。然后业务顾问把需求直接转移到开发顾问(像传话筒一样),开发顾问在代码中 SQL 语句的查询条件中加一行简单的条件。
之后,用户根据 WBS 查询时,一条数据都要执行好半天。
原因分析:并不是所有的销售订单、生产订单都和 WBS 有关系,简单的 SQL 更改可能带来的是灾难式的结果。用户增加一个查询条件和展示字段,这看似简单,但实际上是换了一种模式去查询,用户是希望通过限定 WBS 取满足条件的数据。而这需要开发顾问根据不同的查询条件输入情况来进行代码优化,可是开发顾问对各模块业务上的逻辑可能并不足够熟悉(足够熟悉是业务顾问的职责)
应对策略:
业务顾问:决不能当需求的传话筒,对用户提出的需求,首先要做好这一步——“业务顾问分析需求”。比如对于同一个报表需求,可能要根据不同的查询条件,进行有差别的查询设计,而不是纯粹的把查询条件罗列出来那么简单。
开发顾问:面对业务顾问提出的类似更改需求,不妨多问几个问题,深入的了解变更原因,根据用户的真实需求去做适合的更改。(比如,查询采购订单时,系统中有 ME2L、ME2M、ME2N、ME2J 等多种方式,就是为了满足不同的查询条件,以更高的效率让用户得到更符合需求的查询结果)
例 2:有这样一个报表需求:项目、项目总成本、项目总收入、项目下的生产订单、产品、生产成本、项目下的销售订单、客户、销售订单收入。用户要求在一个报表中展示,并且能汇总统计所有项目的成本和收入。
业务顾问拿到这个需求后,如果一点也不分析,直接转给开发,并且要求做到一个普通的 ALV 中,这实在是难为开发顾问了。即便是做出来,也肯定是没办法让用户愉快的使用的。而且如果在这个基础上再频繁变更需求,最后的结果肯定是谁也看不懂的代码,以及越来越差的性能体验!(用户提出的是在一个报表中,业务顾问粗略的设计完,传递给开发顾问的时候,要求的是明确的报表类型:ALV,普通的 ALV。)
原因分析:
这个报表中,前面三个字段是项目维度,中间三个字段是生产订单维度,后面三个订单是销售订单维度。根本不在一个维度上的数据,能放在一个普通的 ALV 里吗?很明显不能,这种报表,业务顾问只要稍微分析一下,尝试用 Excel 做个样表,就能发现问题了。
应对策略:
业务顾问:决不能当需求的传话筒(重复上面的策略)。面对用户的报表需求,一定要自己尝试做个样表,验证一下用户需求和自己的逻辑。上面举的这个例子,维度区别很明显。还有很多维度比较隐晦的例子,但是经过设计样表的过程之后,就能暴露出来了。
开发顾问:普通的 ALV 确实不能实现这样的需求,但是 ALV TREE 还是勉强可以的。但也仅限于勉强。因为在该例子中,销售订单和生产订单同属于项目维度的子维度,可以在 ALV 树中作为项目节点的子节点。一般情况下,生产订单维度可以作为销售订单维度的子维度。这样可以做一个项目->销售订单->生产订单维度的树状报表。
但是还有两个问题:
1、这样的报表是用户想要的吗?
2、如果生产订单不关联销售订单呢?
所以,问题又回归到业务顾问和用户的需求分析上。
例 3:某客户有个需求,计算每个销售订单上电缆的产品毛利率。为了计算毛利率,需要知道产品的销售收入、原材料的发货成本。
销售收入,在销售模块底表中可以轻松获取。而对于原材料,业务顾问给出的方案是,根据产品递归反推 BOM(BOM 有多层),直至找到原材料的物料号为止。可当一个开发顾问历尽千辛万苦完成程序开发后,却一个多小时执行不出结果,因为反推 BOM 频繁取数据库、以及大量的递归循环的计算过程实在是太耗时了。
之后,我带着开发去找业务顾问了解电缆产品和原材料的关系,了解到电缆产品的原材料只有”铜”和”铝”两个物料号,且只能为其中一个。然后我建议在物料主数据上找一个闲置的字段填原材料,但业务顾问解释,因为种种原因不允许更改物料的标准界面或使用物料的其他字段。
之后,我提出了我的解决方案:
1、建立自定义表,维护产品的原材料属性
2、对于所有存量的销售订单,梳理其销售的产品,确定其原材料属性,作为期初数据维护到自定义表中
3、程序每次执行时,取增量的销售订单上的产品数据,检测其是否维护了原材料属性。若有没维护的则提示维护,若都维护了则记录下增量日期。
而回过头来看,如果我们针对频繁取数据库和递归耗时的 ABAP 代码进行优化,在这样一个业务层面有设计缺陷的功能上进行单纯的代码优化,又能得到什么结果?
原因分析:
原因只有一个:方案做的太死板。开始的方案设计做不好,再多的优化都是徒劳。基础不对,努力白费!
应对策略:
系统是死的,人是活的,方案更应该是活的。
不是所有的需求都要通过标准的方式或逻辑去实现,更不是系统标准功能掌握的滚瓜烂熟,就可以给客户出一个好方案。
“根据需求进行功能设计”,还是需要用心去做。如果性能比较差,就分析性能差在哪里,想办法用”简单粗暴、直奔主题”的方式解决性能瓶颈。
总结:
我们项目中遇到的程序性能问题,在业务层面的表现,大致有以下几种:
1、报表查询维度多,不同的查询维度是完全不同的查询路径
如果大量的查询逻辑和处理代码都是不同的,就应该设计成不同的查询报表。否则将带来极大的运维成本
2、报表展示维度杂乱,用户希望在一个普通 ALV 中展示,导致运算处理逻辑混乱、低效
需求不规范,一般情况下一张普通的报表就应该只有一个维度,不同维度的应该用多个报表或者树状报表展示
3、业务逻辑复杂,取数过程繁琐,造成大量的性能问题
可以根据具体需求,通过增强、自定义表等方式,在不同的业务数据间建立桥梁,简化业务逻辑,简化取数过程。
其实以上三种都有一个共同的处理思路——即,大道至简!
需求和功能之间存在着很重要的一步——需求转化为功能设计。这是一项由业务顾问主导,开发顾问积极参与并提出建议的工作。它需要业务顾问多掌握一些程序设计的理念,也需要团队所有人都对用户需求更多一些业务理解,然后对该拆分的需求进行拆分,对该合并的需求进行合并,对用户不合理的需求进行引导和优化。总之,只有整个团队的紧密配合和互相支持,才能在满足用户需求的基础上做出好的功能设计,这是做出好的开发功能的第一步,也是最重要的一步!之所以说这一步最重要,还是那句话:基础不对,努力白费!
开发优化之读取数据库
重复项-DISTINCT
尽量避免使用 SELECT DISTINCT 语句,使用 ABAP 的 SORT + DELETE ADJACENT DUPLICATES 代替
排序-ORDER BY
使用 ABAP 的 SORT 代替 SQL 中的 ORDER BY,可以降低数据库服务器的负载。
写数据库-INSERT
使用 INSERT dbtab FROM TABLE itab,而不是在 LOOP 中 INSERT INTO dbtab VALUES wa.
读取数据到内表
1、尽量不使用 SELECT *,而是列出索取字段
2、一般写法为 SELECT FIELDS INTO TABLE itab FROM dbtab。如果后续有数据处理,还会用到 LOOP AT itab 的处理。但是如果我们只处理数据一次,比如取出 bseg 的金额,根据 shkzg 对金额进行简单的正负运算后存到内表中(高版本在 OPEN SQL 中提供了处理的语法,请忽略),那么建议使用 SELECT. APPEND. ENDSELECT 的循环处理方式。这样做的效率更高。
嵌套查询、子查询
嵌套查询:SELECT 1 / SELECT 2 / ENDSELECT / ENDSELECT.
子查询:类似于 SELECT WHERE EXISTS ( SELECT WHERE )的方式
子查询优于嵌套查询,也优于 SELECT … INTO TABLE ITAB / SELECT … FOR ALL ENTRIES IN ITAB 的二次查询方式
子查询写法示例:
SELECT * FROM SFLIGHT AS F INTO SFLIGHT_WA
WHERE SEATSOCC < F~SEATSMAX
AND EXISTS ( SELECT * FROM SPFLI
WHERE CARRID = F~CARRID
AND CONNID = F~CONNID
AND CITYFROM = ‘FRANKFURT’
AND CITYTO = ‘NEW YORK’ )
ENDSELECT.
(代码取自 SE30 提示与技巧)
RANGE 和 FOR ALL ENTRIES IN
1、FOR ALL ENTRIES IN itab 使用前,要判断 itab 是否为空
2、一般情况下(法无定法,具体情况具体分析),数据量小(小于 3000),用 for all 更快。数据量大(大于 3000)时,用 range 更快,即便是分多次处理也是 range 更快
(range 条目过多时,OPEN SQL 可能会 dump,需要分多次处理)
查询条件的选择
尽量使用索引字段,包括 KEY 字段、系统提供的索引字段、自己创建的索引字段
使用游标
使用游标的好处是:
1、避免重复打开 SQL 语句,避免重复让数据库为 SQL 语句生成执行计划,节省时间
2、避免一次取出大量的数据,避免程序因内存过大而崩溃
3、降低内表数据量也有助于提升内表循环/嵌套循环的处理速度
使用方法请参见 OPEN CURSOR 的 F1 帮助,或从网上查询使用示例。
索引字段
1、根据需要创建合适的索引字段,索引字段过多对系统也是累赘
2、指定索引字段时,按从左到右的顺序指定。比如表 SFLIGHT,包含主键 CARRID、CONNID、FLDATE。
按从左到右的顺序指定,不是说在 where 条件里要把 CARRID 写在 CONNID 和 FLDATE 的前面,where carrid = ‘’ and connid = ‘’ and fldate = ‘’和 where connid = ‘’ and carrid = ‘’ and fldate = ‘’没有区别。
从左到右的顺序,指的是对于索引中位于最前面的字段,优先限定选择条件。比如,根据 CARRID=AA 和 CONNID=0017 去检索,系统会根据索引检查 AA0017%的数据。如果根据 FLDATE=20051201 检索,系统会根据索引检查**__**20051201 的数据。后者比前者更慢。所以,采用索引字段时,位于左侧的字段尽量不要留空或使用占位符_或使用匹配符%。
运算符
1、对于索引字段,尽量避免使用 NOT、NE,尽量使用肯定表示法。
a) 逻辑非(NOT 运算符)通常用于在检索合适的索引时,防止优化器选择相关的字段。如果因此无法找到合适的检索范围,则确定相应命中列表的处理量可能变得非常高,从而导致运行时间变长。
b) 逻辑非所涉及的不包含在索引中的字段则不会造成该问题。它们仅用于减少命中数量。
c) 如果无法避免对索引字段使用逻辑非(例如,因为所需 IN 列表会变得过大),则您应该指定逻辑非,以减少必须传输的数据量。
3、运算符的速度排序
速度:= > IN > BETWEEN > LIKE > NOT
【但有时 SQL 语句执行时,系统自动分配的先后顺序可能会导致取数更慢。
如:根据 BWART 和 BUDAT_MKPF(建立索引)取 MSEG,如果 BWART 用 IN,BUDAT_MKPF 用 BT,系统可能会先根据 BWART 取数。这时,尽量避免对 BWART 使用 IN,应改为 BT,让 BWART 和 BUDAT_MKPF 处在同一层级的运算符上,让数据库方便优化 SQL 的执行(也可以使用下一条里介绍的%HINTS)】
SQL 语句的人工干预(以 ORACLE 为例)
1、指定 SQL 语句使用表的某个索引
如:
DATA:lt_mseg TYPE TABLE OF mseg.
SELECT * INTO TABLE lt_mseg FROM mseg
WHERE bwart = ‘261’
AND budat_mkpf BETWEEN ‘20170701’ AND ‘20170801’
%HINTS ORACLE’INDEX(“MSEG” “MSEG~Z05”)’.
(此例中,Z05 是我对 MSEG 自己创建的索引,索引字段是 budat_mkpf)
如果我不指定索引,系统可能会优先根据 bwart 去 access 表 mseg(因为=的速度大于 BT 的速度),然后再根据 budat_mkpf 进行 filter,而 261 移动类型的数据是相当多的,这样的 SQL 相当慢,需要我们指定索引人工干预 SQL 的执行。
【注:
a) 我这里说可能会,不是说一定会。ORACLE 生成 SQL 的执行计划时,会根据数据量的大小和以往 SQL 语句执行的统计信息,自动优化代码,但这种自动优化的结果并不一定是最优的,很多时候都需要我们的人工干预。
b) 上面提到了 access 和 filter,这些我们在下一期(ST05 的那些事儿)里重点分享。】
2、对于 For all entries in itab 的优化
首先理解 for all entries in 的机制。
系统中有一个参数(可以用 TCODE:RZ11 查看)叫做 rsdb/prefer_in_itab_opt,当 where 条件只用到 itab 的一个字段时:
a) 如果 prefer_in_itab_opt 为 0,则 for all 语句执行时,对于 itab 中每条数据都转化为 or 的方式,即 field = itab[1]-value or field = itab[2]-value or field = itab[3]-value
b)如果 prefer_in_itab_opt 为 1,则 for all 语句执行时,itab 中每条数据都会转化为 in 的方式,即 field in (itab[1]-value, itab[2]-value, itab[3]-value)。而 in 的速度比 or 快。
系统中还有一个参数叫做 rsdb/max_in_blocking_factor。当 itab 中有 1 万条数据时,可能会被拆解成多个 sql 执行,而 max_in_blocking_factor 控制着 in 运算符能包含的最大的条目数(还有 min_in_blocking_factor 控制最小的条目数,不常用)。
【第二个参数不适用于簇表!!!】
(这两个参数,可以通过 RZ11 设置,也可以用%HINTS 指定,以后者的优先级为最高,且使用后者指定时,不需要写 rsdb/)
用法示例:
SELECT * FROM MSEG
FOR ALL ENTRIES IN itab
WHERE mblnr = itab-mblnr
%HINTS ORACLE ‘&prefer_in_itab_opt 1& &max_in_blocking_factor 500&’
它指定了当只使用 for all entries in itab 中的一个字段时,自动转为 in 的模式,并且每个 SQL 语句的 IN 运算符最多可以包括 500 条数据。如果 itab 有 2300 行,那么这个 SQL 语句在后台会实际执行 5 次。
尽量不在循环中使用 SELECT SINGLE,而使用 FOR ALL ENTRIES IN
依然法无定法,只是说尽量。而且有时使用 select single 还更快。
比如取 BSEG 的数据(不是簇表的版本请忽略),当确定 BSEG 的所有主键时,select into table from bseg for all entries in 要比 loop. select single. append. endloop.的方式花的时间多很多。而当不确定 BSEG 的所有主键时,FOR ALL 执行的时间也比 loop. select appending. endloop.的方式要多一些(差不多太多,但还是 for all 慢)。
不清楚这是不是簇表的原因,有知道的大顾们,欢迎留言告诉我原因。
测试代码(老白的 ABAP 群里的大 S 黄顾问的测试代码)放在百度网盘上了。
链接:https://pan.baidu.com/s/1jJ0VZc6
密码: 3ajk
但对于在稍大一点的循环(比如 1000 条以上)中取公司代码描述、工厂描述、科目描述、人员姓名一类的代码,我们是坚决不能忍的!
其它未尽的手段
除上述纯 ABAP 手段外,还有数据归档、优化表空间、碎片整理、索引重组、硬件扩容等,这些需要业务顾问、BASIS 顾问的介入甚至主导。我不是 BASIS 顾问,就不在此妄言了。但请记住,这些是整个服务器的性能瓶颈凸显后,极为极为重要的优化手段。
https://cloud.tencent.com/developer/news/78193
SD 表读取心得
SD 开发中经常会涉及到以下几张表:
• VBAK – 销售订单头表
• VBAP – 销售订单行项目表
• LIKP – 发货单头表
• LIPS – 发货单行项目表
• VBRK – 会计凭证头表
• VBRP – 会计凭证行项目表
• VBFA – 凭证流表
在读取以上 SD 表时,为提高数据读取效率,应遵循以下几条准则:
通过销售订单查询发货单 (前续凭证):
错误代码:
SELECT FROM lips WHERE vgbel = ... |
正确代码:
SELECT FROM vbfa WHERE VBELV = ... |
通过发货单查询发票凭证(前续凭证)
错误代码:
SELECT FROM vbrp WHERE vgbel = ... |
正确代码:
SELECT FROM vbfa WHERE vbtyp_n = 'M' |
通过销售订单查询发票凭证(前续凭证)
错误代码:
SELECT FROM vbrp WHERE aubel = ... |
正确代码:
SELECT FROM vbfa WHERE vbtyp_n = 'M' |
查询凭证流数据
在凭证流表 VBFA 中,可以通过前续凭证查找后续凭证,但是通过后续凭证查找前续凭证是没有意义的,因为前续凭证都直接保存在凭证表中(lips,vbrp 等)。
VBFA 表的主键:VBELV,POSNV(前续凭证)(HANA 数据库中主键已变为 RUUID)
字段 VBTYP_N 的固定值可以在 domain 中查看:A,B,C,D,E,F;
错误代码:
SELECT vbelv FROM vbfa WHERE vbeln ... |
正确代码:
SELECT vgbel FROM lips WHERE vbeln = ...; or |
通过发货单读取 shipping unit
错误代码:
SELECT FROM vepo WHERE vbtyp = 'J' |
正确代码:
SELECT FROM vbfa WHERE vbtyp_n = 'X' |
开发优化之内表操作
内表操作是 ABAP 程序中除了数据库读取之外,影响性能的第二大因素。在数据量大的场景下,不合理的内表操作可能导致程序运行时间成倍增长。
内表类型的选择
ABAP 提供三种内表类型,选择合适的类型对性能至关重要:
| 类型 | 关键字 | 查找方式 | 适用场景 |
|---|---|---|---|
| 标准表 | STANDARD TABLE | 线性查找 O(n) | 数据量小,无需频繁查找 |
| 排序表 | SORTED TABLE | 二分查找 O(log n) | 需要频繁按key查找,数据有序 |
| 哈希表 | HASHED TABLE | 哈希查找 O(1) | 大量数据,频繁按完整key查找 |
经验法则:
- 当内表超过 100 行且需要频繁 READ TABLE 时,优先使用 SORTED TABLE 或 HASHED TABLE
- 只需要顺序处理(LOOP)而不需要随机查找时,使用 STANDARD TABLE
- 需要按完整 key 精确查找时,HASHED TABLE 最优
- 需要按 key 范围查找或前缀查找时,SORTED TABLE 最优
* 标准表 - 线性查找,数据量大时很慢 |
READ TABLE 的优化
使用二分查找(BINARY SEARCH)
对于 STANDARD TABLE,如果已经排序,可以使用 BINARY SEARCH 将查找从 O(n) 优化到 O(log n):
* 低效写法 - 线性查找 O(n) |
注意: 使用 BINARY SEARCH 前必须先按查找字段排序,否则结果不可靠。
使用 SORTED/HASHED TABLE 替代 BINARY SEARCH
* 最佳实践:直接使用哈希表 |
读取多条记录的优化
* 低效:循环中逐条读取 |
LOOP 的优化
使用 FIELD-SYMBOLS 替代工作区
* 低效:每次循环都复制数据到工作区 |
性能对比: 对于 10 万行数据,FIELD-SYMBOLS 方式通常比 INTO + MODIFY 快 30%-50%。
使用 WHERE 条件过滤
* 低效:在循环体内判断 |
减少嵌套循环
* 低效:嵌套循环 O(n*m) |
避免在循环中修改内表结构
* 低效:频繁 MODIFY |
内表操作的最佳实践
批量操作替代逐行操作
* 低效:逐行 APPEND |
合理使用 COLLECT
* COLLECT 自动按非数值字段汇总,适合分组统计 |
使用 CORRESPONDING 减少字段映射代码
* 低效:逐字段赋值 |
DELETE 与 FILTER 的高效使用
* 删除重复行 |
开发优化之字符串操作
字符串操作在 ABAP 中也常常成为性能瓶颈,尤其是在处理大量文本数据时。
字符串拼接
CONCATENATE vs && vs 模板字符串
* 方式1:CONCATENATE(传统方式,兼容性好) |
性能对比:
- 单次拼接:三者差异不大
- 多次拼接(>5次):模板字符串最优
- 循环中拼接大量字符串:使用 CONCATENATE 或模板字符串
循环中的字符串拼接
* 低效:循环中频繁拼接(每次创建新字符串对象) |
字符串查找
FIND vs CS/NS/CP/NP
* 方式1:FIND(推荐,功能强大) |
使用正则表达式
* 复杂模式匹配使用正则 |
字符串分割
* 低效:循环 + SUBSTRING |
STRING 与 XSTRING 的选择
* 纯文本处理使用 STRING |
开发优化之字段符号与数据引用
字段符号(FIELD-SYMBOLS)的高效使用
避免不必要的数据复制
* 低效:数据复制到工作区 |
动态字段访问
* 动态访问结构字段 |
嵌套表的高效处理
* 处理嵌套内表 |
数据引用(DATA REFERENCE)的使用
动态创建数据对象
DATA: lo_data TYPE REF TO data. |
泛型容器
CLASS lcl_container DEFINITION. |
开发优化之 HANA 数据库
在 SAP HANA 数据库环境下,ABAP 程序的优化策略与传统 Oracle/DB2 有所不同。HANA 是列式存储数据库,擅长聚合计算和大规模数据扫描,但也有一些特殊的优化要点。
计算下沉(Push Down)
HANA 的核心优势在于将计算下沉到数据库层执行,减少应用层与数据库层之间的数据传输。
使用 CDS View 实现计算下沉
* 定义 CDS View,将聚合计算下沉到数据库 |
使用 CDS View Entity (新语法)
define view entity Z_MATERIAL_STOCK |
AMDP (ABAP Managed Database Procedures)
AMDP 允许在 ABAP 中直接编写 HANA 存储过程,适合复杂的计算逻辑。
定义 AMDP 类
CLASS zcl_sales_analytics DEFINITION |
AMDP 的适用场景
- 复杂计算逻辑:多表关联、窗口函数、递归查询
- 大量数据聚合:GROUP BY + 聚合函数
- 存储过程调用:已有 HANA 存储过程需要从 ABAP 调用
- 性能关键路径:CDS View 无法满足的复杂业务逻辑
HANA 特有的 SQL 优化
使用窗口函数
-- 在 CDS View 或 AMDP 中使用窗口函数 |
使用 CE 函数(Calculation Engine)
-- 在 AMDP 中使用 CE 函数获得更好性能 |
利用列式存储特性
* HANA 列式存储对以下操作友好: |
CDS View 性能优化技巧
使用 @Consumption.filter 实现动态过滤
define view entity Z_ORDERS |
避免 CDS View 中的 N+1 问题
* 低效:关联时使用子查询 |
使用 @ObjectModel 实现懒加载
@ObjectModel.representativeKey: 'Material' |
HANA 执行计划分析
使用 ST05 分析 SQL 执行
- 运行 ST05,激活 SQL Trace
- 执行目标程序
- 停止 Trace,查看执行计划
- 关注以下指标:
- Execution Time:SQL 执行时间
- Records:处理的记录数
- Plan Operations:执行计划中的操作类型
常见性能问题及解决
| 问题 | 表现 | 解决方案 |
|---|---|---|
| 全表扫描 | TABLE SCAN 操作 | 添加合适的过滤条件或索引 |
| 数据传输过大 | 返回大量行到应用层 | 使用聚合减少返回数据量 |
| 多次数据库访问 | 循环中执行 SQL | 合并为单次查询或使用 FAE |
| 不必要的列 | SELECT * | 只选择需要的列 |
开发优化之性能分析工具
ABAP 提供了丰富的性能分析工具,帮助开发者定位和解决性能瓶颈。
SE30/SAT - 运行时分析
SE30(新版本为 SAT)是 ABAP 最核心的性能分析工具,可以精确测量程序中每个方法、每条 SQL 的执行时间。
使用步骤
- 启动分析:输入事务码 SE30/SAT
- 配置参数:
- 输入要分析的程序/事务码
- 选择分析类型(统计/详细/限制)
- 设置测量范围(完整程序/特定部分)
- 执行程序:系统自动记录执行数据
- 分析结果:查看热点列表、调用层次、时间分布
关键指标解读
+-----------------------------------------------------------------------+ |
重点关注:
- 时间占比最高的方法/SQL:最大的优化目标
- 事件数过多的循环:可能存在不必要的重复操作
- 数据库访问占比:理想情况应低于 30%
限制分析范围
* 在代码中精确控制分析范围 |
提示与技巧(Tips & Tricks)
SE30 中集成了大量性能优化技巧,按类别组织:
| 类别 | 技巧数量 | 典型内容 |
|---|---|---|
| SQL | 30+ | SELECT优化、索引使用、FAE技巧 |
| 内表 | 20+ | 表类型选择、排序、查找优化 |
| 字符串 | 10+ | 拼接、查找、转换优化 |
| ABAP Objects | 10+ | 对象创建、方法调用、接口使用 |
ST05 - SQL 跟踪
ST05 用于分析程序执行过程中的数据库访问,是优化 SQL 语句的必备工具。
使用步骤
- 激活 Trace:ST05 → Activate Trace
- 执行程序:运行需要分析的程序
- 停止 Trace:ST05 → Deactivate Trace
- 显示 Trace:ST05 → Display Trace
- 分析执行计划:选择 SQL 语句 → Display Execution Plan
关键指标
| 指标 | 说明 | 优化方向 |
|---|---|---|
| Duration | SQL 执行时间 | 减少不必要的查询 |
| Records | 返回/处理的行数 | 缩小查询范围 |
| Operation | 执行计划操作 | 避免 TABLE SCAN,使用 INDEX |
| Buffer | 缓存命中率 | 合理使用表缓冲 |
执行计划解读
OPERATION | OBJECT | COST | ROWS | BYTES | FILTER |
优化建议:
- COST 值越高,优化空间越大
- INDEX RANGE SCAN 优于 TABLE ACCESS FULL
- 减少 NESTED LOOPS 的外层行数
SQL 执行计划图形化分析
ST05 支持图形化显示执行计划,直观展示:
- 数据流向
- 各节点耗时
- 索引使用情况
SQLM - SQL Monitor
SQLM 用于监控生产环境中 SQL 语句的执行情况,适合长期性能监控。
配置步骤
- 启动监控:SQLM → Start Monitoring
- 设置过滤:指定用户、程序、时间范围
- 收集数据:系统持续记录 SQL 执行
- 分析结果:SQLM → Display Monitoring Data
监控结果分析
+--------------------------------------------------------------------+ |
优化优先级:
- 总时间最高的 SQL 语句优先优化
- 执行次数过多的 SQL 考虑合并或缓存
- 平均时间过长的 SQL 需要检查执行计划
SCI - 代码检查器
SCI(Code Inspector)用于静态代码分析,可以在开发阶段发现潜在的性能问题。
检查类别
| 类别 | 说明 | 典型问题 |
|---|---|---|
| Performance | 性能相关 | SELECT *、循环内SQL、缺少索引 |
| Security | 安全相关 | SQL注入、权限检查 |
| Robustness | 健壮性 | 空指针、异常处理 |
| Search Functions | 搜索功能 | 废弃语法、兼容性问题 |
自定义检查变式
检查变式: Z_PERF_CHECK |
与 ATC 集成
ATC(ABAP Test Cockpit)是 SCI 的增强版,集成在开发流程中:
- 传输请求发布时自动检查
- Eclipse ADT 中实时检查
- 持续集成(CI)中自动运行
代码审查 Checkpoint
在性能优化中,以下检查点应作为代码审查的标准项:
数据库访问
- 避免 SELECT *
- 避免在循环中执行 SELECT
- FOR ALL ENTRIES IN 前检查内表是否为空
- 使用合适的索引字段
- 子查询优于嵌套查询
内表操作
- 选择合适的内表类型(STANDARD/SORTED/HASHED)
- READ TABLE 使用 BINARY SEARCH 或 SORTED/HASHED 表
- LOOP 中使用 FIELD-SYMBOLS
- 批量操作替代逐行操作
- 避免不必要的内表复制
字符串操作
- 多次拼接使用模板字符串
- 循环中拼接先收集再合并
- 使用 SPLIT 替代手动解析
算法设计
- 避免不必要的嵌套循环
- 使用哈希表优化查找
- 减少数据库与应用层的数据传输
- 考虑计算下沉(CDS/AMDP)
开发优化之内存管理
ABAP 程序的内存管理对性能有直接影响,尤其是在处理大量数据时。
内存分配原则
ABAP 内存架构
+---------------------------+ |
内存限制
- 单个内表:最大 2GB(理论值)
- 单个工作进程:默认 2GB,可通过参数调整
- 扩展内存:通过 ztta/roll_extension 参数配置
内表内存优化
使用 COMPACT 内表
* 标准内表 - 每行有对齐填充 |
合理使用 HEADER LINE
* 旧语法(不推荐) |
及时释放内存
* 处理完大内表后及时释放 |
字符串内存优化
STRING vs C 类型
* STRING - 动态分配,适合变长文本 |
避免不必要的字符串复制
* 低效:创建多个临时字符串 |
对象内存管理
对象池模式
CLASS lcl_object_pool DEFINITION. |
避免循环引用
* 低效:循环引用导致内存泄漏 |
大数据量处理策略
分批处理
* 分批读取大数据量 |
使用临时表
* 使用全局临时表(GTT)减少内存压力 |
开发优化之并行处理
并行处理可以充分利用多核 CPU 资源,显著提升程序执行效率。
异步 RFC 调用
基本用法
* 定义 RFC 函数模块 |
接收结果
* 在回调函数中接收结果 |
处理资源限制
控制并发数
* 使用 RFC 资源限制 |
监控并行任务
* 检查任务状态 |
并行处理的最佳实践
适用场景
| 场景 | 适用性 | 说明 |
|---|---|---|
| 大数据量查询 | ✓ 优秀 | 按范围拆分并行查询 |
| 批量数据处理 | ✓ 优秀 | 独立数据块并行处理 |
| 复杂计算 | ✓ 良好 | CPU密集型任务 |
| 事务性操作 | ✗ 不适合 | 需要数据一致性 |
| 有依赖的操作 | ✗ 不适合 | 步骤间有依赖关系 |
注意事项
- 数据一致性:并行处理的数据必须相互独立
- 资源控制:避免创建过多并行任务导致资源耗尽
- 错误处理:每个并行任务都需要独立的错误处理
- 结果合并:考虑结果合并的性能开销
- 调试困难:并行程序调试复杂,建议先串行验证逻辑
开发优化之实战案例
案例一:销售报表优化
问题描述
原始程序执行时间超过 30 分钟,用户无法接受。
性能分析
使用 SE30 分析发现:
- 70% 时间在数据库访问
- 20% 时间在内表循环
- 10% 时间在数据输出
优化方案
* 优化前:循环中多次查询 |
优化结果
- 执行时间:30 分钟 → 45 秒
- 数据库访问:500 次 → 3 次
- 内存使用:减少 60%
案例二:物料主数据批量处理
问题描述
批量更新 10 万条物料数据,程序运行缓慢。
优化方案
* 使用并行处理 |
优化结果
- 执行时间:2 小时 → 25 分钟
- 系统资源利用:单核 100% → 多核 80%
案例三:库存查询报表
问题描述
查询特定时间段的库存变动,数据量约 500 万条。
优化方案
* 使用 CDS View + 参数化查询 |
优化结果
- 执行时间:15 分钟 → 8 秒
- 数据传输量:减少 95%
- 应用层处理:减少 90%
开发优化之 Checklist
开发前检查
需求分析
- 明确数据量级(千/万/百万)
- 确定查询条件和索引字段
- 评估是否需要并行处理
- 确认报表展示维度是否统一
技术选型
- 选择合适的技术栈(ALV/CDS/AMDP)
- 确定内表类型
- 评估是否需要分批处理
- 确认数据库特性(HANA/Oracle)
开发中检查
数据库访问
- 避免 SELECT *
- 使用合适的索引
- FOR ALL ENTRIES IN 前检查内表
- 避免循环中执行 SQL
- 使用子查询替代嵌套查询
内表操作
- 选择合适的内表类型
- READ TABLE 使用 BINARY SEARCH
- 使用 FIELD-SYMBOLS
- 批量操作替代逐行操作
- 避免不必要的数据复制
字符串操作
- 使用模板字符串
- 循环中先收集再合并
- 使用 SPLIT 替代手动解析
算法设计
- 避免不必要的嵌套循环
- 使用哈希表优化查找
- 减少数据传输量
- 考虑计算下沉
开发后检查
性能测试
- 使用 SE30 分析执行时间
- 使用 ST05 分析 SQL 执行
- 测试大数据量场景
- 验证内存使用情况
代码审查
- 通过 SCI 检查
- 同行代码评审
- 文档更新
性能基线
| 数据量 | 目标响应时间 | 技术建议 |
|---|---|---|
| < 1,000 | < 1 秒 | 标准 ALV |
| 1,000 - 10,000 | < 5 秒 | 优化 SQL + 哈希表 |
| 10,000 - 100,000 | < 30 秒 | 分批处理 + CDS |
| 100,000 - 1,000,000 | < 2 分钟 | 并行处理 + AMDP |
| > 1,000,000 | < 5 分钟 | HANA 优化 + 分页 |
总结
ABAP 程序效率优化是一个系统工程,需要从多个维度综合考虑:
优化层次
+---------------------------+ |
核心原则
- 业务先行:方案设计比代码优化更重要
- 数据库为王:80% 的性能问题源于数据库访问
- 用对工具:善用 SE30/ST05/SCI 等分析工具
- 持续优化:性能优化是持续的过程,不是一次性的任务
学习路径
- 入门:掌握基本的 SQL 优化和内表操作
- 进阶:学习 HANA 特性和 CDS/AMDP
- 高级:掌握并行处理和性能分析工具
- 专家:能独立完成复杂系统的性能优化
记住:基础不对,努力白费。在开始优化之前,先确保方案设计是合理的。然后从数据库访问、内表操作、算法设计三个维度逐步优化,最终达到理想的性能目标。








