相关文章推荐
    2015年3月,今天无意翻到这篇写于2010年7月的文档,回想那时的工作,毕业3年初出茅庐的我面对接触不多的SAP+DB2竟敢操刀动斧,自信满满。
    虽然这过程一路坎坷,数次判断几乎全都被打脸验证,看着如小强般坚毅的我,哈哈~ 文档里带着情绪的措词十分逗笑,可以洞悉那时的心情。 
    ——如今5年过去,却久未闻硝烟,怀念ing~ 

优化目的 : ZCOR0015 能够迅速取出数据 , 并且不通过 MKPFMSEG 中间表 的处理 .

业务的接受度 : 1 个月跨度的报表 , 要在 5 分钟内返回结果 , 跑半年的报表 , 要在 1 小时内返回结果 .

结果 : ZCOR0015 统计一个月的数据 20090101~20090131

红色 为数据库层时间消耗 , 蓝色 ABAP 层时间消耗 .

1. 寻找问题根源 , 切入分析

先在 se38 里找出该程序 , 根据开发人员的测试 , 该程序的大部分的时间消耗在数据库层面 , 过后开发人员做了一个定时 job, 定期把 视图 WB2_V_MKPF_MSEG2 的数据导入 MKPFMSEG , 写程序的时候就直接从 MKPFMSEG 里取数据即可 , 这样暂时可以解决这个程序的缓慢问题 , 但是却把性能的隐患转移至 后台 job 程序上了 , 当前这个 job 跑起来非常消耗资源 , 并且也存在数据实时性的问题 ,

这次的调优目的就是彻底的解决这个问题 .

我一开始的假想 , 认为这性能的问题 , 无非就是 SQL 语句所走的执行计划匹配上不合理的索引 , 导致不合理的资源消耗 , 当然这个早早下的结论也没错 , 只是太宽泛了 , 起初我认为折腾一天就可以搞定 , 甚至是一下午 , 但是最终却消耗了 10 天的时间

没有全盘了解系统的细节结构、应用层面的逻辑 SAP 经验不足 , 却又自信满满的姿态去看这次的问题 , 其实还蛮可怕的 , 这次的事情 所覆盖的知识面完全超出了我的想象。

把这段存在性能问题的 sql abap 程序里抓出来 , 基本如下 :

SAP端:
    select a~matnr_i
    a~mblnr_i
    a~bwart_i
    * b~aufnr
    a~budat
    a~WERKS_I
    a~bukrs_i
    a~menge_i
    a~mjahr_i
    a~aufnr_i
    a~SHKZG_I
    INTO CORRESPONDING FIELDS OF TABLE g_t_result
    from WB2_V_MKPF_MSEG2 as a "inner join aufm as b on a~mblnr_i = b~mblnr and a~mjahr_i = b~mjahr and a~matnr_i = b~matnr
    where
    a~budat BETWEEN s_budat1 AND s_budat2 and
    a~bukrs_i = s_bukrs and
    a~werks_i = s_werks and
    a~mjahr_i = s_mjahr and
    a~bwart_i in ('101','102','122','123','161','162','Z09','Z10','Z11','Z12','Z21','Z22','Z01','Z02','601','602','653','654','261','262','531','532','543','544','961','962') AND
    a~matnr_i BETWEEN '000000000010000000' and '000000000019999999'.

改成 DB2 里的 SQL: ( 先暂时改成 count(*) 计数 , 优化完了再改为实际取的字段名 , 大体上执行计划不会有差异 .)

    select count(*)
    from sapprd."MKPF" T0001, sapprd."MSEG" T0002
    where
    T0002."MANDT"= T0001."MANDT"
    AND T0002."MBLNR"= T0001."MBLNR"
    AND T0002."MJAHR"= T0001."MJAHR"
    AND T0001."BUDAT" BETWEEN '20090101' AND '20090131'and
    T0002."BUKRS"='1000'and
    T0002."WERKS"='1000'and
    T0001."MJAHR"='2009'and
    T0002."BWART"in('101','102','122','123','161','162','Z09','Z10','Z11','Z12','Z21','Z22','Z01','Z02',
    '601','602','653','654','261','262','531','532','543','544','961','962') AND
    T0002."MATNR" BETWEEN '000000000010000000'and'000000000019999999';

