ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ABAP性能优化与代码整洁:从对抗到统一的项目实战指南

ABAP性能优化与代码整洁:从对抗到统一的项目实战指南 作为一个常年泡在SAP项目里的ABAP开发我见过太多同事在“性能优化”和“代码整洁”之间来回拉扯。业务顾问上线前拉着你说报表太慢了程序一多就崩代码评审时架构师又拎着你的SELECT *和循环内查库不放。于是很多人养成了“先写能跑的再想能不能跑得快”的习惯结果重构时发现比重写还累。这篇东西其实是从我自己的项目经历里整理出来的一套打法核心就一句话性能与整洁不是单选题它们本来就是同一件事的两面。一个结构干净的ABAP程序往往更容易做性能分析也更容易写出高性能的实现反过来刻意追求“快”的代码如果写得很乱后面反而会因为无法扩展、难以定位问题而拖慢整体效率。文章适合正在做报表优化、接口开发、增强开发的ABAP开发也适合刚从其他语言转到ABAP、对Mind the performance这个概念还不够熟悉的同学。内容不是空谈是能直接抄回项目里用的实战。1. 性能与整洁一对被误读的冤家开局先泼一盆冷水我见过很多“性能优化”其实是在给代码埋雷。比如为了减少一次数据库查询把一大堆业务规则塞进一个方法里为了让某个报表快几秒把正常的数据结构拆成全局变量满天飞。这种代码上线后确实快了几天等需求一变改起来从半天变成一周这账怎么算都不划算。1.1 “二选一”的困局是怎么形成的ABAP这个语言有个特点——它离业务太近了。SAP项目里绝大多数ABAP代码都是被业务需求推着走的销售凭证增强、采购审批、生产订单状态检查、接口数据落库每个需求都有明确的时间点没人愿意等你把代码打磨到“完美”。于是不少开发形成了一种肌肉记忆能用SELECT *就用SELECT *循环里取数也能接受变量命名能看懂就行。这种代码几周后连自己都看不懂更别提优化。另外一个原因是性能分析的滞后。通常在性能测试阶段才暴露问题这时已经离上线很近你的第一反应肯定是“哪里慢改哪里”而不是“重构出干净的程序”。这种救火式的改法往往换来的是另一个新坑让人误以为优化和整洁天生互斥。1.2 重新定义“性能”和“整洁”性能不只是“跑得快”。真正的性能包含三层单次执行速度、并发下的稳定性、以及当业务规模变化时的伸缩能力。整洁也不等于“注释多”。我见过注释比代码还多、逻辑还是一团乱麻的程序。整洁的标准是读代码的人可以在脑子里跑通整个流程并且能在不破坏其他功能的前提下做修改。把这两个标准放在一起看你会发现它们的交集非常大。一个方法只干一件事你就能更快定位慢在哪个方法里一个查询只取必要的字段数据库侧的索引才能正常工作这是性能和整洁共同的要求一段循环内没有副作用你才敢放心缓存数据。1.3 整洁的代码降低性能劣化风险这里有个很容易被忽视的点业务是不断变化的。今天你的报表只有一万条数据跑300毫秒没人关心性能明年数据量变成一千万同样逻辑可能跑三分钟。这时候如果你的代码是整洁的拆成清晰的模块性能劣化的位置通常一眼就能锁定——某个SQL缺索引、某个循环内消耗高。反观那种一团糟的程序你得先花一天搞清楚它在干什么才谈得上优化。所以把整洁当成性能的“预防性维护”一点都不夸张。2. SQL查询层面最直观的性能与整洁交汇点ABAP应用慢八成都慢在数据库访问上。而SQL写得好不好恰好也是判断代码整洁与否的试金石。我在项目里做代码评审先看程序里的SELECT长什么样基本就能判断这个人的开发习惯。2.1 不要在循环里反复查库循环内查表是最常见、最容易被点评的性能反模式。比如遍历一个内表在循环里用SELECT SINGLE取客户名称、物料描述数据量一上来就是灾难。LOOP AT lt_items INTO ls_item. SELECT SINGLE maktx FROM makt INTO ls_item-maktx WHERE matnr ls_item-matnr AND spras sy-langu. MODIFY lt_items FROM ls_item. ENDLOOP.这段代码读起来很直观但每一次循环都是一次数据库往返。如果lt_items有5000行就是5000次查询本地网络好一点还能忍放到生产环境基本要被骂。整洁的写法是先把这批数据准备好一次性取回IF lt_items[] IS NOT INITIAL. SELECT matnr, maktx FROM makt INTO TABLE lt_makt FOR ALL ENTRIES IN lt_items WHERE matnr lt_items-matnr AND spras sy-langu. ENDIF. SORT lt_makt BY matnr. LOOP AT lt_items ASSIGNING FIELD-SYMBOL(fs_item). READ TABLE lt_makt INTO ls_makt WITH KEY matnr fs_item-matnr BINARY SEARCH. IF sy-subrc 0. fs_item-maktx ls_makt-maktx. ENDIF. ENDLOOP.一次查询加一次SORT加一次二分查找熟悉这套模式后写起来也不比循环内SELECT麻烦多少。关键是这种写法让数据库访问集中、清楚读代码的人一眼看出“这里就查了一次库”这就是整洁的力量。2.2 字段列表和索引从SELECT *到精确取列的性能跃迁我在项目中几乎每次都强调不要用SELECT *。很多人觉得ABAP语句写长了显得啰嗦直接SELECT *省事。但省掉的并不是复杂度而是把必要的约束从代码里抹掉了。SELECT *带来几个问题一是网络传输数据量大二是ABAP层要分配大量不必要的内存三是大概率无法命中最优索引。而明确列出字段列表一方面让数据库优化器更容易选择覆盖索引另一方面也逼着开发者思考“我到底需要哪些字段”这本身就是个整理思路的过程。配合索引时字段列表更加重要。比如你经常用销售凭证号查一批数据那么在凭证号字段上建索引再写IF lt_header[] IS NOT INITIAL. SELECT vbeln, posnr, matnr, kwmeng FROM vbap INTO TABLE lt_vbap FOR ALL ENTRIES IN lt_header WHERE vbeln lt_header-vbeln. ENDIF.相比SELECT *这种写法既减少了传输量也能让数据库在索引上扫描更少的页。关于索引建议开发在优化阶段主动用SE14或SE11查看表上现有索引而不是等DBA来提醒。创建索引是件需要权衡的事但基本共识是查询频率高且查询字段区分度高的列值得建。2.3 FOR ALL ENTRIES 和 JOIN不是非此即彼关于取数方式一直有争议。老派的ABAP开发喜欢FOR ALL ENTRIESFAE因为它在某些版本和数据库上效率稳定而且对数据量不敏感。而新一些的项目里大家更愿意直接写JOIN因为JOIN逻辑上更整洁表之间的关系写在SQL里而不是靠内存里的临时集合去匹配。我的经验是两者都有适用场景别迷信任何一个。FAE的本质是把你临时表里的值展开成一个IN条件但它有个坑临时表为空时会全表扫描所以必须先判断非空。IF lt_items[] IS NOT INITIAL. SELECT vbeln, posnr FROM vbap FOR ALL ENTRIES IN lt_items WHERE vbeln lt_items-vbeln. ENDIF.JOIN的好处是SQL清晰但也要小心笛卡尔积和连接结果过大。多个内连接改成显式JOIN可以提升可读性不过ABAP里的OPEN SQL对JOIN的支持比数据库原生SQL弱一些。在复杂的业务场景里我通常建议超过三个表的关联不要硬写成一个JOIN拆分两步查询会更好维护。2.4 别小看配置表SM30带出描述的性能陷阱SAP项目里到处都是配置表比如状态、类型、文本描述。这些表数据量不大但被访问的频率极高。我见过一个销售凭证增强循环里对每行调用函数读取状态文本结果状态多了报表直接从2秒拖到20秒。SM30配置表经常只需要“带出描述”最简单的是调用功能模块或者用配置视图直接查。但在批量场景里最高效的做法还是把所有描述一次性缓存到内部表。TYPES: BEGIN OF ty_status, stsma TYPE j_status-stsma, txt04 TYPE j_status-txt04, txt30 TYPE j_status-txt30, END OF ty_status. DATA lt_status_cache TYPE SORTED TABLE OF ty_status WITH UNIQUE KEY stsma txt04. SELECT stsma, txt04, txt30 FROM j_status INTO TABLE lt_status WHERE spras sy-langu. INSERT LINES OF lt_status INTO TABLE lt_status_cache.之后每次取描述就用READ TABLE不会再碰数据库。这种“预加载缓存”的模式在ABAP里非常通用而且代码写出来很整洁数据从哪来、缓存在哪、怎么用看代码就能懂。3. 内部表操作的性能与整洁数据库访问优化好之后真正的战场就转到内存里的内部表处理。很多ABAP程序慢不是数据库的问题而是内表选型、排序、查找方式出了岔子。3.1 内部表类型选型STANDARD、SORTED还是HASHED刚开始学ABAP的时候大家最常用的是STANDARD TABLE因为创建起来不用动脑。但到了性能敏感的场景类型选不对代码再整洁也没用。内表类型适用场景性能特征整洁度影响STANDARD顺序处理、频繁修改、按索引访问二分查找需要先排序写法最自由但需要自己维护排序SORTED按关键字查找、数据量中等自动排序READ TABLE默认二分声明后自动保证有序代码更简单HASHED大数据量、按唯一键精确查找哈希索引按关键字查找极快只能按完整关键字访问语义清晰但受限一个常见的误区是数据量大就用HASHED。其实HASHED的查找速度虽然快但无法按非主键排序遍历也无法部分匹配。如果后续需要顺序处理还得转成STANDARD反而多余。SORTED TABLE很多时候是“均衡之选”有序可用于批量顺序访问也可默认二分查找。我个人的建议如果表创建后主要按一个唯一键精确查找使用HASHED如果既要按关键字找又要做顺序处理使用SORTED如果只是中间结果或者需要频繁修改用STANDARD然后SORT READ TABLE ... BINARY SEARCH。3.2 二分查找不是自动发生的在STANDARD TABLE上使用READ TABLE ... WITH KEY默认是线性查找数据量上千后性能就会肉眼可见地变差。很多人忘了先SORT再BINARY SEARCH。SORT lt_data BY matnr. READ TABLE lt_data INTO ls_data WITH KEY matnr lv_matnr BINARY SEARCH.这个细节说起来简单却是排查性能问题时最容易忽略的一个点。尤其数据来自多次APPEND顺序是乱的直接READ TABLE线性扫扫到几万行时就会卡顿。为了让这套逻辑保持整洁我通常把“排序查找”封装成一个局部方法METHODS get_by_matnr IMPORTING !iv_matnr TYPE matnr RETURNING VALUE(rs_data) TYPE ty_data. METHOD get_by_matnr. SORT me-mt_data BY matnr. READ TABLE me-mt_data INTO rs_data WITH KEY matnr iv_matnr BINARY SEARCH. ENDMETHOD.所有对内部表的访问都走这个方法调用方代码简洁且稳定高效。3.3 避免不必要的复制和转换ABAP对大数据量的复制开销非常敏感。比如用把内表整体复制一遍如果后续发现只是读取就不如在变量声明时直接引用或使用FIELD-SYMBOL。LOOP AT lt_result ASSIGNING FIELD-SYMBOL(fs_result). 修改 fs_result 直接作用于内表不需要 MODIFY ENDLOOP.使用字段符号可以减少一次数据拷贝代码上也少写MODIFY看起来清爽。在循环内调用方法传内表时如果方法不需要修改用IMPORTING传引用比COPY更高效。循环里也尽量少做类型转换尤其是字符串拼接和数值格式化能提到循环外就提到循环外。3.4 用运行计时量化性能改善谈到性能优化数据比感觉靠谱。ABAP里获取毫秒级的运行时间通常用GET RUN TIMEDATA: lv_start TYPE i, lv_end TYPE i. GET RUN TIME FIELD lv_start. 要测量的业务逻辑... GET RUN TIME FIELD lv_end. DATA(lv_elapsed_ms) ( lv_end - lv_start ) / 1000. WRITE: / 执行耗时(毫秒):, lv_elapsed_ms.GET RUN TIME返回的是运行计数精度足以覆盖毫秒级比较。在优化前后各打一个点轻轻松松就能量化优化效果。我习惯每次做优化时在修改前后各跑一次记录耗时这样才有说服力。不要只凭感觉说“快了很多”。拿报表真实数据量做对比比如循环内查库改为批量查库后从43秒降到2.1秒这种数据一摆出来谁都能理解改动价值。4. 接口传输与集成场景性能与整洁如何握手SAP项目中接口开发占了很大比重实际操作中性能问题也最容易在这里爆发。文件传输、HTTP接口、异步队列、权限校验一个不慎就会造成大量数据堆积或超时。而这个场景恰恰需要代码整体设计整洁才能让性能优化不失控。4.1 大文件不要一次性读入内存ABAP接口传文件是项目里特别常见的需求。比如定时从FTP读取供应商主数据或者向银行发送付款文件。很多新手会习惯性用OPEN DATASET READ TABLE把整个文件读进内存文件一大就内存暴涨甚至程序dump。以文本文件为例正确做法是逐行读取OPEN DATASET lv_filepath FOR INPUT IN TEXT MODE ENCODING DEFAULT. IF sy-subrc 0. DO. READ DATASET lv_filepath INTO lv_line. IF sy-subrc 0. EXIT. ENDIF. 处理当前行 ENDDO. CLOSE DATASET lv_filepath. ENDIF.这种逐行处理方式把内存占用压到最低而且代码结构也非常清晰打开文件、循环处理、关闭文件一目了然。配合批量提交比如每100行或者每1000行调用一次BAPI写入既避免了单次写入事务过大也让处理进度可控。传大文件时另外要注意文件传输本身往往比ABAP处理更耗时最好用异步Job定时拉取而不是在同步对话中做。把“读文件”和“写数据库”拆成两个独立的步骤也便于失败后重跑。4.2 异步处理与队列别让同步事务背锅很多接口设计成同步是因为业务要求即时返回结果。但如果是大批量数据同步处理会让用户端干等还容易超时。这时候可以考虑异步队列用户请求先进队列后台Job批量消化。ABAP中可以用SMQR等队列机制也可以自己写后台报表调度。无论走哪种一个很重要的整洁原则是队列处理程序的状态和日志必须完整。否则一旦某个批次失败连开发都查不出问题在哪。我通常在接口处理表里加一个状态字段如PROC_STATUS0待处理、1成功、2失败和重试次数。每处理一条更新一次这样出问题时可以直接查表。日志完整排查方便这本身就是对性能的一种保障——定位慢、定位失败的成本往往比查一次程序慢还要高。4.3 权限校验和业务校验尽量前置接口开发中权限校验是个典型的“又慢又乱”的点。有人写一个方法在处理过程中反复做权限检查结果每个循环都调一次AUTHORITY-CHECK。更好的做法是在程序入口处做一次权限校验整体校验失败就不继续处理。权限检查一般都很快但不能放在循环里放大100倍。同理业务校验比如订单状态、物料合法性也应该尽量批量查询出来在循环里只做内存判断不要循环里调BAPI或查询业务表。这里可以结合ABAP请求提交中常见的授权校验来思考虽然场景各有不同但原则一样能把校验数据一次捞出来就别循环访问。5. 增强开发中的性能与整洁边界SAP项目基本绕不开增强。销售凭证VA03、采购订单ME23N、生产订单CO03这些界面上到处是增强点。增强开发里性能问题特别隐蔽因为用户感知到的往往是“界面卡一下”而不是报表慢。而且增强代码分散在标准程序各处一旦不规范对后续维护就是噩梦。5.1 找对增强点性能与维护的最优解以VA03销售凭证增强为例如果业务需要显示额外的字段或按钮可能在PBO/PAI、USEREXIT、BADI等不同入口都可以实现。选哪个增强点直接决定了代码性能和后续可维护性。我的建议是能用BADI或显式增强点就别用隐式增强能在一次遍历中完成的事情就别在界面的多个位置各写一段。增强点选得对后续不会出现“这个字段在显示时改了打印时却还是旧值”这种低级问题。而选错增强点往往要靠多次调用和重复取值来补救性能自然好不到哪去。5.2 别在增强里画蛇添足另一个常见问题是增强代码里放了太多无关逻辑。特别是用户看到标准界面上有某个字段就想顺手把其他相关的值也带出来结果一个简单增强越写越长每次显示都执行一大段SQL。我见过一个VA03增强只是为了显示一行备注结果代码里又查了物料表、客户表、定价表前后加起来四五次数据库访问。优化起来其实很简单把所有的字段查询合并成一次SELECT或者用缓存。但在增强点里更重要的是克制只做增强需求要求的事情把复杂度挡在外面。5.3 低调而整洁隐式增强的纪律隐式增强点Implicit Enhancement有时是不得不用的。用它的好处是侵入小不需要修改标准代码升级时不会冲突。但坏处是它看不见摸不着维护的人常常不知道这里有逻辑。所以用隐式增强时必须有纪律第一注释写清楚这个增强解决什么问题、为什么放在这里第二代码只增强不改造标准逻辑第三定期排查有没有废弃增强。我见过一个系统里好几个隐式增强互相覆盖每次查问题都要把所有增强点翻一遍。后来团队定了一个规矩每个隐式增强必须写注释标明责任人、需求单号和修改日期否则不允许提交。这其实就是把“整洁”制度化而干净之后性能定位也快了很多。6. 性能分析实操让工具替你说话讲了这么多原则落地时离不开工具。很多ABAP开发者一遇到性能问题就直接翻代码硬看效率很低。正确的顺序应该是先用工具定位瓶颈再有针对性地看相关代码。6.1 SE30 / SATABAP运行时分析的入门SE30新版本SAT是最基础也最常用的ABAP性能分析事务码。跑完一个程序后它会列出各个语句的耗时占比比如某个SELECT占了总执行时间的60%那么优化目标就有了。整洁实践的思路是在分析之前先保证被测数据量和真实用户场景一致。拿一个只有几百条数据的测试环境去分析结论基本没有参考价值。记录基线数据优化后再跑一次对比差异这是给开发团队演示“性能提升”的最有说服力的材料。6.2 ST05SQL追踪的精准定位ST05可以追踪数据库访问看到每条SQL是怎么执行的、访问了多少行、耗时多少。它特别适合查循环内查询和缺失索引的问题。使用流程也不复杂在ST05激活追踪勾选SQL Trace运行或操作目标程序停止追踪查看追踪结果。在追踪结果里重点看每个SQL语句的执行次数和总耗时。如果同一句SQL出现了几千次那就可以判断是循环内查询了。修改为批量查询后再追踪一次执行次数会大幅下降。6.3 代码评审中的性能嗅觉最后想说性能优化不只是跑工具的事。日常代码评审里看到下面这些信号就要警觉循环体内出现SELECT、CALL FUNCTION、外部调用大数据量内部表用标准线性查找使用SELECT *且未过滤关键条件没有判断空表就使用FOR ALL ENTRIES全局变量满天飞方法之间隐式传值。这些信号往往是性能和整洁同时出问题的征兆。把它们列成评审检查清单比事后救火有效得多。7. 常见性能问题排查实录写到这里分享几个我实际踩过的坑。这些问题在项目里反复出现基本可以当成速查表来用。7.1 问题一报表越跑越慢现象某张库存报表早期秒开数据量增长后变成十几秒用户频繁投诉。初步排查SE30显示主要耗时在某个内部表的READ TABLE上。进一步看代码原来是STANDARD TABLE直接READ TABLE WITH KEY没有排序也没有二分查找。解决把表声明改成SORTED TABLE或先SORT再READ TABLE ... BINARY SEARCH。耗时从12.3秒下降到2.4秒。心得很多“报表越跑越慢”不是数据库问题而是内存内表查找方式没有随数据量增长同步优化。内表类型一开始就要想清楚别等到卡了才想到查。7.2 问题二接口处理数据大量堆积现象每日付款接口处理数万条记录原本设计同步调用用户反馈经常超时队列里堆积大量消息处理速度跟不上。解决把同步逻辑改造成异步队列批量读取文件、批量写入数据库同时增加重试机制和日志表。心得接口性能优化不能只盯着单次SQL或单次函数调用要看整体处理链路是否匹配数据规模。同步调用天生不适合大批量设计接口时就要定好“大文件分段、异步、断点续传”这些基调。7.3 问题三增强代码导致界面卡顿现象VA03显示销售凭证时明显卡顿。排查发现一个隐式增强在PBO中循环内多次调用BAPI和查询函数界面每次刷新都触发。解决把这个增强移到用户操作时才触发同时把循环内的大量取数改为批量读取。心得增强开发尤其要克制。界面每次刷新都执行的代码必须保证极低的耗时实在无法保证就要想办法推迟执行或做缓存。7.4 性能问题排查速查表症状典型原因快速定位解决思路报表响应慢循环内查库/READ TABLE线性查找SE30看语句耗时批量查询缓存二分查找接口堆积同步处理大数据量队列日志/后台Job日志异步化、分批处理、重试机制界面卡顿增强代码PBO频繁执行ST05追踪调用次数延迟加载、缓存、减少数据库往返内存溢出大文件一次性读入内表ABAP dump类型逐行处理、分块提交8. 写在后面的话我在项目里反复讲一个观点性能优化不是写到一半才发现的事而是从设计代码那天起就该有的习惯。而保持整洁正是让这个习惯持续下去的最有效方式。一个程序员可以把代码写得看起来“很猛”各种花哨语法但也正因为猛后面没有人敢动。真正能长期维护、还能保持高性能的ABAP代码往往读起来很朴素。如果你正在接手一个别人留下的“又快又乱”的老程序别急着全盘重写。先跑SE30/ST05定位真正的瓶颈把循环内查询、线性查找这类问题逐个修掉守住“改动最小、收益最大”的原则。等你手头的代码结构理顺了、运行时性能也稳了自然就会认同这句话性能与整洁从不矛盾它俩本来就是同一个目标的两张面孔。最后再分享一个习惯每次写完一段性能优化代码我都会在注释里记一下优化前后的耗时和当时的业务数据量。这看起来是小事但等三个月后别人问起“这段逻辑为什么这样写”你会庆幸当时的记录留下了答案。ABAP开发这行最贵的永远不是CPU时间是人的注意力和排查成本。把这两样省下来性能自然就到位了。
返回列表