SAP ABAP 性能优化全攻略:理论+实战案例_abap中 表 2000万的数据, 取200条 数据都特别慢
SAP ABAP 性能优化全攻略:理论+实战案例
目录
-
优化的基本原则
-
性能影响因素
-
数据库层优化
-
ABAP程序层优化
-
业务调优层
-
调用方式优化
-
监控与分析工具
-
实战案例:FCC 财务成本控制优化
一、优化的基本原则
在任何性能优化中,有三个核心目标:
-
减少 I/O 操作 —— 尽量减少数据库交互次数,降低网络传输量。
-
减少 CPU 操作 —— 减少无意义的循环、重复计算与数据复制。
-
减少内存占用 —— 合理管理内存,避免无用数据长期驻留。
1. 减少 I/O 操作
-
批量读取替代单条读取:一次取完数据,不要在循环中多次访问数据库。
-
靠近数据库处理逻辑:使用 CDS View、AMDP 等手段,将复杂逻辑尽量下推到数据库执行。
-
索引优化:使用主键或二级索引减少扫描。
2. 减少 CPU 操作
-
避免嵌套循环与重复判断。
-
善用 SORTED TABLE 和 HASHED TABLE 提高查找效率(O(log N) 或 O(1))。
-
使用批量修改(
MODIFY ... WHERE
)代替循环内单条修改。
3. 减少内存占用
-
清理不再使用的内表与对象(
FREE
/CLEAR
)。 -
静态变量尽量少用,因为它们在程序加载时就会占用内存。
-
网络传输数据量尽量精简,避免一次传输大量无用列和字段。
-
示例(释放内表和减少传输):
END-OF-SELECTION. FREE lt_data. \"释放内表占用的内存 \" RFC 传输时只取必要字段 SELECT vbeln erdat FROM vbak INTO TABLE lt_vbak PACKAGE SIZE 50.
二、影响性能的主要因素
-
硬件与网络带宽
应用服务器、数据库服务器的 CPU/内存/磁盘性能,以及网络带宽,都会直接影响 ABAP 程序的执行速度。 -
SAP 服务器参数
内存参数(如abap/buffersize
)、工作进程数、缓存设置等需要系统管理员调整。 -
数据库索引设计
-
缺少必要索引 → 全表扫描 → 性能差
-
过多无效索引 → 更新慢,占用空间
-
注意:簇表(Cluster Table)不能创建二级索引。
-
-
数据量规模
性能曲线:-
执行时间随数据量增加对数级增长 → 性能可接受
-
执行时间随数据量指数级增长 → 必须优化
-
三、数据库层优化
数据库是 ABAP 性能优化的第一道防线,绝大多数瓶颈出现在 SQL 执行阶段。
3.1 使用索引
-
按照索引顺序写 WHERE 条件(从左到右匹配)
-
优先用
EQ
、NE
替代=
、 -
示例(FOR ALL ENTRIES 提升性能):
SELECT belnr gjahr FROM bkpf INTO TABLE lt_bkpf WHERE gjahr = \'2025\'.IF lt_bkpf IS NOT INITIAL. SELECT * FROM bseg INTO TABLE lt_bseg FOR ALL ENTRIES IN lt_bkpf WHERE belnr = lt_bkpf-belnr AND gjahr = lt_bkpf-gjahr.ENDIF.
-
注意:
-
检查内表非空 (
CHECK lt_bkpf IS NOT INITIAL
) -
内表需去重、排序
-
内表字段类型必须与数据库字段一致
3.2 避免全表扫描
-
不要
SELECT *
,只取必要字段 -
将多次
SELECT SINGLE
改为一次批量SELECT
-
避免 LOOP 中
SELECT
,改为一次性取全表 -
反例:
LOOP AT lt_cntry. SELECT SINGLE * FROM zfligh INTO ls_fligh WHERE cntry = lt_cntry-cntry. APPEND ls_fligh TO lt_result.ENDLOOP.
优化后:
SELECT * FROM zfligh INTO TABLE lt_result FOR ALL ENTRIES IN lt_cntry WHERE cntry = lt_cntry-cntry.
3.3 使用聚合函数
-
用
SUM()
/COUNT()
代替在 ABAP 中累加 -
数据库执行聚合远快于应用服务器循环累加
SELECT COUNT(*) INTO @DATA(lv_count) FROM vbak WHERE bukrs = \'1000\'. SELECT SUM( netwr ) INTO @DATA(lv_netwr) FROM vbak WHERE bukrs = \'1000\'.
3.4 使用 CDS View / AMDP
-
将复杂逻辑尽量下推到数据库执行
3.5 SORT 代替 ORDER BY
SORT it_vbak BY erdat.
3.6 避免 SELECT DISTINCT
SORT lt_bseg BY belnr gjahr. DELETE ADJACENT DUPLICATES FROM lt_bseg COMPARING belnr gjahr.
3.7 大数据量分段查询
-
使用
PACKAGE SIZE n
分批读取,减少内存峰值 -
select * into table itab pacakage size 100000 from zccwpa insert zcpaf1000 from table itab.endselect.\" select * into table itab package size n 必须与endselect结尾,每次循环都将重新更新内表itab;
-
或使用游标(CURSOR)逐批处理
DATA:S_CURR TYPE cursor.open cursor with hold s_curr for select * from db1.do. fetch next cursor s_curr into table itab package size 10000. if sy-subrc eq 0. do something. else. exit. endif.enddo.
3.8 避免动态 SQL
-
动态表名、字段名尽量用宏或子程序
3.9 大量内表插入数据库
SELECT * INTO TABLE itab PACKAGE SIZE 100000 FROM zccwpa. INSERT zcpaf1000 FROM TABLE itab.
四、ABAP程序层优化
4.1 减少 LOOP,使用 HASHED / SORTED TABLE
选择合适的内表类型
-
STANDARD TABLE(默认):适合追加数据,查找性能 O(n),不适合大数据频繁查找。
-
SORTED TABLE:自动排序,二分查找 O(log n),适合范围查询。
-
HASHED TABLE:哈希查找 O(1),适合按键值快速查找。
示例(用 HASHED TABLE 替代 LOOP 检索):
DATA: lt_data TYPE HASHED TABLE OF ztable WITH UNIQUE KEY kunnr, ls_data TYPE ztable.SELECT * FROM ztable INTO TABLE lt_data.READ TABLE lt_data WITH TABLE KEY kunnr = \'10000001\' INTO ls_data.IF sy-subrc = 0. WRITE: / \'找到客户\', ls_data-kunnr, ls_data-name1.ENDIF.
注意:使用哈希表必须注意键值的唯一性!如果键值会出现重复的话,不能使用哈希表,只能用排序表和标准表
4.2 避免不必要的数据复制
ASSIGN itab TO .
4.3 批量修改/新增内表
\" 不推荐 LOOP AT itab. IF flag IS INITIAL. flag = \'X\'. ENDIF. MODIFY itab. ENDLOOP. \" 推荐 itab-flag = \'X\'. MODIFY itab TRANSPORTING flag WHERE flag IS INITIAL.
4.4 WHERE 条件减少循环次数
LOOP AT itab WHERE vbeln IN s_vbeln. ENDLOOP.
4.5 使用宏代替频繁函数调用
-
宏会在编译时展开,大量使用会导致代码体积膨胀
-
性能敏感逻辑建议改为 FORM 或 METHOD
-
公用逻辑封装成 Class Method,可重用且便于维护
DEFINE add_one. number = number + 1. END-OF-DEFINITION.
4.6 避免频繁类型转换
不推荐MOVE-CORRESPONDING ls_source TO ls_target.
4.7 内表 OCCURS n / PACKAGE SIZE
(1)使用OCCURS n 与 OCCURS 0的区别
OCCURS n 代表初始化内表的空间大小为n,当内表存储记录条数超出n时,系统将依靠页面文件存放超出部分的数据。当系统内存资源十分紧缺的时候,我们可以使用OCCURS n 的初始化方法,但是这样的效率稍微慢点。
OCCURS 0 代表初始化内表的空间大小为无限,当内表存储记录条数不断增加时,内表所使用的内存空间不断扩大,直到系统无法分配为止。使用内存比使用页面交换更快一些,但是要考虑系统的资源状态。
(2) 使用SELECT... PACKAGE SIZE n 分段查询数据,减低数据库缓存负担
SELECT ... INTO TABLE itab PACKAGE SIZE 100 FROM vbak...
该语句实现每次打开DB会话时,往应用服务器上传输100条记录,然后关闭会话,刷新缓存。用于在数据库缓存资源紧缺的情况下使用。
SELECT * INTO TABLE itab PACKAGE SIZE 100 FROM vbak.
五、业务调优层
-
ABAP 程序很多时候不是单机运行,而是调用 BAPI / RFC / IDoc / WebService 与外部系统交互。调用方式选择直接影响性能。
5.1 RFC 调用优化
-
批量数据:合并为一次 RFC 调用
-
异步 RFC(aRFC):对实时性要求不高的任务,异步执行减少前端等待
-
示例(异步 RFC):
CALL FUNCTION \'Z_PROCESS_DATA\' STARTING NEW TASK \'TASK1\' DESTINATION IN GROUP \'GROUP1\' PERFORMING frm_callback ON END OF TASK EXPORTING iv_param = lv_param.
5.2 BAPI 调用优化
-
避免循环中多次调用同一个 BAPI
-
尽可能使用批量版本(如 BAPI_GOODSMVT_CREATE 支持一次过账多行,以及用BAPI_MATERIAL_SAVEREPLICA 替代 BAPI_MATERIAL_SAVEDATA)
-
5.3 IDoc 处理优化
-
批量收发(集成 IDoc package)
-
5.4 WebService / OData 优化
-
只请求必要字段($select 参数)
-
使用分页($top / $skip)
-
合理设置缓存策略
-
避免逐条 COMMIT WORK
-
异步发送减少对主事务的影响
-
六、调用方式优化
-
RFC 异步调用(TRFC/QRFC)
-
后台 Job 调用
-
举例:向 ECAS生一个按照三个字段汇总12个月的数据,生成一个大文件
-
解决方法:
-
(1) 启动12个JOB,按照月份汇总数据生成12个结果
(2) 根据12个结果在汇总按照3个字段的数据。
-
BDC编程方式,使用Session方式,不使用transaction方式
七、监控与分析工具
-
ST05 SQL 跟踪
-
SAT / SE30 ABAP 代码分析
-
ST12 SQL + Runtime 分析
-
SE14 / DB02 数据库统计与索引分析
-
SM37 后台作业性能分析
-
STAD 事务级性能分析
八、实战案例:FCC 财务成本控制优化
8.1 项目背景
-
系统:SAP S/4HANA
-
模块:FI-CO + ABAP 报表
-
数据量:每天数百万条凭证
-
目标:优化报表执行效率,降低系统压力
8.2 遇到的问题
-
ALV 报表加载慢
-
BSEG 查询频繁 CPU 占用高
-
RFC 多次分批发送数据
-
内表重复数据多
8.3 优化策略与 ABAP 代码示例
8.3.1 内表清理
END-OF-SELECTION. CLEAR: gt_bseg[], gt_vbak[]. FREE: gt_bseg, gt_vbak.
8.3.2 FOR ALL ENTRIES 替代 LOOP + SELECT SINGLE
SELECT belnr gjahr bukrs tcode FROM bkpf INTO TABLE lt_bkpf WHERE bukrs = \'1000\' AND gjahr IN s_gjahr AND tcode = \'PRRW\'. IF lt_bkpf IS NOT INITIAL. SELECT belnr gjahr kostl aufnr pernr hkont kstar pswbt FROM bseg INTO TABLE lt_bseg FOR ALL ENTRIES IN lt_bkpf WHERE bukrs = lt_bkpf-bukrs AND belnr = lt_bkpf-belnr AND gjahr = lt_bkpf-gjahr. ENDIF.
8.3.3 PACKAGE SIZE 分段处理
SELECT belnr gjahr bukrs FROM bkpf INTO TABLE lt_temp PACKAGE SIZE 6000 WHERE bukrs = \'1000\' AND gjahr IN s_gjahr AND tcode = \'PRRW\'. IF lt_temp[] IS NOT INITIAL. SELECT belnr gjahr kostl aufnr pernr hkont kstar pswbt FROM bseg APPENDING TABLE lt_bseg FOR ALL ENTRIES IN lt_temp WHERE bukrs = lt_temp-bukrs AND belnr = lt_temp-belnr AND gjahr = lt_temp-gjahr. CLEAR lt_temp. ENDIF.
8.3.4 HASHED TABLE 查找优化
DATA: ht_bseg TYPE HASHED TABLE OF bseg WITH UNIQUE KEY belnr gjahr. LOOP AT lt_bseg INTO DATA(ls_bseg). INSERT ls_bseg INTO TABLE ht_bseg. ENDLOOP. READ TABLE ht_bseg WITH TABLE KEY belnr = \'123456\' gjahr = \'2025\' INTO DATA(ls_check). IF sy-subrc = 0. WRITE: / \'找到凭证\', ls_check-belnr. ENDIF.
8.3.5 RFC 异步调用
LOOP AT lt_fcc_parts INTO DATA(ls_part). CALL FUNCTION \'Z_FCC_PROCESS\' STARTING NEW TASK ls_part-task DESTINATION IN GROUP \'FCC_GROUP\' EXPORTING it_data = ls_part. ENDLOOP.
8.3.6 去重与排序
SORT lt_bseg BY belnr gjahr kostl. DELETE ADJACENT DUPLICATES FROM lt_bseg COMPARING belnr gjahr kostl.