> 技术文档 > SAP ABAP 性能优化全攻略:理论+实战案例_abap中 表 2000万的数据, 取200条 数据都特别慢

SAP ABAP 性能优化全攻略:理论+实战案例_abap中 表 2000万的数据, 取200条 数据都特别慢


SAP ABAP 性能优化全攻略:理论+实战案例

目录

  1. 优化的基本原则

  2. 性能影响因素

  3. 数据库层优化

  4. ABAP程序层优化

  5. 业务调优层

  6. 调用方式优化

  7. 监控与分析工具

  8. 实战案例:FCC 财务成本控制优化


一、优化的基本原则

在任何性能优化中,有三个核心目标:

  1. 减少 I/O 操作 —— 尽量减少数据库交互次数,降低网络传输量。

  2. 减少 CPU 操作 —— 减少无意义的循环、重复计算与数据复制。

  3. 减少内存占用 —— 合理管理内存,避免无用数据长期驻留。

1. 减少 I/O 操作

  • 批量读取替代单条读取:一次取完数据,不要在循环中多次访问数据库。

  • 靠近数据库处理逻辑:使用 CDS View、AMDP 等手段,将复杂逻辑尽量下推到数据库执行。

  • 索引优化:使用主键或二级索引减少扫描。

2. 减少 CPU 操作

  • 避免嵌套循环与重复判断。

  • 善用 SORTED TABLEHASHED 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.

二、影响性能的主要因素

  1. 硬件与网络带宽
    应用服务器、数据库服务器的 CPU/内存/磁盘性能,以及网络带宽,都会直接影响 ABAP 程序的执行速度。

  2. SAP 服务器参数
    内存参数(如 abap/buffersize)、工作进程数、缓存设置等需要系统管理员调整。

  3. 数据库索引设计

    • 缺少必要索引 → 全表扫描 → 性能差

    • 过多无效索引 → 更新慢,占用空间

    • 注意:簇表(Cluster Table)不能创建二级索引。

  4. 数据量规模
    性能曲线:

    • 执行时间随数据量增加对数级增长 → 性能可接受

    • 执行时间随数据量指数级增长 → 必须优化


三、数据库层优化

数据库是 ABAP 性能优化的第一道防线,绝大多数瓶颈出现在 SQL 执行阶段。

3.1 使用索引

  • 按照索引顺序写 WHERE 条件(从左到右匹配)

  • 优先用 EQNE 替代 =

  • 示例(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

    • 异步发送减少对主事务的影响


六、调用方式优化

  1. RFC 异步调用(TRFC/QRFC)

  2. 后台 Job 调用

  • 举例:向 ECAS生一个按照三个字段汇总12个月的数据,生成一个大文件

  • 解决方法:

  • (1) 启动12个JOB,按照月份汇总数据生成12个结果

    (2) 根据12个结果在汇总按照3个字段的数据。

  1. BDC编程方式,使用Session方式,不使用transaction方式


七、监控与分析工具

  1. ST05 SQL 跟踪

  2. SAT / SE30 ABAP 代码分析

  3. ST12 SQL + Runtime 分析

  4. SE14 / DB02 数据库统计与索引分析

  5. SM37 后台作业性能分析

  6. STAD 事务级性能分析


八、实战案例:FCC 财务成本控制优化

8.1 项目背景

  • 系统:SAP S/4HANA

  • 模块:FI-CO + ABAP 报表

  • 数据量:每天数百万条凭证

  • 目标:优化报表执行效率,降低系统压力

8.2 遇到的问题

  1. ALV 报表加载慢

  2. BSEG 查询频繁 CPU 占用高

  3. RFC 多次分批发送数据

  4. 内表重复数据多

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. 

8.4 优化效果

优化前 优化后 ALV 报表加载:5 分钟 50 秒 CPU 占用峰值:80% 30% 网络负载大 异步 RFC 降低 60% 内表清理耗时 2 分钟 < 5 秒

旅途日志