SAP基础数据校验工具开发系列博客(共5篇)
第五篇:性能优化与上线运维:保障高并发场景下的工具稳定运行
当校验规则越来越多、主数据量突破十万级、用户并发请求激增时,你的校验工具还能撑得住吗?单次批量校验从几分钟变成几小时,实时校验导致业务操作卡顿,甚至系统资源耗尽……这些都是性能瓶颈的真实写照。本文在前一篇优化基础上,进一步深入分享十万级以上批量数据校验的性能优化实战技巧,包括并行处理、缓存设计、数据库访问优化等核心手段,同时系统介绍上线后的监控告警体系、日志排查方案以及常见运维问题的快速处理方法,确保工具在高并发、大数据量场景下依然稳定、高效运行。
一、性能优化的整体思路
性能优化不能“头痛医头”,而应从数据流的角度系统分析。校验工具的性能瓶颈通常出现在以下三个层次:
| 层次 | 典型问题 | 优化方向 |
|---|---|---|
| 数据获取层 | 循环内逐条SELECT、未使用批量读取 | 批量查询、哈希表缓存 |
| 规则执行层 | 每条规则独立查表、重复计算 | 规则预编译、结果复用 |
| 并发调度层 | 单线程处理、资源闲置 | 并行处理、异步任务 |
本文将从这三个层次逐一展开。
二、数据获取层优化:从“逐条查”到“批量读”
2.1 核心原则:能用一次SELECT解决的,绝不使用N次
以物料校验为例,原始代码往往类似:
LOOP AT lt_materials INTO ls_mat. SELECT SINGLE mtart FROM mara INTO lv_mtart WHERE matnr = ls_mat-matnr. " N次 SELECT SINGLE maktx FROM makt INTO lv_maktx WHERE matnr = ls_mat-matnr AND spras = sy-langu. " N次 " 其他关联表... ENDLOOP.优化后:
" 一次读取所有物料基础信息 SELECT matnr mtart FROM mara INTO TABLE lt_mara FOR ALL ENTRIES IN lt_materials WHERE matnr = lt_materials-matnr. " 一次读取所有物料描述 SELECT matnr maktx FROM makt INTO TABLE lt_makt FOR ALL ENTRIES IN lt_materials WHERE matnr = lt_materials-matnr AND spras = sy-langu. " 转换为哈希表 DATA: lt_mara_hash TYPE HASHED TABLE OF ty_mara WITH UNIQUE KEY matnr, lt_makt_hash LIKE lt_mara_hash. lt_mara_hash = lt_mara. lt_makt_hash = lt_makt. " 循环中使用 READ TABLE WITH TABLE KEY LOOP AT lt_materials INTO ls_mat. READ TABLE lt_mara_hash INTO ls_mara WITH TABLE KEY matnr = ls_mat-matnr. READ TABLE lt_makt_hash INTO ls_makt WITH TABLE KEY matnr = ls_mat-matnr. ... ENDLOOP.效果:查询次数从 2×N 降到 2,性能提升与N成正比。
2.2FOR ALL ENTRIES的使用注意事项
- 去重:如果
lt_materials中有大量重复物料号,应先用SORT和DELETE ADJACENT去重,避免FOR ALL ENTRIES生成冗余的OR条件。 - 空表检查:如果
lt_materials为空,FOR ALL ENTRIES会被忽略,导致全表扫描。因此使用前必须判断:
IF lt_materials IS NOT INITIAL. SELECT ... FOR ALL ENTRIES IN lt_materials ... ENDIF.- 性能诊断:使用事务码
ST05查看生成的SQL语句,确认是否合理使用了索引。
2.3 数据库索引设计
即使使用了批量查询,如果查询条件没有索引,数据库仍会全表扫描。对于自定义的校验表(如ZMD_CHECK_RESULT),建议创建以下索引:
| 索引字段 | 用途 |
|---|---|
OBJECT_KEY + RULE_ID | 快速查询某个物料的特定规则错误 |
STATUS + CREATE_DATE | 统计待处理问题、清理旧数据 |
RESPONSIBLE + STATUS | 按责任人查看待办清单 |
对于SAP标准表,尽量利用其已有索引。例如MARC的主键是MANDT+MATNR+WERKS,查询MATNR时走索引;但只查MATNR而不指定WERKS,优化器可能选择全扫描。这时可以考虑创建自定义索引(SE11)仅包含MATNR,但需谨慎评估对插入性能的影响。
三、规则执行层优化:预编译与结果复用
3.1 规则表达式预编译
如果规则使用字符串表达式(如MATNR LENGTH EQ 18),每次校验都要解析字符串,成本较高。可以在程序启动时将规则加载到内存,并预编译为可执行的形式(如生成函数指针或存储解析后的语法树)。
简化方案:将规则类型(字段校验、存在性检查)与参数分开存储,减少运行时解析。
" 规则表结构优化 FIELD_NAME TYPE fieldname, " 字段名 OPERATOR TYPE char10, " EQ, NE, LENGTH, REGEX EXPECT_VALUE TYPE string, " 期望值这样,执行时直接使用字段名和操作符,无需解析表达式字符串。
3.2 批量结果缓存
对于同一主数据在同一批次中多次校验(例如多个规则都需查询同一张关联表),可以将中间结果存储在共享内存中。但要注意缓存失效策略。
一个简单的实现:在程序级别使用静态内表缓存已查询过的对象。
CLASS lcl_cache DEFINITION. PUBLIC SECTION. CLASS-DATA: BEGIN OF cache_mara, matnr TYPE matnr, mtart TYPE mtart, END OF cache_mara, mt_cache TYPE HASHED TABLE OF cache_mara WITH UNIQUE KEY matnr. CLASS-METHODS: get_mtart IMPORTING iv_matnr TYPE matnr RETURNING VALUE(rv_mtart) TYPE mtart. ENDCLASS. METHOD get_mtart. READ TABLE mt_cache INTO DATA(ls_cache) WITH TABLE KEY matnr = iv_matnr. IF sy-subrc = 0. rv_mtart = ls_cache-mtart. RETURN. ENDIF. SELECT SINGLE mtart FROM mara INTO rv_mtart WHERE matnr = iv_matnr. ls_cache-matnr = iv_matnr. ls_cache-mtart = rv_mtart. INSERT ls_cache INTO TABLE mt_cache. ENDMETHOD.注意:静态缓存在整个程序生命周期内有效,对于单次批量校验是安全的;但对于常驻服务(如RFC多次调用),需设计缓存超时或手动清理。
四、并发调度层优化:并行处理
4.1 何时使用并行处理?
- 数据量超过5万行。
- 单个物料校验耗时较短(毫秒级),但总行数很大,CPU成为瓶颈。
- 服务器拥有多个应用服务器实例或多个CPU核心。
4.2 SAP并行处理实现方式
方式一:使用异步RFC(STARTING NEW TASK)
将物料表按范围分割,每个工作进程处理一个子集。
DATA: lt_tasks TYPE TABLE OF char8, lt_results TYPE TABLE OF ty_result. " 分割物料表 DATA(lv_chunk_size) = 5000. DATA(lv_chunks) = ceil( lines( lt_materials ) / lv_chunk_size ). DO lv_chunks TIMES. DATA(lv_offset) = ( sy-index - 1 ) * lv_chunk_size + 1. DATA(lv_end) = sy-index * lv_chunk_size. DATA(lt_chunk) = VALUE #( FOR i = lv_offset TO lv_end ( lt_materials[ i ] ) WHERE ( table_line IS NOT INITIAL ) ). DATA(lv_task_name) = |TASK_{ sy-index }|. CALL FUNCTION 'ZMD_CHECK_CHUNK' STARTING NEW TASK lv_task_name EXPORTING it_materials = lt_chunk IMPORTING et_results = DATA(lt_chunk_results) EXCEPTIONS communication_failure = 1 MESSAGE lv_msg system_failure = 2 MESSAGE lv_msg. APPEND lv_task_name TO lt_tasks. ENDDO. " 等待所有任务完成并收集结果 LOOP AT lt_tasks INTO lv_task_name. WAIT UNTIL function 'ZMD_CHECK_CHUNK' ON TASK lv_task_name IS FINISHED. RECEIVE RESULTS FROM FUNCTION 'ZMD_CHECK_CHUNK' IMPORTING et_results = lt_chunk_results EXCEPTIONS ... APPEND LINES OF lt_chunk_results TO lt_results. ENDLOOP.方式二:使用SPBT框架(老式并行处理)
事务码SPBT定义服务器组,调用SPBT_INITIALIZE和SPBT_DISPATCH。配置较复杂,但稳定性高,适合超大批量。
4.3 并行度设置原则
- 并行进程数不超过系统可用对话进程数的70%。
- 对数据库敏感的规则(如大量
FOR ALL ENTRIES查询),并行度不宜过高,否则可能锁表或耗尽数据库连接。 - 测试调优:从2开始逐步增加,观察系统负载和完成时间。
五、监控告警体系
5.1 关键性能指标(KPI)采集
在ZMD_CHECK_LOG表中增加以下字段:
| 字段 | 说明 |
|---|---|
SELECT_TIME | 数据读取耗时(微秒) |
RULE_EVAL_TIME | 规则执行耗时 |
PARALLEL_DEGREE | 使用的并行进程数 |
MEMORY_USAGE | 内表占用内存(估计值) |
这些数据可用于性能趋势分析和容量规划。
5.2 实时告警实现
使用SAP标准事务码ALM或自定义后台作业,监控以下阈值:
| 监控项 | 阈值 | 动作 |
|---|---|---|
| 单次批量校验耗时 > 30分钟 | 超时 | 发送邮件给运维,同时作业自动中断 |
| 错误率 > 20% | 比率异常 | 暂停后续校验,通知数据治理团队 |
| 实时校验响应时间 > 2秒 | 慢查询 | 记录慢SQL,触发性能分析 |
邮件告警示例:
DATA: lv_subject TYPE so_obj_des, lv_text TYPE string. lv_subject = '【SAP校验工具告警】批量校验超时'. lv_text = |批次{ lv_run_id } 执行超过30分钟,请检查系统负载和规则配置|. PERFORM send_mail USING lv_subject lv_text.六、日志排查方案
6.1 分级日志记录
为便于排查问题,在关键代码点插入日志:
- INFO:批次开始/结束、分块数量、并行度。
- DEBUG:每条规则的匹配过程、临时变量值(仅开发环境)。
- ERROR:数据库错误、RFC通信失败、规则表达式解析异常。
日志表ZMD_SYS_LOG结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| TIMESTAMP | DEC15 | 时间戳(精确到微秒) |
| LEVEL | CHAR1 | I/D/E |
| RUN_ID | CHAR20 | 关联的校验批次(如有) |
| TASK_NAME | CHAR8 | 并行任务名 |
| MSG_TEXT | STRING | 日志内容 |
6.2 错误追踪ID
对于实时校验(RFC/OData),客户端传入一个唯一TRACE_ID(如UUID),工具在处理过程中将此ID写入所有日志和结果记录。用户报错时提供此ID,运维人员可快速过滤出相关日志。
METHOD zif_check~check_material. DATA(lv_trace_id) = iv_trace_id. WRITE: / 'Trace ID:', lv_trace_id. " 或写入日志表 ENDMETHOD.6.3 慢查询定位
使用事务码ST05激活SQL跟踪,执行一次慢速校验,停止跟踪后分析耗时最长的SQL语句。常见优化点:
- 缺少索引 → 创建索引。
FOR ALL ENTRIES驱动表过大 → 先对驱动表去重、减少行数。- 使用了
SELECT *→ 改为只选需要的字段。
七、常见运维问题与解决方案速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 批量校验长时间未结束 | 某条规则全表扫描,或并行度太高导致锁等待 | 查看ST05跟踪,检查锁表情况SM12 | 优化慢查询;降低并行度;将大表拆分为多个小批次 |
| 实时校验导致MM01卡死 | 校验代码中有COMMIT WORK或WAIT | 检查校验函数中是否有数据库提交 | 移除COMMIT;将非必要校验移至后台 |
内存不足(MEMORY_NO_MORE) | 一次性加载了过多数据到内表 | 使用AL21查看程序内存占用 | 改为分块处理,或增大abap/heap_area_total参数 |
| 规则修改后不生效 | 未清除全局缓存或程序仍在运行旧代码 | 检查规则版本表中IS_ACTIVE标志 | 重新激活程序;重启批量作业 |
| 结果表中重复记录 | 同一批次被多次执行 | 查看后台作业调度是否重复 | 增加幂等性检查:按RUN_ID+OBJECT_KEY+RULE_ID唯一约束 |
八、上线前最后检查清单(补充篇)
- 在测试环境中使用生产数据量的副本执行压力测试。
- 配置后台作业自动清理3个月前的历史日志和结果表。
- 为关键事务码(如结果报表、规则维护)分配权限角色,避免误操作。
- 准备一份应急预案:当校验工具出现严重故障时,如何临时绕过校验(如修改增强开关变量)。
- 与BASIS团队确认系统资源:对话进程数、内存上限、数据库连接数。
九、总结
性能优化不是一蹴而就的,而是贯穿开发、测试、运维全周期的持续改进过程。本文总结的三大层次优化手段——批量读取、缓存复用、并行处理——可以帮助校验工具承受十万级甚至百万级的数据压力。同时,完善的监控告警和日志排查方案,能够让你在问题发生的第一时间发现并定位根因。
记住一条黄金法则:先让代码正确运行,再让它快速运行,最后让它稳定运行。希望本系列的五篇文章能帮助你在SAP主数据校验工具的建设中少走弯路,构建出高效、可靠的数据质量底座。
💬 你在批量校验中遇到过最离奇的性能问题是什么?最后是如何解决的?欢迎留言讨论。
作者:你的SAP学习伙伴
版本记录:2026年6月