尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

031、内表读取与循环:别再用READ TABLE硬扛了

031、内表读取与循环:别再用READ TABLE硬扛了 031、内表读取与循环别再用READ TABLE硬扛了晚上十点半生产系统报了个莫名其妙的错程序在循环里读取内表数据量才两万行却跑出了五分钟的耗时。客户电话打过来的时候我正盯着ST05的SQL跟踪记录发愣——明明用的是标准READ TABLE怎么比数据库查询还慢后来把循环体里的READ TABLE换成二分查找总耗时直接掉到三秒。今天这篇就把内表读取那点事儿掰开揉碎全是从坑里爬出来的经验。先说最基础的按表索引读取。READ TABLE lt_itab INDEX 5.这个写法在数据量小的时候很爽但你要是拿它去循环两万次每次读第N行那就等着被性能优化小组请喝茶吧。ABAP里的内表索引是线性表按索引访问是O(1)复杂度没错可你循环里每次都重新计算索引位置再加上工作区拷贝实际开销远超想象。我见过最离谱的写法是循环里嵌套一个READ TABLE ... INDEX sy-index去取另一张内表的对应行——这种场景直接改成两张表并列循环或者用SWAP技术别跟自己过不去。再说说READ TABLE配合WITH KEY的写法。这是最容易被误用的地方。READ TABLE lt_itab WITH KEY field1 lv_a field2 lv_b.在没有SORTED或HASHED表类型的前提下默认是线性查找复杂度O(n)。如果你在十万行的表里循环一万次每次都要线性扫一遍那就是十亿次比较不卡才怪。很多人知道加BINARY SEARCH但BINARY SEARCH要求表必须先按查找键升序排序否则结果随机出错。这里有个更隐蔽的坑表排序时用了多个字段你BINARY SEARCH时只查其中一部分字段那结果也是错的。ABAP的二分查找要求排序字段和查找字段完全匹配少一个都不行。有时候我们不得不在循环里反复查找同一张表。比如读取物料主数据一个物料号在循环里出现很多次每次都要去MAKT表里读描述。这种场景请记住一个经典做法先把要读取的数据按KEY排序然后循环时记录上一次的INDEX用READ TABLE ... WITH KEY ... BINARY SEARCH配合sy-subrc判断如果找到了就继续没找到就再线性找一次。但更好的方案是在循环外先把内表按需要的KEY排序然后用AT NEW/AT END OF分组把读取操作提到包含行组的外层。别嫌麻烦数据量上来的时候这能救你命。HASHED内表是另一个解药。DATA lt_itab TYPE HASHED TABLE OF ty_itab WITH UNIQUE KEY key_field.这种表内部用哈希索引单条读取时间复杂度O(1)比二分查找还快。但注意HASHED表不支持非UNIQUE KEY也不支持按索引访问排序更没意义。你要是业务上确实需要按非唯一键读取那就老老实实建SORTED表或STANDARD表加二分。另外HASHED表的创建和填充开销比STANDARD表大如果表只有几百行用HASHED反而杀鸡用牛刀。循环读取还有一个容易忽视的性能杀手每次循环里都往工作区塞数据时如果内表是HEADER LINE的老式写法READ TABLE lt_itab WITH KEY ...会把匹配行复制进表头下一次循环又得覆盖。这里有个习惯——用ASSIGNING FIELD-SYMBOL代替工作区。比如LOOP AT lt_itab ASSIGNING fs. READ TABLE lt_other WITH KEY id fs-id ASSIGNING fs_other. IF sy-subrc 0. fs-name fs_other-name. ENDIF. ENDLOOP.这样省去了内表行到工作区的二次拷贝尤其当内表行结构里有长字符串或内表嵌套时差距立竿见影。但注意你不能在循环体内对引用的表执行修改行数目的操作比如DELETE或INSERT否则指针会失效。这个坑百踩百中除非你在修改前先暂停循环或者用MODIFY ... TRANSPORTING。还有一种情况是循环中需要删除满足条件的行。铁律是倒序循环LOOP AT lt_itab ASSIGNING fs. IF fs-flag X. DELETE lt_itab. 别这么写删完当前行sy-tabix会乱循环也会跳过下一行 ENDIF. ENDLOOP.正确做法是LOOP AT lt_itab INTO wa WHERE flag X. DELETE lt_itab. ENDLOOP.或者先收集要删的行号再统一删。更正一下上面那个带ASSIGNING的写法里DELETE会把下一行顶上来然后LOOP自动跳到再下一行直接漏掉一条。除非你立即CONTINUE但CONTINUE之前指针已经失效访问就是非法操作。所以倒序LOOP AT lt_itab ASSIGNING fs. 什么都不做只是遍历 ENDLOOP. DATA(lv_lines) lines( lt_itab ). DO lv_lines TIMES. DATA(lv_index) lv_lines - sy-index 1. READ TABLE lt_itab INDEX lv_index ASSIGNING fs. IF fs-flag X. DELETE lt_itab INDEX lv_index. ENDIF. ENDDO.每次删除一行都会造成索引重排但倒序删不会影响前面未处理的行号完美。再说说循环中修改内表行内容。常见需求根据关联表更新当前行的字段。别在LOOP里再套一个READ TABLE这样两层循环就是O(n*m)。先把关联表按KEY排序然后在主循环里用二分查找读取关联表配合MODIFY ... FROM wa TRANSPORTING field只更新需要的字段。注意TRANSPORTING只对非KEY字段有效你要改KEY字段就得先删行再插入否则后果自负。如果你用FOR循环ABAP 7.4的新语法配合LET条件做内表构造看起来优雅但性能不一定比传统的LOOP好。比如lt_result VALUE #( FOR wa IN lt_itab WHERE ( field1 X ) LET lv_tmp wa-field2 1 IN ( field1 wa-field1 field2 lv_tmp ) ).这是纯函数式写法适合一次性过滤和映射。但如果要做一对多关联还是老办法更可控。新语法有个好处是避免修改原始表能写出不可变风格的代码调试时心智负担小。可别为了炫技把复杂业务逻辑塞进FOR里后面维护的人可能就是你会想抽自己。真正的调试技巧当觉得READ TABLE慢的时候先别看代码用SE30或SAT跑一下运行时分析看哪一行占的CPU时间最多。大部分时候会发现是循环内的READ TABLE。然后你用SO排序后二分或者改成SORTED表性能立刻提升。还有一种情况是表字段里有STRING类型比较时开销巨大。把大字段挪到单独的辅助表主表只留KEY和关联ID会快很多。最后分享一个我在项目里用烂了的模式构建“查找表”时不直接读原始表而是先按需求维度生成一个二级HASHED表只包含必要的KEY和值。比如要按物料工厂读描述就建DATA: lt_mat_desc TYPE HASHED TABLE OF ty_mat_desc WITH UNIQUE KEY matnr werks.然后一次性填充这个HASHED表循环里直接READ TABLE lt_mat_desc WITH TABLE KEY ... ASSIGNING fs_desc。注意这里用WITH TABLE KEY明确告诉ABAP用哈希索引别写成WITH KEY后者在某些情况会退化成线性扫描。记住一句话内表不是数据库但别拿它当线性扫描机。数据结构选型决定性能上限算法优化只是兜底。写完代码后自己用sy-tabix观察一下循环次数心里要有数。如果你在循环里写了三个READ TABLE那大概思路就错了。把读取操作提升到循环外把重复计算缓存起来把内表类型选对——这三板斧下去80%的性能问题能消失。下次遇到慢的内表循环别急着加BINARY SEARCH先问自己这个表真的需要STANDARD吗真的不能改成HASHED吗真的必须在循环里读吗如果答案都是“是”那你再优化代码细节也不迟。
返回列表