1. 客户主数据报表性能瓶颈分析
第一次接手这个客户主数据报表项目时,我就被它的运行速度震惊了。点击执行按钮后,系统就像被冻住一样,足足等了3分钟才吐出结果。这完全不符合业务部门对实时查询的需求,特别是销售团队经常需要快速调取客户信息。
通过ST12事务码抓取性能跟踪数据,发现主要瓶颈集中在以下几个地方:
- 最耗时的操作是查询"最近一次SO创建时间"字段,单这一项就占用了总执行时间的65%
- 多表关联查询(KNA1、KNB1、KNVV等)没有充分利用索引
- 报表中包含了大量非必要字段,比如Agile扩展时间、银行Swift代码等业务部门几乎不用的数据
- 循环中对VBAP表的单独查询导致大量重复IO操作
举个例子,原代码中获取SO创建日期的逻辑是这样的:
SELECT vbak~vbeln erdat vkorg vbpa~kunnr INTO TABLE t_vbak FROM vbak JOIN vbpa ON vbak~vbeln = vbpa~vbeln FOR ALL entries IN t_knvv1 WHERE vbpa~kunnr = t_knvv1-kunnr AND vbpa~parvw IN ('AG','WE').这个查询在全量数据情况下要执行8-10秒,是性能的最大杀手。
2. SQL执行计划优化实战
2.1 使用SE30和ST05分析执行计划
首先通过SE30事务码分析了报表各部分的执行时间,发现数据库操作占了总时间的85%。接着用ST05进行SQL跟踪,抓取了实际执行的SQL语句。
关键发现:
- 系统在执行多表JOIN时没有使用最优的索引
- 部分WHERE条件导致全表扫描
- 循环中的单条查询产生了大量网络往返
2.2 优化JOIN逻辑
原代码中的表关联存在几个问题:
FROM knvv JOIN knb1 ON knvv~kunnr = knb1~kunnr AND knvv~vkorg = knb1~bukrs这种写法导致优化器无法有效使用索引。我将其改为:
FROM knvv JOIN knb1 ON knvv~kunnr = knb1~kunnr WHERE knvv~vkorg = knb1~bukrs同时确保所有关联字段都有合适的索引。
2.3 索引优化策略
针对性能最差的几个查询,我创建了以下组合索引:
- 在VBAP表上创建(KUNNR, VBELN, POSNR)的组合索引
- 在KNVV表上创建(KUNNR, VKORG, VTWEG)的组合索引
- 在KNA1表上创建(KUNNR, LAND1)的组合索引
创建索引后,关键查询的执行时间从秒级降到了毫秒级。
3. 代码层面的优化技巧
3.1 使用FOR ALL ENTRIES的正确姿势
原代码中大量使用了FOR ALL ENTRIES,但存在两个问题:
- 没有检查驱动表是否为空
- 关联字段没有排序去重
优化后的写法:
IF t_knvv1[] IS NOT INITIAL. SORT t_knvv1 BY kunnr. DELETE ADJACENT DUPLICATES FROM t_knvv1 COMPARING kunnr. SELECT vbeln erdat vkorg kunnr INTO TABLE t_vbak FROM vbak FOR ALL ENTRIES IN t_knvv1 WHERE kunnr = t_knvv1-kunnr AND erdat IN s_erdat. ENDIF.3.2 减少非必要字段查询
原报表查询了50多个字段,但业务实际只用其中30个左右。我移除了以下非必要字段:
- 所有时间类型的字段(业务只需要日期)
- 银行Swift代码等金融信息
- 多个备用名称字段
这使数据传输量减少了40%。
3.3 使用内表代替多次查询
对于像客户主数据这种变化不频繁的数据,我改为一次性查询到内表,后续直接从内存读取:
SELECT kunnr name1 land1 INTO TABLE t_kna1 FROM kna1 WHERE kunnr IN s_kunnr. "后续使用 READ TABLE t_kna1 INTO w_kna1 WITH KEY kunnr = w_knvv-kunnr BINARY SEARCH.4. 缓存机制的实现
4.1 使用共享内存缓存
对于变化频率低的主数据,我实现了共享内存缓存:
DATA: shm_area TYPE REF TO zcl_customer_shm. TRY. shm_area = zcl_customer_shm=>attach_for_read( ). t_kna1 = shm_area->get_kna1_data( ). CATCH cx_shm_error. "缓存不存在时从数据库读取 SELECT * FROM kna1 INTO TABLE t_kna1 WHERE kunnr IN s_kunnr. shm_area = zcl_customer_shm=>attach_for_write( ). shm_area->set_kna1_data( t_kna1 ). shm_area->detach_commit( ). ENDTRY.4.2 结果集缓存
对于常用查询条件组合的结果,我增加了本地缓存:
DATA: lt_cache TYPE SORTED TABLE OF ty_show WITH UNIQUE KEY kunnr vkorg. READ TABLE lt_cache INTO w_show WITH TABLE KEY kunnr = w_knvv-kunnr vkorg = w_knvv-vkorg TRANSPORTING ALL FIELDS. IF sy-subrc <> 0. "执行正常查询逻辑 APPEND w_show TO lt_cache. ENDIF.5. 性能优化效果对比
优化前后的关键指标对比:
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 平均响应时间 | 182秒 | 3.2秒 | 98% |
| CPU占用率 | 85% | 15% | 82% |
| 内存使用 | 1.2GB | 320MB | 73% |
| 数据库负载 | 78% | 12% | 85% |
业务部门的反馈也非常积极,原来需要等待几分钟的报表现在几乎是实时响应。特别是在月末结账期间,这个优化为财务部门节省了大量等待时间。
这个案例让我深刻体会到,ABAP性能优化不仅需要掌握技术手段,更要理解业务实际需求。有时候最简单的优化——比如去掉几个非必要字段——就能带来显著的性能提升。