执行计划如下 ,MKPF 上的索引是非常不合理的 , 光是扫描一个索引就消耗那么多的资源 , 从这里就判定出索引的不合理性 .

下面为这 MSEG,MKPF 2 张表在 SDX 里的状况 , 行数 , 统计信息更新时间 , 页数等等 . 与生产机的数据量基本同属于一个数量级 .

第二段是 MKPF 的两个索引状况 , 叶子数 , 索引字段的基数 , 统计信息时间 .( 还有 levels 一般不超过 3)

以下为这个执行计划消耗的 I/O ,CPU,BUFFERS 等等资源的状况 . ( 这里为估算值 , 并非真正的实际值 )

接着开始在 DB2 数据库层面优化 SQL.

单纯在数据库层面其实不复杂 , 就那么几个招式 :

1. SQL.

2. 建立合适的索引 ( 根据业务逻辑 + 需要把多少工作交给 ABAP 层面来做 , 减少 SQL 的谓词 来取一个平衡点 )

3. 收集统计信息 , 让数据库有稳定的执行计划 .

SQL 改写了一下

  • T0002 . "BUKRS" = '1000' and
  • T0002 . "WERKS" = '1000' and
  • T0001 . "MJAHR" = '2009' and
  • T0002 . "BWART" in ( '101' , '102' , '122' , '123' , '161' , '162' , 'Z09' , 'Z10' , 'Z11' , 'Z12' , 'Z21' , 'Z22' , 'Z01' , 'Z02' ,
  • '601' , '602' , '653' , '654' , '261' , '262' , '531' , '532' , '543' , '544' , '961' , '962' )
  • 上面这部分 繁琐的业务逻辑去掉 , 如果把这部分内容纳入 SQL 里的话 , 会明显的加重取回数据的负担 , 将会扫描更多的索引页 , 或是建立更多的索引 , 本身 SAP 的内表就很强大 , 把这部分逻辑交给 ABAP 层面做即可 , 先取回数据到内表里 , LOOP 循环一次 , 把结果筛选一遍 , 这样的思路应该是最优的 .

    然后再分别建两个索引 .

    附上建立的 ddl:

        CREATE INDEX "SAPPRD"."IX_MKPF_BUDATMANDT"
        ON "SAPPRD"."MKPF"
        ("BUDAT" ASC,
        "MANDT" ASC
        PCTFREE 10
        DISALLOW REVERSE SCANS
        COLLECT DETAILED STATISTICS;
        GRANT CONTROL ON INDEX "SAPPRD"."IX_MKPF_BUDATMANDT" TO USER "SAPPRD";
        CREATE INDEX "SAPPRD"."MSEG~GM"
        ON "SAPPRD"."MSEG"
        ("MANDT" ASC,
        "MBLNR" ASC,
        "MJAHR" ASC,
        "MATNR" ASC
        PCTFREE 10
        DISALLOW REVERSE SCANS
        COLLECT DETAILED STATISTICS;
        GRANT CONTROL ON INDEX "SAPPRD"." MSEG~GM " TO USER "SAPPRD";

    大致上解释下这几个参数 ,

    PCTFREE: 为填充因子 ,10 的含义是 遗留下 10% 的页空间 , 以供 insert or update 时不至于那么快导致页分裂 , 如果值太大的话 , 会导致扫描更多的索引页 , 增加成本 .

    DISALLOW REVERSE SCANS: 是否使用反转索引 , 其实在这是物理上的概念 , 并非索引就倒序了 , 只是避免热点块的发生 , 我们的 SAP 不存在那么高的并发 , 所以 DISALLOW 即可 .

    COLLECT DETAILED STATISTICS: 收集详细的统计信息 .

    接着在 DB2 里执行一下这个 SQL:

        select
        T0002.matnr,
        T0001.mblnr,
        T0002.bwart,
        T0001.budat,
        T0002.WERKS,
        T0002.bukrs,
        T0002.menge,
        T0001.mjahr,
        T0002.aufnr,
        T0002.SHKZG
        from sapprd."MKPF" T0001 inner join sapprd."MSEG" T0002 on
        T0002."MANDT"= T0001."MANDT"
        AND T0002."MBLNR"= T0001."MBLNR"
        AND T0002."MJAHR"= T0001."MJAHR"
        where T0001."BUDAT" BETWEEN '20090101' AND '20090201'and
        T0002."MATNR" BETWEEN '000000000010000000'and'000000000019999999';
    速度不错 , 两次索引扫描 , 然后 fetch 出数据 , 再做 Nested loop join,

    并且先 loop 的小表 MKPF, 成本都很低 , 这已经是最佳的执行计划以及索引选择了 , 这个时候我笑了 , 我认为问题已经完美解决 , 并且返回数据的速度都在个位数的秒级 .

    2. 再次碰到问题 , 转折点 .

    这时我把程序交给开发人员 , 我把思路说明了一遍 , 我说这个程序现在 1 个月数据量在 10 秒内即可出来 , 半年的数据量顶天也就 1 分钟 ,

    等开发人员把程序写入 ABAP 报表里了 , 跑了一下 , 我傻眼了 .... 一个月的数据量 , 竟然需要 10+ 分钟 ... 和我预期的完全 不一样 .

    脑袋里开始空白了 , 这是过于自信所带来的空白 ..

    开始扫荡式寻找问题点 , 数据库性能 , 数据库参数 ,AIX 性能 , 磁盘 存储 网络 ,ABAP 代码 .. 全都翻了一遍 ,

    起初发现 AIX 里的 TOPAS 显示

    hdisk4 busy% 程度非常高 , 高达 90+, 但是 CPU 显示的 user 不高 , 大部分 CPU 时间都是在 wait,IDLE 基本持平 90% 左右 ( 这时 SDX 的环境只有我一个人在用 )

    (tips: 后来事情结束后才恍然大悟 , 其实大量表扫描 +hash join 的特征 .)

    虽然我脑子里很清晰的知道 , 他们出问题的可能性不大 ,

    其实这时我心里很清楚, 由于我自身的瓶颈( SAP+DB2 的经验不足)其间的逻辑交互机制不清晰,单方面从数据库层面来判断问题,显然是捉襟见肘的。

    但是近乎自负的那种自信却抵触了我否定自己的念头。

    接着还是一味的在 AIX 操作系统层面寻找问题…… 并且还消耗了蛮多时间, 索性到最后, 还是回到数据库 啃了些文档, 打算在数据库层面寻找问题点。

  • select * from sysibmadm . TOP_DYNAMIC_SQL order by AVERAGE_EXECUTION_TIME_S desc fetch first 5 rows only ;
  • 上面的语句可以找出在共享池里的 SQL, 按执行时间排序 .( 这个手段我在 Oracle SQLServer 里倒是经常用 , 但是 DB2 比较陌生 ..)

    发现了从 ABAP 层送出的 SQL 语句 和我所认知的完全 不一样 , 这个时候开始有突破口了 .

    下面的代码为 ABAP 层面送给 DB2 的原句

    SELECT T_00 . "MATNR" , T_01 . "MBLNR" , T_00 . "BWART" , T_01 . "BUDAT" ,
    T_00 . "WERKS" , T_00 . "BUKRS" , T_00 . "MENGE" , T_01 . "MJAHR" ,
    T_00 . "AUFNR" , T_00 . "SHKZG"
    FROM
    SAPPRD . "MSEG" T_00 INNER JOIN SAPPRD . "MKPF" T_01 ON
    T_01 . "MANDT" = ?
    AND
    T_01 . "MANDT" = T_00 . "MANDT"
    AND
    T_01 . "MBLNR" = T_00 . "MBLNR"
    AND
    T_01 . "MJAHR" = T_00 . "MJAHR"
    WHERE
    T_00 . "MANDT" = ? AND
    T_01 . "BUDAT" BETWEEN ? AND ? AND
    T_00 . "MATNR" BETWEEN ? AND ?
    WITH UR
    -- OPTLEVEL( 10 ) -- QUERY_DEGREE( 1 ) -- LOCATION( ZCOR0015_1 , 125 ) -- SYSTEM( SDX , SAPPRD )'

    看了下执行计划 , 终于找到问题了 ,

    ABAP 层面送出的 SQL 语句 , 所导致的执行计划是非常烂的 , 又走回之前的 2 个索引 , 一个 MKPF~BUD, 一个是 MSEG~M,

    并且根据这个索引计算出来的连接方式为 HASH JOIN. 使用不合理的索引 , 带来的成本是非常高的 .

    这段程序和我所想传送给数据库的语句有 2 个不同的地方

    首先是 T_01 . "MANDT" = ?

    其次是 WITH UR -- OPTLEVEL( 10 ) -- QUERY_DEGREE( 1 ) -- LOCATION( ZCOR0015_1 , 125 ) -- SYSTEM( SDX , SAPPRD )'

    琢磨了下 MANDT 这个是 cline 代码 , 用来区分 800,900 的数据 ,SAP 自动给加上的 , 可以理解

    再者是这个 WITH UR 是锁级别 ,

    OPTLEVEL(10) 是优化级别 ,QUERY_DEGREE 是并行的参数 .

    为什么带这些东西后 , 这段程序的执行计划差异会那么大呢 ??

    我把『 ? 问号 都替换成了实际值 , 然后套入 SQL , 执行下面的语句 .

    而且还在我后来建立的 MKPF 索引里添加了 MANDT 字段 ,

    SELECT T_00 . "MATNR" , T_01 . "MBLNR" , T_00 . "BWART" , T_01 . "BUDAT" ,
    T_00 . "WERKS" , T_00 . "BUKRS" , T_00 . "MENGE" , T_01 . "MJAHR" ,
    T_00 . "AUFNR" , T_00 . "SHKZG"
    FROM
    SAPPRD . "MSEG" T_00 INNER JOIN SAPPRD . "MKPF" T_01 ON
    T_01 . "MANDT" = '900'
    AND
    T_01 . "MANDT" = T_00 . "MANDT"
    AND
    T_01 . "MBLNR" = T_00 . "MBLNR"
    AND
    T_01 . "MJAHR" = T_00 . "MJAHR"
    WHERE
    T_00 . "MANDT" = '900' AND
    T_01 . "BUDAT" BETWEEN '20090101' AND '20090201' AND
    T_00 . "MATNR" BETWEEN '000000000010000000' AND '000000000019999999'
    WITH UR
    -- OPTLEVEL( 10 ) -- QUERY_DEGREE( 1 ) -- LOCATION( ZCOR0015_1 , 125 ) -- SYSTEM( SDX , SAPPRD )'

    这样子速度非常快 , 并且执行计划也是正确的 , 我就纳闷了 . 为什么同样的程序 , 我提交给 DB2 的 与 ABAP 提交给 DB2 的相差那么 ?

    脑子再次迷糊 .

    3. 反复尝试 , 直至最后解决

    这时我想到了用 HINT 来解决问题 , ABAP 里写 HINT 强制走 NESTED LOOP, 指定索引

    尝试了一大堆 %_HINT 未果 , 仍然找不到匹配 DB2 使用的 HINT

    就连网上比较普遍的一个 HINT 也无果 , 没有实际作用 ,

    %_HINTS
    ADABAS
    'ORDERED'
    INFORMIX
    'ORDERED'
    MSSQLNT
    'OPTION FORCE ORDER'
    DB6
    '<NLJOIN><IXSCAN TABLE=''MSEG'' SAP_INDEX=''M''/>'
    DB6
    '<IXSCAN TABLE=''MKPF'' SAP_INDEX=''0''/></NLJOIN>' .

    我猜测或许 SAP 针对 DB2 这一块写的 HINT 程序 , 只是匹配的早前的 DB2 版本 , 在后续版本中已经废弃掉了 ,

    ( 我之所以会这么猜测 , 是因为我跟踪了 MB51 的标准程序 , 有一个选择用过账日期开始来访问数据库 , 但是这个标准程序根本就不会走到那段 HINT 代码里 , 说明这个选择是不会 生效的 )

    忽然有种被忽悠的感觉 .

    如果废弃了 , 应该有所说明 , 并且把新的语法公告之 , 但是我没有翻到相关的 ABAP 基于 DB2 HINT 的说明文档 , 这种 undocument 的方式让人十分不爽啊 .

    反倒是 Oracle HINT 说明非常多 , 而且很容易使用 , 我在这里非常的 BS IBM DB2, 它让我翻看了一晚的程序 , 翻文档 翻到有一种想吐的感觉 ..

    Google 搜索 ”ABAP DB2 HINT” 这三个关键字 , 能搜索到的内容我全都尝试了一遍 .

    我已经快 2 年没有这样的感觉了 , 真恶心啊。

    下面这个也是模仿着 Oracle 写的 , 没法使用 , ABAP 里写 Oracle HINT 就很容易 , 因为语法和 Oracle 里的基本一样 , 对比之下 Oracle 的文档就丰富许多 .(黝黑18摸)

    *%_HINTS DB2 'USE VALUES FOR OPTIMIZATION'
    *DB2 'INDEX("MKPF" "MKPF~BUD")'.

    SAP F1 文档很烂 , 在这一块基本没什么说明 , 如此大的几家厂商 , 一起融洽的合作就那么困难吗 ?? 稍微麻烦的点的东西就 undocument , 太让人气愤 .. 这个时候我甚至想到把问题发 msg SAP.

    多次测试之后 , 以瞎猫碰上死老鼠的方式

    发现 USE_OPLEVEL 这个参数起到了效果 , 原来是

    WITH UR -- OPTLEVEL( 10 ) -- QUERY_DEGREE( 1 ) -- LOCATION( ZCOR0015_1 , 125 ) -- SYSTEM( SDX , SAPPRD )'
    这个 10 级的 OPTLEVEL, 起到了影响 .

    这个参数是可以对应到 DB2 里的 . 不明白为什么 SAP 默认要 强制送给 DB2 这个参数 ?? 而且 SAP 默认安装的时候 DB2 数据库的这个参数值为 5 , 也不知道从哪里可以改变 SAP 的这个默认级别 .

    无论你在数据库层面更改这个值 , ABAP 程序送出 SQL 时总会是带上 -- OPTLEVEL( 10 ) , 有点类似于 基于 session 更改参数的概念 . 不影响整体参数 .

    这时快要水落石出了 , 解决它有 2 种途径:

    1、 更改 &SUBSTITUTE VALUES& 对于 SAP 内核版本 4.6 ,这个参数需要把 SAP 配置参数 dbs/db6/dbsl_substitute_literals 设置为 1 这个参数使在 ABAP 语句中的所有输入值都被作为 SQL 语句文本的文字实现。就像 &SUBSTITUTE LITERALS& DB2 优化器可以使用关于涉及表中的分布信息。这个基本同等于 Oracle 理解的绑定变量 , 如果设置了这个值的话 ,SAP 送给 DB2 SQL 就是完整的 , 带有 字面值 , 那么我相信执行计划也一定会是正确的 , 不过修改这个 SAP 配置参数的评估已不在我目前的工作范围内了 .

    2、 ABAP 程序里加上提示 DB6 'USE_OPTLEVEL 0' .

    我现在在 DB2 里测试下这个级别 0, 10 的区别

    在数据库里对应这个 SAP 的参数是 DFT_QUERYOPT

    ( DB2 里为 0,1,2,3,5,7,9, 但是不明白为什么 SAP 里有 10 咳咳 , 不知道这是怎么对应上的 , 反正只能靠猜测了 )
    session 端更改 DFT_QUERYOPT(db cfg 级别 ), 默认为 5
    譬如 SQL:set current query optimization = 5

    数据库里最大值为 9,SAP 里最大值为 10. 最小值都为 0

        Set current query optimization =9;
        SELECT T_00."MATNR", T_01."MBLNR", T_00."BWART", T_01."BUDAT",
        T_00."WERKS", T_00."BUKRS", T_00."MENGE", T_01."MJAHR",
        T_00."AUFNR", T_00."SHKZG"
        FROM SAPPRD."MSEG" T_00 INNER JOIN SAPPRD."MKPF" T_01 ON
        T_01."MANDT"=?
        AND T_01."MANDT"= T_00."MANDT"
        AND T_01."MBLNR"= T_00."MBLNR"
        AND T_01."MJAHR"= T_00."MJAHR"
        WHERE
        T_00."MANDT"=? AND
        T_01."BUDAT" BETWEEN ? AND ? AND
        T_00."MATNR" BETWEEN ? AND ?
        WITH UR;
    以下为执行计划

    烂的一塌糊涂 , 还是 hash join, 而且也走的错误的索引 , 不是我想要的执行计划 .

    难怪 ABAP 送出的 SQL 与我相同 , 但是执行计划不同 , 导致性能并且很慢的原因就在这里 .

    改为 0 级别 , 再测试一次 .

    set current query optimization = 0 ;

    这次的执行计划正确了 !!

    4. 结果

    在之前所创建的索引下 ,

    套用到 ABAP 程序底部 , 加个 HINT

        select
        MSEG~matnr
        MKPF~mblnr
        MSEG~bwart
        * b~aufnr
        MKPF~budat
        MSEG~WERKS
        MSEG~bukrs
        MSEG~menge
        MKPF~mjahr
        MSEG~aufnr
        MSEG~SHKZG
        INTO CORRESPONDING FIELDS OF TABLE g_t_result
        from MSEG AS MSEG INNER JOIN MKPF as MKPF
        on MKPF~MANDT = MSEG~MANDT
        AND MKPF~MBLNR = MSEG~MBLNR
        AND MKPF~MJAHR = MSEG~MJAHR
        WHERE
        * a~budat in s_budat AND
        MKPF~BUDAT BETWEEN '20090101' AND '20090201'and
        MSEG~MATNR BETWEEN '000000000010000000'and'000000000019999999'
        %_HINTS DB6 'CONVERT_FAE_TO_CTE'
        DB6 'USE_OPTLEVEL 0'.

    其实让 DB2 查询优化器 , 自己分析出来的执行计划我还是很放心的 , 但是在 SAP 里解析后的程序 , 添油加醋 再给 DB2, 这是完全不同的东西 , 很让我失望

    MKPF~BUDAT BETWEEN '20090101' AND '20090105' and
    MSEG~MATNR
    BETWEEN '000000000010000000' and '000000000019999999'

    调整过后的速度非常快 , 与我预料的一样 , 类似上面这样的 5 天范围的数据查询 , 把数据返回到内表里 , 1 秒钟都不需要 .

    MKPF~BUDAT BETWEEN '20090101' AND '20090201' and
    MSEG~MATNR
    BETWEEN '000000000010000000' and '000000000019999999'

    一个月的数据返回到内表里 , 大致 5~10 .

    MKPF~BUDAT BETWEEN '20090101' AND '20090701' and
    MSEG~MATNR
    BETWEEN '000000000010000000' and '000000000019999999'

    半年的数据返回到内表 , 不到 1 分钟

    我本来以为数据库层面的问题已经完全解决了 , 但是这里又再出出了问题 , 其实在测试阶段出问题倒挺好的 , 做到全面的测试 , 把问题都扼杀在测试阶段 .. 省得到时候部署到生产系统之后再出乱子 .

    2010-7-29 , 今天发现程序变得异常缓慢 , 与昨天的不一样 , 我调整了下优化级别为 0, 然后到 DB2 里看看执行计划 .

        set current query optimization =0;
        SELECT T_00."MATNR", T_01."MBLNR", T_00."BWART", T_01."BUDAT",
        T_00."WERKS", T_00."BUKRS", T_00."MENGE", T_01."MJAHR",
        T_00."AUFNR", T_00."SHKZG"
        FROM SAPPRD."MSEG" T_00 INNER JOIN SAPPRD."MKPF" T_01 ON
        T_01."MANDT"=?
        AND T_01."MANDT"= T_00."MANDT"
        AND T_01."MBLNR"= T_00."MBLNR"
        AND T_01."MJAHR"= T_00."MJAHR"
        WHERE
        T_00."MANDT"=? AND
        T_01."BUDAT" BETWEEN ? AND ? AND
        T_00."MATNR" BETWEEN ? AND ?
        WITH UR;

    真奇怪 , 又用回 MKPF~0 索引了 , 真的有够烂 , 不稳定的执行计划 是不可能放入生产环境使用的 , 看来还是要彻底的去解决这个问题 .

    假设 : 每次 ABAP 层面发送 SQL 语句给 DB2 的时候都是发送的字面值 , 而不是类似 “MANDT=?” 这样的问号 , 那么这时数据库的 CBO 就会根据统计信息生成最优的执行计划。

    ( 虽然每次发送字面值 , 都会让数据去解析执行计划 , 会造成执行计划不共享 , 带来一小部分 CPU 开销 , 但是我认为是值得的 , 因为是报表程序( OLAP 应用) , 正确的执行计划显得非常 重要 , 并且这个 CPU 开销是十分小的 , 对比这个报表的消耗时间来说 , 基本可以忽略掉了 )

    如果把 ABAP SQL 语句造成的影响降到最低 , 那么就是发送字面值了 ( 字面值的意思就是类似 “MANDT=900” 这样的程序 ).

    以下的公式是成立的 :

    SQL 发送字面值 + 正确稳定的统计信息 + 合理的索引 最优的执行计划 最优的速度

    这时候想办法让 ABAP 发送字面值吧

    1. 开始想到了改 SAP 配置参数 dbs/db6/dbsl_substitute_literals 设置为 1 , 这样 SAP 系统发送的全部 SQL 语句都是字面值 ..

    可惜我没找到相关的参数 , 网上的资料说这个参数是 4.6 版本的 , 新版本的找不到了 , 但是却可以找到对应 Oracle MSSQL 的参数 .( 再次晕倒 ..)

    2. ABAP 程序里增加 %_HINT 来强制 程序发送字面值 ,

    WHERE
    MKPF~BUDAT
    BETWEEN '20090101' AND '20090201' and
    MSEG~MATNR
    BETWEEN '000000000010000000' and '000000000019999999'
    %_HINTS
    DB6
    'CONVERT_FAE_TO_CTE'
    DB6
    'USE_OPTLEVEL 0'
    DB2
    '&SUBSTITUTE LITERALS&'
    DB6
    '&SUBSTITUTE LITERALS&'
    DB2
    '&SUBSTITUTE VALUES&'
    DB6
    '&SUBSTITUTE VALUES&' .

    经过测试 , 程序速度很快 10 秒内数据就返回到内表了 : ) 估计发送了字面值 ,DB2 的执行计划已经正确 , 但是为了严谨 , 我还是要验证一下 .

    select * from sysibmadm . TOP_DYNAMIC_SQL where stmt_text like '% FROM "MKPF" T_00 INNER JOIN "MSEG" T_01 %' order by AVERAGE_EXECUTION_TIME_S;

    经查询 , 字面值已经传入数据库 . 这样子 , 执行计划就是正确的了 !

    只要保证 ABAP 报表程序传入的是 字面值 , 并且数据库端的索引是 合理 , 并且数据库的统计信息是 正确的 , 就会拥有 稳定的 执行计划 , 以及 最优的 执行效率 !

    至此数据库层面的优化基本结束 , 这才是数据库真正的 威力
    接着放到测试环境里测试 , 发现 ABAP 程序里还是消耗了 10 分钟 ( 这时数据库的消耗只有 5 ),
    进行跟踪发现 瓶颈在 2 万多数据量的内表嵌套 LOOP 部分 . 导致的性能低下。
    这时与吴恒取得联系 , 让他给予一些关于嵌套 LOOP 的优化思路 . 然后我们这边的开发人员迅速做出了响应 , 不断的更改测试 , 更改测试 , 最后取得了非常好的效果 !!!

    以下为开发人员做的一些 优化记录 , 经过这段时间的折腾 , 我们的开发人员能力也得到了 提升 , 懂得如何撰写性能良好的程序 : )

    SAP 系统 ABAP 层面的性能优化。

    sap 中进行 abap 编程时候会不可避免的遇到循环嵌套,在本着尽量少的访问数据库时,将数据多放在内存中处理,提高数据的处理速度。一般不建议使用 loop 嵌套循环,特别是针对数据量大时,体现的尤为明显。尽量在通过 READ TABLE 来读取第二张内表中的数据,再进行数据逻辑的处理,这样子在取数性能上可以大大提高效率。
    下面我们就拿取一个月的数据时,所用到的两个逻辑取数耗时上的对比:
    一、使用 LOOP 循环嵌套取数。
    1
    、语法:

        LOOP AT g_t_out.
        LOOP AT g_t_result WHERE matnr = g_t_out-matnr_i .
        IF g_t_result-bwart ='101'.
        g_t_out-DDCHS = g_t_out-DDCHS + g_t_result-menge.
        ELSEIF g_t_result-bwart ='102'.
        g_t_out-CXDDS = g_t_out-CXDDS + g_t_result-menge.
        ELSEIF g_t_result-bwart ='122'.
        g_t_out-GYSTH = g_t_out-GYSTH + g_t_result-menge.
        ……………
        ENDLOOP.
        modify g_t_out.
        CLEAR g_t_out.
        ENDLOOP.
        sort g_t_out by matnr_i auart budat mblnr_i.
        sort g_t_collect by matnr_i auart budat mblnr.
        LOOP AT g_t_out.
        read table g_t_collect With key matnr_i = g_t_out-matnr_i bwart_i ='101' BINARY SEARCH.
        if sy-subrc =0.
        g_t_out-DDCHS = g_t_out-DDCHS + g_t_collect-menge_i.
        endif.
        read table g_t_collect With key matnr_i = g_t_out-matnr_i bwart_i ='102' BINARY SEARCH.
        if sy-subrc =0.
        g_t_out-CXDDS = g_t_out-CXDDS + g_t_collect-menge_i.
        endif.
        read table g_t_collect With key matnr_i = g_t_out-matnr_i bwart_i ='122' BINARY SEARCH.
        if sy-subrc =0.
        g_t_out-GYSTH = g_t_out-GYSTH + g_t_collect-menge_i.
        endif.
        modify g_t_out.
        CLEAR g_t_out.
        ENDLOOP.

    使用 READ TABLE 09 1 月份的数据耗时为 19
    通过以上两种方法进行比较,第一种方法取同一月的数据要 784 秒,而第二种取同一月的数据只要 19 秒。显然采用第二种方法取数在效率上要远远优于第一种方法。

    5. 总结

    这次的调优的总体难度对比以往在 MIS MES 上的 Oracle,SQLServer 来说并不算很难 ( 单纯考虑数据库层面的对比 ),

    但是对 SAP 的一些内部机制不够了解 , DB2 的陌生 …… 导致消耗了大量的时间在反复测试和阅读文档上 ,

    途中找瓶颈点时还折腾了 AIX, 怀疑操作系统配置 , 改动了 SAP 参数 , 数据库参数 , 怀疑存储性能 , 把所有可能的环节全都怀疑了一遍 , 把熬工 , 刘工等人也折腾得不行了 ,ABAP 开发人员也配合一起做了大量的更改 + 测试 ..

    总体来讲 , 调优还是需要对整体应用系统涵盖数据库 , 后台操作系统 , 存储等等 .. 有全面的了解

    只有全方位的了解应用系统 , 以及数据库 才能快速的 定位出问题根源 ,

    否则只能是铺天盖地的寻找瓶颈点 头痛医头 脚痛医脚 , 甚至像我一样到最后依靠瞎猫碰死老鼠的招式 , 不过能解决问题仍然是很高兴的 ,

    与大家分享之 : )

    6. 参考文档

    DB2 最佳实践 : 编写并调优查询语句以优化性能最佳实践

    http://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0909querytuning/

    SAP 官方联机丛书

    IBM DB2 官方联机丛书

    2010-7-28 @LDK xinyu

     
    推荐文章