性能优化效果验证与回归测试:优化前后性能对比、业务逻辑兼容性校验方案
博客简介:讲解性能优化后的验证流程,涵盖优化前后响应时间、内存占用、CPU 使用率的量化对比方法,以及业务逻辑正确性回归测试、边界场景兼容测试的核心要点,梳理优化导致的业务逻辑异常、数据错误等次生问题的排查方案,保障优化效果符合预期且不影响原有业务。
📖 写在前面
性能优化是个"改完代码就想跑"的过程------SAT 显示 DB Time 降了 90%,你迫不及待想上线。
但别急。
你有没有想过这些问题:
- 把循环内 SELECT 改成了 FOR ALL ENTRIES,但 FATE 空表行为呢?内表为空时它会返回整张表的数据!
- 把
SELECT *改成了指定字段,但漏掉了某个业务逻辑依赖的字段,现在跑起来值为空! - 加了一个复合索引,新索引的字段顺序和原有其他查询的匹配度怎么样?会不会让别的程序变慢?
- 优化完 SAT 显示 48 秒,但用户反馈数据和之前不一样了?
优化不是"改完代码就完事",而是"改完代码 → 验证性能没坏 → 验证逻辑没坏 → 再考虑上线"。
#mermaid-svg-ahCnZJ5ZSAjx7SBo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .error-icon{fill:#552222;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .marker.cross{stroke:#333333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo p{margin:0;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .cluster-label text{fill:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .cluster-label span{color:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .cluster-label span p{background-color:transparent;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .label text,#mermaid-svg-ahCnZJ5ZSAjx7SBo span{fill:#333;color:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .node rect,#mermaid-svg-ahCnZJ5ZSAjx7SBo .node circle,#mermaid-svg-ahCnZJ5ZSAjx7SBo .node ellipse,#mermaid-svg-ahCnZJ5ZSAjx7SBo .node polygon,#mermaid-svg-ahCnZJ5ZSAjx7SBo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .rough-node .label text,#mermaid-svg-ahCnZJ5ZSAjx7SBo .node .label text,#mermaid-svg-ahCnZJ5ZSAjx7SBo .image-shape .label,#mermaid-svg-ahCnZJ5ZSAjx7SBo .icon-shape .label{text-anchor:middle;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .rough-node .label,#mermaid-svg-ahCnZJ5ZSAjx7SBo .node .label,#mermaid-svg-ahCnZJ5ZSAjx7SBo .image-shape .label,#mermaid-svg-ahCnZJ5ZSAjx7SBo .icon-shape .label{text-align:center;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .node.clickable{cursor:pointer;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .arrowheadPath{fill:#333333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ahCnZJ5ZSAjx7SBo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ahCnZJ5ZSAjx7SBo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ahCnZJ5ZSAjx7SBo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .cluster text{fill:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .cluster span{color:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ahCnZJ5ZSAjx7SBo rect.text{fill:none;stroke-width:0;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .icon-shape,#mermaid-svg-ahCnZJ5ZSAjx7SBo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .icon-shape p,#mermaid-svg-ahCnZJ5ZSAjx7SBo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .icon-shape .label rect,#mermaid-svg-ahCnZJ5ZSAjx7SBo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ahCnZJ5ZSAjx7SBo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ahCnZJ5ZSAjx7SBo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ahCnZJ5ZSAjx7SBo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🔧 编码实现
📊 性能量化对比
SAT / STAT / ST05
✅ 业务逻辑回归
结果对比 / 功能测试 / 事务完整性
🔍 边界场景测试
并发 / 锁 / 特殊用户 / 配置差异
📋 次生问题排查
数据不一致 / 其他程序变慢 / 字段缺失
🚀 上线 + 灰度 + 监控
本篇学习目标
- 掌握 SAT 并排对比、STAT Time 分解对比、ST05 Top SQL 对比三种性能量化方法
- 学会设计"等价类 + 边界值"的业务逻辑回归测试用例
- 了解 FATE 空表、Buffer 绕过、索引顺序等容易引入 Bug 的优化操作
- 掌握并发、锁、特殊用户、配置差异等边界场景的测试方法
- 掌握次生问题的快速排查思路
- 建立从开发到上线的完整验证 Checklist
前置阅读 :强烈建议读完第七篇(全流程排查实战)和第一~六篇专项优化篇。本篇大量引用第七篇 ZORDER_REPORT 的代码改动做案例。
适用版本:SAP NetWeaver 7.51+
一、为什么验证是必须的 ------ 三个真实踩坑案例
在讲方法之前,先分享三个我亲眼见过的"优化反而搞出问题"的案例。这些不是极端情况,都是正常开发中很容易犯的错。
1.1 案例一:FATE 空表 Bug 导致报表多了 3000 行
abap
" ❌ 改动前(OK):
LOOP AT gt_orders INTO ls_order.
SELECT SINGLE * FROM vbap INTO ls_item
WHERE vbeln = ls_order-vbeln.
APPEND ls_result TO gt_result.
ENDLOOP.
" 如果 gt_orders 为空 → 循环不执行 → gt_result 也是空的 ✅
" ❌ 改动后(Bug!):
SELECT * FROM vbap INTO TABLE gt_items
FOR ALL ENTRIES IN gt_orders
WHERE vbeln = gt_orders-vbeln.
" 💣 如果 gt_orders 为空 → FATE 返回 vbap 表的**所有数据**!
" 那天用户选了一个无数据的日期范围 → 报表拉了全表 50000 行!
" ALV 渲染爆了 → WP 内存爆了 → WP 被 Kill → 连锁反应
根因 :改代码时忘了加 IF gt_orders IS NOT INITIAL. 空表保护。
💡 回归测试怎么发现? 如果有一条测试用例是"选择条件查不到数据",就会发现报表应该显示"无数据"但实际上显示了全表。
1.2 案例二:加了索引反而让另一个查询变慢
加的索引 (ERDAT, KUNNR, VBELN) 本意是优化报表,但另一个高频事务里的查询变慢了:
abap
SELECT SINGLE * FROM vbak INTO ls_vbak
WHERE vbeln = gs_order-vbeln.
" 原来走主键索引 PRIMARY(vbeln) → 很快
" 新索引也覆盖了 VBELN,DB 优化器可能决定走新索引
" 但新索引先按 ERDAT 排序 → VBELN 不是第一排序字段 → 查找效率反而更低
" 结果:5ms → 50ms(单次虽少,但该事务每秒跑上千次)
💡 回归测试怎么发现? 把 TOP 20 高频事务也纳入回归范围,跑一次 STAT 看 Time 分布就能发现某事务的 DB Time 突然涨了。
1.3 案例三:SELECT * 改指定字段 → 漏掉了业务依赖的隐含字段
abap
" ❌ 改动前:
SELECT * FROM vbap INTO ls_item
FOR ALL ENTRIES IN gt_orders WHERE vbeln = gt_orders-vbeln.
" ❌ 改动后(漏了 MEINS 字段!):
SELECT vbeln posnr matnr netwr menge werks chargi
FROM vbap INTO TABLE gt_items FOR ALL ENTRIES IN gt_orders
WHERE vbeln = gt_orders-vbeln.
" 后面有这段逻辑:
WRITE: ls_item-netwr UNIT ls_item-meins.
" 💣 ls_item-meins 现在是空的 → 输出的金额没有单位!
💡 回归测试怎么发现? 如果完整跑一遍后人工检查输出格式,就能看到金额后面缺少单位。
1.4 三个案例的共同教训
| 教训 | 对应验证方法 |
|---|---|
| 改了查询逻辑 → 必须加边界 Case 测试(空表、单条、极端值) | 等价类测试用例 |
| 加了索引 → 必须回归 TOP 20 高频事务(防止"按下葫芦浮起瓢") | ST05 Top SQL 全局对比 |
| 改了 SELECT 字段 → 必须全局搜索所有引用点 | SE80 Where-Used List |
🚨 性能优化的 Bug 往往是隐蔽的------它不会让程序 Dump,只会让结果不对或让别的地方变慢。
二、性能量化对比方法 ------ 用数据说话
2.1 三种方法总览
#mermaid-svg-81OhAn4LcmtHBCZs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-81OhAn4LcmtHBCZs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-81OhAn4LcmtHBCZs .error-icon{fill:#552222;}#mermaid-svg-81OhAn4LcmtHBCZs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-81OhAn4LcmtHBCZs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-81OhAn4LcmtHBCZs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-81OhAn4LcmtHBCZs .marker.cross{stroke:#333333;}#mermaid-svg-81OhAn4LcmtHBCZs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-81OhAn4LcmtHBCZs p{margin:0;}#mermaid-svg-81OhAn4LcmtHBCZs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-81OhAn4LcmtHBCZs .cluster-label text{fill:#333;}#mermaid-svg-81OhAn4LcmtHBCZs .cluster-label span{color:#333;}#mermaid-svg-81OhAn4LcmtHBCZs .cluster-label span p{background-color:transparent;}#mermaid-svg-81OhAn4LcmtHBCZs .label text,#mermaid-svg-81OhAn4LcmtHBCZs span{fill:#333;color:#333;}#mermaid-svg-81OhAn4LcmtHBCZs .node rect,#mermaid-svg-81OhAn4LcmtHBCZs .node circle,#mermaid-svg-81OhAn4LcmtHBCZs .node ellipse,#mermaid-svg-81OhAn4LcmtHBCZs .node polygon,#mermaid-svg-81OhAn4LcmtHBCZs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-81OhAn4LcmtHBCZs .rough-node .label text,#mermaid-svg-81OhAn4LcmtHBCZs .node .label text,#mermaid-svg-81OhAn4LcmtHBCZs .image-shape .label,#mermaid-svg-81OhAn4LcmtHBCZs .icon-shape .label{text-anchor:middle;}#mermaid-svg-81OhAn4LcmtHBCZs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-81OhAn4LcmtHBCZs .rough-node .label,#mermaid-svg-81OhAn4LcmtHBCZs .node .label,#mermaid-svg-81OhAn4LcmtHBCZs .image-shape .label,#mermaid-svg-81OhAn4LcmtHBCZs .icon-shape .label{text-align:center;}#mermaid-svg-81OhAn4LcmtHBCZs .node.clickable{cursor:pointer;}#mermaid-svg-81OhAn4LcmtHBCZs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-81OhAn4LcmtHBCZs .arrowheadPath{fill:#333333;}#mermaid-svg-81OhAn4LcmtHBCZs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-81OhAn4LcmtHBCZs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-81OhAn4LcmtHBCZs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-81OhAn4LcmtHBCZs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-81OhAn4LcmtHBCZs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-81OhAn4LcmtHBCZs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-81OhAn4LcmtHBCZs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-81OhAn4LcmtHBCZs .cluster text{fill:#333;}#mermaid-svg-81OhAn4LcmtHBCZs .cluster span{color:#333;}#mermaid-svg-81OhAn4LcmtHBCZs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-81OhAn4LcmtHBCZs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-81OhAn4LcmtHBCZs rect.text{fill:none;stroke-width:0;}#mermaid-svg-81OhAn4LcmtHBCZs .icon-shape,#mermaid-svg-81OhAn4LcmtHBCZs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-81OhAn4LcmtHBCZs .icon-shape p,#mermaid-svg-81OhAn4LcmtHBCZs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-81OhAn4LcmtHBCZs .icon-shape .label rect,#mermaid-svg-81OhAn4LcmtHBCZs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-81OhAn4LcmtHBCZs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-81OhAn4LcmtHBCZs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-81OhAn4LcmtHBCZs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} STAT 快速迭代
5分钟出结果
开发期日常检查
SAT 并排对比
正式验证
给 Code Review 的硬数据
ST05 Top SQL 确认
DB 侧效果
索引是否真的生效
2.2 方法一:SAT Optimization Mode(最完整)
操作步骤:跑优化前的 Trace → Export 为 BEFORE.sat → 跑优化后的 Trace → Ctrl 同时选中两个 Trace → 右键 Compare → Time Analysis。
第七篇 ZORDER_REPORT 的实际对比结果:
| Metric | BEFORE | AFTER | Delta |
|---|---|---|---|
| Total Runtime | 492.3 s | 48.1 s | 🟢 -444.2 s (-90.2%) |
| DB Time | 421.3 s (85.6%) | 22.5 s (46.8%) | 🟢 -398.8 s (-94.7%) |
| ABAP Time | 61.2 s (12.4%) | 21.5 s (44.7%) | 🟢 -39.7 s (-64.9%) |
| Buffer Time | 0.0 s (0.0%) | 11.2 s (23.3%) | 🔵 缓冲终于生效了 |
| Memory Peak | 187 MB | 32 MB | 🟢 -155 MB (-83%) |
| Executed Statements | 812,456 | 143,200 | 🟢 -82% |
SAT 对比的关键看点:
- Total Time 降了多少 → 整体优化效果
- DB Time 占比变化 → After 中 DB 降到 50% 以下就安全了
- Memory Peak → 有无内存泄漏风险
- Buffer Time 有无从 0 变有值 → 说明缓冲用上了
🚨 SAT 对比只适用于同一程序、相同输入条件。输入条件不同会导致对比失真。
2.3 方法二:STAT Single Time Analysis(看 Time 分解)
STAT 比 SAT 轻量,跑一次 5 分钟就有结果,适合快速迭代。
| Time Component | BEFORE | AFTER | 变化 |
|---|---|---|---|
| Total Response Time | 95.3 s | 23.1 s | 🟢 -72s |
| ├── DB Time | 82.1 s | 10.8 s | 🟢 -71s |
| ├── ABAP Processor Time | 11.8 s | 9.2 s | 🟢 -2.6s |
| └── Other | 1.4 s | 3.1 s | +1.7s |
💡 如果 After 中 ABAP Processor Time 占比反而上去了 → DB 瓶颈解除后,原本次要的 ABAP 循环变成了新瓶颈。
2.4 方法三:ST05 Top SQL 对比(看 DB 侧效果)
| SQL | BEFORE → AFTER | 原因 |
|---|---|---|
| SELECT vbak (主查询) | 98s → 3.2s | 加了复合索引 |
| SELECT makt (物料描述) | 20s → 2.1s | 循环内 → FATE 批量 + 缓冲 |
| SELECT vbbe (交货计划) | 34s → 1.9s | 循环内 SELECT → FATE 批量 |
| 合计 | 218s → 19s | 降 91% |
必须检查的点:
- Before 是
TABLE ACCESS FULL→ After 应变为TABLE ACCESS BY INDEX RANGE SCAN - After 的 Top 10 里有没有冒出新面孔(可能原长查询释放了 DB 连接)
2.5 量化对比报告模板(给管理层/业务同事看)
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 运行时间 | 492 秒 | 23 秒 | 21 倍 🎊 |
| DB Time 占比 | 85.6% | 46.8% | 瓶颈已解除 |
| 内存峰值 | 187 MB | 32 MB | -83% |
| 数据正确性 | - | - | ✅ 8152 行完全一致 |
💡 给管理层的报告要简洁(重点数字、百分比、结论);给 Code Review 的报告可以更技术一些(加 SAT 截图、ST05 Explain Plan)。
三、业务逻辑正确性回归测试 ------ 确保"快了但没坏"
3.1 回归测试的三层结构
| 层次 | 测试什么 | 怎么测 |
|---|---|---|
| 第一层:结果对比 | 输出数据是否完全一致 | OLD vs NEW 逐行逐字段 VLOOKUP 对比 |
| 第二层:功能测试 | 各分支逻辑是否正常 | 等价类划分:正常/空/极端值 |
| 第三层:事务完整性 | 更新类操作的 DB 一致性 | DB10n Consistency Check |
3.2 第一层:结果对比自动化
手工对比适用于小数据量。大数据量建议写 ABAP 对比程序,核心逻辑:
abap
" 核心:主键 BINARY SEARCH + 逐字段值对比 + 容忍浮点误差
SORT lt_old_data BY key_field.
SORT lt_new_data BY key_field.
LOOP AT lt_old_data INTO DATA(ls_old).
READ TABLE lt_new_data INTO DATA(ls_new)
WITH KEY key_field = ls_old-key_field BINARY SEARCH.
IF sy-subrc = 0.
" 逐字段对比
IF ls_old-field_01 <> ls_new-field_01 OR
ABS( ls_old-field_04 - ls_new-field_04 ) > 0.01.
APPEND VALUE #( key_field = ls_old-key_field ... ) TO lt_diff_report.
ENDIF.
ELSE.
" NEW 里没有这行 → 丢数据了!
APPEND VALUE #( key_field = ls_old-key_field
field_name = 'ROW_MISSING_IN_NEW' ) TO lt_diff_report.
ENDIF.
ENDLOOP.
3.3 第二层:功能测试用例设计(等价类划分)
以 ZORDER_REPORT 为例:
| Case | 输入条件 | 预期结果 | 覆盖的逻辑分支 |
|---|---|---|---|
| TC01 | 正常日期范围(3个月) | 8152 行正常输出 | 正常路径 + FATE 批量查询 |
| TC02 🔑 | 未来日期(没有订单) | 无数据输出(空结果) | FATE 空表保护分支 |
| TC03 | 只有 1 天范围 | 少量数据正常输出 | 极端小数据集 |
| TC04 | 超大范围(1 年) | 正常输出但运行时间在合理范围 | 压力测试 |
| TC05 | 订单类型 Filter | 只输出特定类型的订单 | IN s_auart 的 WHERE 条件 |
| TC06 | 选了不存在的订单号 | 空结果 | WHERE 未命中 → 结果为空 |
💡 最关键的是 TC02(FATE 空表保护)------案例一的 Bug 就是没测这个 Case。
3.4 第三层:事务完整性测试
如果优化的是更新类程序(BAPI 批量写、BDC、财务过账等),必须验证 DB 数据完整性:
| 优化类型 | 需要检查的完整性项 |
|---|---|
| BAPI 批量写入 | DB10n Consistency Check;BKPF/BSEG 凭证头段能否对上;余额总计能否 RECONCILE |
| 主数据维护 | 改 SELECT 字段会不会导致 UPDATE 时清空不该清空的字段 |
| 数据同步 (RFC/IDoc) | 发送方和接收方数据量能否对上;失败重发有无导致重复 |
| Archive 归档 | 归档后能否正常查询;归档表和原表数据总量能否对上 |
四、边界场景与兼容性测试
4.1 并发场景测试
为什么重要:单用户跑 15 秒,50 并发最慢可能 2 分 45 秒------因为有锁等待。
并发测试关键指标:
| 指标 | 正常范围 | 预警信号 |
|---|---|---|
| 平均响应时间 | ≤ 单用户 × 1.5 | > 单用户 × 3 |
| 响应时间标准差 (CV) | < 30% | > 60%(有长尾请求) |
| Enqueue 等待时间 | < 总时间的 10% | > 30%(锁竞争严重) |
| DB 连接等待 | < 最大值的 70% | > 90%(连接池不够) |
| Work Process 使用率 | 峰值 < 80% | 持续 > 95% |
4.2 锁场景测试
性能优化经常涉及锁策略调整。在并发测试期间同时打开 SM12 和 SM66 实时观察,重点关注:
- SM12 → 有没有大量锁排队(Lock Entries > 100)
- SM66 → 有没有进程卡在 Enqueue 步骤
4.3 特殊用户场景
| 特殊场景 | 原因 | 测试方法 |
|---|---|---|
| 权限特别多的用户 | SAP_ALL 权限的 Auth Check 可能慢几倍 | 用 SAP_ALL 和普通用户各跑一遍对比 |
| 多语言用户 | 文本读取因语言回退变慢 | 用英文、日文等非默认语言跑一遍 |
| 参数 ID 特殊的用户 | 硬编码了参数值导致特殊用户走错分支 | 检查 SY-UNAME 相关的硬编码逻辑 |
4.4 配置差异检查
| 配置项 | 检查方法 | 影响 |
|---|---|---|
| 表缓冲设置 | SE13 → Technical Settings → Buffering | 直接影响 SELECT 性能 |
| 数据库统计信息 | DB02 → Statistics | 统计不准 → 优化器选错执行计划 |
| Profile 参数 | RZ11 检查 rsdb/max_blocking_factor 等 | 影响 FATE 的批量大小 |
| 数据量 | ST05 Trace 里的 ROWS 栏位 | 索引选择、执行计划完全不同 |
| 索引一致性 | SE14 → 比较不同系统的索引 | 漏索引导致全表扫描 |
五、次生问题排查 ------ 优化了 A,搞坏了 B?
5.1 次生问题类型一:数据不一致
典型场景:批量更新跳过了逐行检查。
abap
" ❌ 优化前(每行都做检查 + 更新,慢但安全)
LOOP AT lt_items INTO ls_item.
SELECT stock FROM zmaterial INTO lv_stock WHERE matnr = ls_item-matnr.
IF lv_stock < ls_item-qty. CONTINUE. ENDIF.
UPDATE zmaterial SET stock = stock - ls_item-qty.
ENDLOOP.
" ❌ 优化后(批量检查 + 批量更新 → Bug!)
SELECT matnr stock INTO TABLE lt_stock FROM zmaterial
FOR ALL ENTRIES IN lt_items WHERE matnr = lt_items-matnr.
LOOP AT lt_items INTO ls_item.
" 检查和更新之间,其他用户也在扣减库存 → 库存变负数!
APPEND ls_item TO lt_batch_update.
ENDLOOP.
修复 :检查与更新之间加 SELECT ... FOR UPDATE,或使用乐观锁。
5.2 次生问题类型二:其他程序变慢了
你给 ZORDER_HEADER 加了索引,ZORDER_REPORT 从 60 秒降到 3 秒。但 ZBILLING_RUN 从 18 秒变成了 2 分 15 秒------因为 DB 优化器选错了索引。
排查方法:
- STAD 找变慢程序的 Top SQL
- ST05 对比新旧执行计划的 Rows Read
- 确认后,在代码中用
%_HINTS INDEX指定原有索引
5.3 次生问题类型三:隐藏字段缺失
abap
" 把 SELECT * 改成指定字段后,调用方可能还在依赖被删掉的字段
ASSIGN ls_header TO ls_ref->* CASTING.
" 如果 ls_header 结构不完整,CASTING 会出错
检查方法:SE80 → Where-Used List 全局搜索;SAT Runtime Error 检查;SCI/SCIF 静态检查。
5.4 次生问题排查工具清单
| 工具 | 用途 | 操作要点 |
|---|---|---|
| SE80 | 全局搜索被改动对象的使用方 | 输入表名/函数名 → Where-Used List |
| SCI/SCIF | 静态代码检查 | 新建检查变体 → 全量扫描 |
| SAT Optimization Mode | 新旧代码并排对比 + Runtime Errors | 勾选 Collect Runtime Errors |
| ST03N | 系统级负载对比 | 对比优化前后同一天的时间分布 |
| SM13/DB12 | 数据库日志 | 重点看 Deadlock 和 Rollback 条目 |
| DB10n | 一致性检查 | 定时跑,或在优化后立即手动跑 |
六、验证流程 Checklist ------ 一页纸搞定所有验证环节
6.1 完整版(20 步)
| 阶段 | # | 具体动作 | 输出物 | 通过标准 |
|---|---|---|---|---|
| ① 开发自测 | 1 | STAT 新旧代码各跑 10 次 | 响应时间对比表 | 新 < 旧 × 0.7 |
| 2 | ST05 新旧各 Trace 一次 | Top SQL 对比表 | DB 时间显著下降,索引走对 | |
| 3 | SAT Optimization Mode 并排对比 | CPU/Memory/DB 三栏对比 | 所有关键指标下降或持平 | |
| 4 | SAT Runtime Error 检查 | Error 列表 | 0 Error / 0 Warning | |
| 5 | SCI 静态代码检查 | SCI 结果 | Critical=0, Warning≤3 | |
| ② 功能回归 | 6 | OLD vs NEW 结果对比 | 差异报告 | 差异记录数=0 |
| 7 | 等价类测试用例 | 测试用例执行表 | 100% Pass(空表 Case 必测!) | |
| 8 | DB10n Consistency Check | DB10n 报告 | 0 条不一致 | |
| 9 | 单元测试(如有) | Unit Test 报告 | 100% Pass | |
| ③ 边界场景 | 10 | 并发测试(50 后台作业同时跑) | 响应时间分布表 | CV<30%, Lock Wait<10% |
| 11 | 锁冲突注入测试 | 锁等待时间记录 | 锁等待增加 < 原时间 50% | |
| 12 | 特殊用户测试 | 对比报告 | 性能差异 < 20% | |
| 13 | 配置差异检查 | 差异对照表 | 关键配置一致 | |
| ④ 次生问题 | 14 | SE80 Where-Used List | 被影响程序清单 | 无关键业务程序被影响 |
| 15 | ST03N 系统负载对比 | 系统负载图 | 整体 CPU/DB 无显著上升 | |
| 16 | SM13/DB12 死锁回滚检查 | DB 日志摘要 | 无新增 Deadlock/Rollback | |
| 17 | DB10n 数据一致性 | 一致性报告 | 0 条不一致 | |
| ⑤ 上线前 | 18 | Code Review | CR 记录 | 至少 2 人 Approve |
| 19 | 生产灰度发布 | 灰度观察报告 | 灰度期无异常 | |
| 20 | 上线后 24 小时持续监控 | 监控记录 | 无新增告警 |
6.2 紧急修复简化版(红线不能碰)
🚨 紧急修复最低验证标准(必做!):
✅ STAT 新旧对比 → 确认性能真的提升了
✅ OLD vs NEW 结果对比 → 至少选 5 组真实数据跑一遍
✅ SAT Runtime Error 检查 → 不能引入新的 Short Dump
✅ SE80 Where-Used → 确认没改到公共接口的调用方
✅ 并发测试 → 至少 10 个并发跑一遍
✅ Code Review → 至少 1 个人看过
❌ 可以临时跳过的(但之后必须补做):
· SCI 静态检查
· 完整等价类测试用例
· 多语言/特殊用户测试
· DB10n 完整一致性检查(改成只查受影响的几张表)
七、总结与预告
本篇总结:优化不是终点,验证才是
#mermaid-svg-W1XnFXw5SdhcYXAY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W1XnFXw5SdhcYXAY .error-icon{fill:#552222;}#mermaid-svg-W1XnFXw5SdhcYXAY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W1XnFXw5SdhcYXAY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W1XnFXw5SdhcYXAY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W1XnFXw5SdhcYXAY .marker.cross{stroke:#333333;}#mermaid-svg-W1XnFXw5SdhcYXAY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W1XnFXw5SdhcYXAY p{margin:0;}#mermaid-svg-W1XnFXw5SdhcYXAY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY .cluster-label text{fill:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY .cluster-label span{color:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY .cluster-label span p{background-color:transparent;}#mermaid-svg-W1XnFXw5SdhcYXAY .label text,#mermaid-svg-W1XnFXw5SdhcYXAY span{fill:#333;color:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY .node rect,#mermaid-svg-W1XnFXw5SdhcYXAY .node circle,#mermaid-svg-W1XnFXw5SdhcYXAY .node ellipse,#mermaid-svg-W1XnFXw5SdhcYXAY .node polygon,#mermaid-svg-W1XnFXw5SdhcYXAY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W1XnFXw5SdhcYXAY .rough-node .label text,#mermaid-svg-W1XnFXw5SdhcYXAY .node .label text,#mermaid-svg-W1XnFXw5SdhcYXAY .image-shape .label,#mermaid-svg-W1XnFXw5SdhcYXAY .icon-shape .label{text-anchor:middle;}#mermaid-svg-W1XnFXw5SdhcYXAY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W1XnFXw5SdhcYXAY .rough-node .label,#mermaid-svg-W1XnFXw5SdhcYXAY .node .label,#mermaid-svg-W1XnFXw5SdhcYXAY .image-shape .label,#mermaid-svg-W1XnFXw5SdhcYXAY .icon-shape .label{text-align:center;}#mermaid-svg-W1XnFXw5SdhcYXAY .node.clickable{cursor:pointer;}#mermaid-svg-W1XnFXw5SdhcYXAY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W1XnFXw5SdhcYXAY .arrowheadPath{fill:#333333;}#mermaid-svg-W1XnFXw5SdhcYXAY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W1XnFXw5SdhcYXAY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W1XnFXw5SdhcYXAY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W1XnFXw5SdhcYXAY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W1XnFXw5SdhcYXAY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W1XnFXw5SdhcYXAY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W1XnFXw5SdhcYXAY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W1XnFXw5SdhcYXAY .cluster text{fill:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY .cluster span{color:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W1XnFXw5SdhcYXAY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W1XnFXw5SdhcYXAY rect.text{fill:none;stroke-width:0;}#mermaid-svg-W1XnFXw5SdhcYXAY .icon-shape,#mermaid-svg-W1XnFXw5SdhcYXAY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W1XnFXw5SdhcYXAY .icon-shape p,#mermaid-svg-W1XnFXw5SdhcYXAY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W1XnFXw5SdhcYXAY .icon-shape .label rect,#mermaid-svg-W1XnFXw5SdhcYXAY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W1XnFXw5SdhcYXAY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W1XnFXw5SdhcYXAY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W1XnFXw5SdhcYXAY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 识别问题
分析瓶颈
设计方案
编码实现
🔍 验证
上线
监控
没有验证的优化 = 盲飞。你以为解决了问题,实际上可能引入了新 Bug。验证环节投入产出比极高------花 1 天做验证,避免上线后花 3 天救火。
全系列回顾
到第九篇为止,ABAP 性能分析与优化系列核心技术篇 + 专项优化篇 + 实战篇全部完成:
核心技术篇(第 1-2 篇)
├── 第一篇:运行时性能基础入门
└── 第二篇:核心工具入门
专项优化篇(第 3-6 篇)
├── 第三篇:数据库访问性能优化
├── 第四篇:内存优化
├── 第五篇:业务逻辑层优化
└── 第六篇:接口与报表专项优化
实战与运维篇(第 7-9 篇)
├── 第七篇:全流程排查实战
├── 第八篇:生产系统监控与预警
└── 第九篇:效果验证与回归测试 ← 本篇
下一篇预告:《ABAP 性能优化常见误区与开发规范:10 类典型优化错误与前置规避方案》------本系列最后一篇,从源头减少 80% 的性能问题。
作者 :爱喝水的鱼丶
版本记录:2026 年 8 月
💬 你在性能优化验证中踩过哪些坑?有没有"测试环境好好的,一上线就崩"的经历?欢迎分享你的验证故事。