今年一直搞Oracle EBS优化,脑壳痛

Posted robinson1988

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了今年一直搞Oracle EBS优化,脑壳痛相关的知识,希望对你有一定的参考价值。

搞了10几年的性能优化,OLTP系统,OLAP系统,Oracle,mysql,PG,Greenplum,Oceanbase,hive,
达梦等等各种数据库优化项目做过太多太多...唯独EBS系统没有单独做过优化,一直都很遗憾。
虽然之前做网络培训的时候教了几个EBS DBA徒弟,他们也找我优化过EBS的SQL,但是都是零零散散的。
今年开春,公司接了一个EBS优化项目(版本ebs11i),这也算公司是第一个真正意义的EBS优化项目。
早期是3个DBA和3个EBS顾问以及项目经理在客户现场,搞了2个多月,进展缓慢。
因为项目面临失败的风险,项目经理只能升级,申请公司高级资源,自然的EBS优化这活就到我这里了。
到了客户现场我就发现为什么进展缓慢了,EBS这个优化项目当时谈是先做FORM优化,再做报表优化

当时FORM优化遇到的问题

1.这个EBS系统里面的SQL有大量视图套视图套视图套视图,一直套....想把整个SQL逻辑看懂要一层一层拔视图源码,巨恶心,而且,经常有最里面的VIEW慢,导致外面的VIEW也跟着变慢
2.有些视图是标准的(一般不能改写),有些视图是客户化的(可以改写),这些VIEW单独查询有时候跑得慢....更别提它还要再去关联其他表和VIEW了
3.把这些SQL里面的VIEW全部展开,一个SQL基本上500行起步
4.SQL里面有大量的自定义函数,各种标量子查询,各种or exists,凡是SQL编程规范不建议的全给写上了
5.为了实现统一接口调用,要应付界面各种传参,where条件里面有一大堆AP.ORG_ID = NVL(:B1, AP.ORG_ID),OR EXISTS...OR ...OR ...
6.超级复杂SQL硬解析都要十几秒到几分钟,客户提的要求是所有FORM界面要5秒能返回结果,EBS版本是11i,Oracle数据库版本是11gR1,11gR1优化器有很多BUG....这个系统没办法升级EBS,数据库也基本上不可能升级了...
7.传统优化手段:收集统计信息,加索引,加HINT,调整执行计划,开并行...凡是你能想到的统统没用
8.我10几年的优化名声差点毁于这个项目,太难搞了,去了2天没找到地方下手(主要是看到view套view这种俄罗斯套娃心里烦,复杂SQL硬解析太慢)

第三天我在想,今天不搞出一点成就出来恐怕名声都毁了。于是沉下心找突破口
找EBS顾问对SQL进行了初始化(EBS SQL绝大部分都要初始化),又将临时表数据灌到普通表方便调试

SQL太复杂,这里就不贴了

把SQL里面的or exists,自定义函数,标量,等等能改写的改写了,有一定效果,从之前不出结果到10几分钟可以出结果,但是这离5秒内出结果也很远啊,客户还是不能接受,最后又定位到EBS标准VIEW了,就是因为标准VIEW写得有问题导致查它本身都很慢。一般来说标准VIEW大家是不会去改写的,但是不改无解,对其进行改写之后,硬解析问题自己就解决了,SQL也在3秒内跑完了,于是赶紧和EBS顾问商量能否这样搞

终于定位到问题了:标准VIEW,客户化VIEW需要改写,不改无解,之前是一直避开,尽量不去动标准VIEW,难怪没有太大进展

和客户商量之后,客户说反正我们以后也不升级了,而且客户自己也改写过标准VIEW,让我们放心改,但是改动要注明改的原因,方便上线评审
找到突破口之后,我赶紧拉了自己团队的3个DBA远程支持,我一个人肯定做不完这么多改写工作,又叫EBS顾问再派2个EBS开发以及让客户EBS开发也参与进来

经过2个月艰苦奋斗终于完成了优化任务,这次EBS优化,相当于把EBS整个FORM,报表,代码全新重写一遍,把标准VIEW,客户化VIEW重头到尾全给改写了,这需要专业的优化DBA(等价改写很牛逼的)和高级EBS开发,高级EBS功能顾问一起配合,3方缺一不可

有了这个标杆的EBS优化项目,公司又接了一个EBS迁移项目,虽说是迁移,但是实际上要干很多EBS报表优化的活... 
正好SQL代码很简单(其余的太复杂了,不好写文章,见谅),就拿出来与大家分享一下,SQL代码如下:

SELECT AP.INVOICE_ID,
       AP.OU_NAME,
       AP.BUSINESS_NAME,
       AP.ATTRIBUTE15 DEPART_NAME,
       NVL(APS.VENDOR_NAME, HP.PARTY_NAME) VENDOR_NAME,
       DECODE(AP.INVOICE_TYPE_LOOKUP_CODE,
              'PAYMENT REQUEST',
              NULL,
              APS.SEGMENT1) VENDOR_NUMBER,
       ALC.DISPLAYED_FIELD INVOICE_TYPE,
       AP.INVOICE_NUM,
       TO_CHAR(AP.INVOICE_DATE, 'YYYY-MM-DD') INVOICE_DATE,
       TO_CHAR(AP.GL_DATE, 'YYYY-MM-DD') GL_DATE,
       AP.INVOICE_CURRENCY_CODE CURRENCY_CODE,
       AP.INVOICE_AMOUNT ENTERED_AMOUNT,
       ROUND(AP.INVOICE_AMOUNT *
             NGL_ACCT_DETAIL_ACC_PKG.GET_CONVERSION_RATE(AP.INVOICE_CURRENCY_CODE,
                                                         NAP_UNINVOICE_REPORT_OUTPUT_N.GET_CURRENCY_CODE(AP.ORG_ID),
                                                         AP.GL_DATE),
             2) ACCOUNTED_AMOUNT
  FROM (SELECT APIA.*,
               (SELECT HOU.NAME
                  FROM HR_OPERATING_UNITS HOU
                 WHERE HOU.ORGANIZATION_ID = APIA.ORG_ID) OU_NAME,
               (SELECT NBDV.BUSSION_NAME
                  FROM NCM_BU_DEPARTMENT_V NBDV
                 WHERE NBDV.DEPARTMENT_CODE = APIA.ATTRIBUTE15) BUSINESS_NAME
          FROM AP_INVOICES_ALL APIA) AP,
       (SELECT TMP.INVOICE_ID
          FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
               (SELECT T.NUM1 INVOICE_ID
                  FROM NGL_UNIVERSAL_TMP_DXB T
                 WHERE T.TYPE_STR IN
                       ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
                   AND T.NUM2 = NVL(null, T.NUM2)) TMP
         WHERE TMP.INVOICE_ID = AID.INVOICE_ID
           AND AID.ACCRUAL_POSTED_FLAG <> 'Y'
           AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
           AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'P'
           AND (NOT EXISTS
                (SELECT 1
                   FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                  WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                    AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                    AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                    AND XTE.ENTITY_CODE = 'AP_INVOICES'
                    AND XE.EVENT_STATUS_CODE = 'U'
                    AND (XE.PROCESS_STATUS_CODE = 'U' OR
                        XE.PROCESS_STATUS_CODE = 'I')
                    AND XTE.APPLICATION_ID = 200
                    AND XE.APPLICATION_ID = 200) OR
                (AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND
                EXISTS (SELECT 1
                          FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                         WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                           AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                           AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                           AND XTE.ENTITY_CODE = 'AP_INVOICES'
                           AND XE.EVENT_STATUS_CODE = 'U'
                           AND XE.PROCESS_STATUS_CODE = 'U')) OR
                (EXISTS (SELECT 1
                           FROM AP_PREPAY_HISTORY_ALL APH
                          WHERE APH.INVOICE_ID = AID.INVOICE_ID
                            AND APH.POSTED_FLAG <> 'Y') AND NOT EXISTS
                 (SELECT 1
                    FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                   WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                     AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                     AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                     AND XTE.ENTITY_CODE = 'AP_INVOICES'
                     AND XE.EVENT_STATUS_CODE = 'U'
                     AND (XE.PROCESS_STATUS_CODE = 'U' OR
                         XE.PROCESS_STATUS_CODE = 'I')
                     AND XTE.APPLICATION_ID = 200
                     AND XE.APPLICATION_ID = 200)))
        UNION ALL
        SELECT TMP.INVOICE_ID
          FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
               (SELECT T.NUM1 INVOICE_ID
                  FROM NGL_UNIVERSAL_TMP_DXB T
                 WHERE T.TYPE_STR IN
                       ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
                   AND T.NUM2 = NVL(null, T.NUM2)) TMP
         WHERE TMP.INVOICE_ID = AID.INVOICE_ID
           AND AID.ACCRUAL_POSTED_FLAG = 'Y'
           AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
           AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y'
           AND ((AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND
               EXISTS (SELECT 'x'
                          FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                         WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                           AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                           AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                           AND XE.EVENT_STATUS_CODE = 'U'
                           AND XE.PROCESS_STATUS_CODE = 'U'
                           AND XTE.ENTITY_CODE = 'AP_INVOICES'
                           AND XTE.APPLICATION_ID = 200
                           AND XE.APPLICATION_ID = 200)) OR
               (EXISTS (SELECT 1
                           FROM AP_PREPAY_HISTORY_ALL APH
                          WHERE APH.INVOICE_ID = AID.INVOICE_ID
                            AND APH.POSTED_FLAG <> 'Y')))
        UNION ALL
        SELECT TMP.INVOICE_ID
          FROM (SELECT T.NUM1 INVOICE_ID
                  FROM NGL_UNIVERSAL_TMP_DXB T
                 WHERE T.TYPE_STR IN
                       ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
                   AND T.NUM2 = NVL(null, T.NUM2)) TMP,
               AP_INVOICES_ALL AP,
               AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
               AP_PREPAY_HISTORY_ALL APH
         WHERE TMP.INVOICE_ID = AID.INVOICE_ID
           AND TMP.INVOICE_ID = AP.INVOICE_ID
           AND TMP.INVOICE_ID = APH.INVOICE_ID(+)
           AND AP_INVOICES_PKG.GET_APPROVAL_STATUS(AP.INVOICE_ID,
                                                   AP.INVOICE_AMOUNT,
                                                   AP.PAYMENT_STATUS_FLAG,
                                                   AP.INVOICE_TYPE_LOOKUP_CODE) =
               'APPROVED'
           AND AID.MATCH_STATUS_FLAG = 'N'
           AND EXISTS (SELECT 'x'
                  FROM XLA.XLA_TRANSACTION_ENTITIES XTE,
                       XLA_AE_HEADERS               AH,
                       XLA_AE_LINES                 AL,
                       XLA_EVENTS                   XE
                 WHERE AH.AE_HEADER_ID = AL.AE_HEADER_ID
                   AND AH.APPLICATION_ID = AL.APPLICATION_ID
                   AND XE.ENTITY_ID = XTE.ENTITY_ID
                   AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                   AND AH.EVENT_ID = XE.EVENT_ID
                   AND AH.APPLICATION_ID = XE.APPLICATION_ID
                   AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                   AND AH.ACCOUNTING_DATE >= to_date('2021-09-01','yyyy-mm-dd')
                   AND AH.ACCOUNTING_DATE < to_date('2021-09-30','yyyy-mm-dd') + 1
                   AND XTE.ENTITY_CODE = 'AP_INVOICES'
                   AND AL.APPLICATION_ID = 200
                   AND XTE.APPLICATION_ID = 200
                   AND AH.APPLICATION_ID = 200
                   AND XE.APPLICATION_ID = 200)) TMP,
       HZ_PARTIES HP,
       AP.AP_SUPPLIERS APS,
       AP_LOOKUP_CODES ALC
 WHERE 1 = 1
   AND AP.INVOICE_ID = TMP.INVOICE_ID
   AND AP.PARTY_ID = HP.PARTY_ID
   AND EXISTS (SELECT 1
          FROM NCM_COMMON_GT_TMP_DXB NCGT
         WHERE NCGT.CHAR1 = AP.ATTRIBUTE15)
  -- AND XY_COM_DEPT_READ.DEPT_READ(AP.ATTRIBUTE15) = 'Y'
   AND AP.VENDOR_ID = APS.VENDOR_ID(+)
   AND AP.INVOICE_TYPE_LOOKUP_CODE = ALC.LOOKUP_CODE(+)
   AND ALC.LOOKUP_TYPE(+) = 'INVOICE TYPE'
   AND AP.ORG_ID = NVL(null, AP.ORG_ID)
 GROUP BY AP.OU_NAME,
          AP.BUSINESS_NAME,
          AP.INVOICE_ID,
          AP.ATTRIBUTE15,
          NVL(APS.VENDOR_NAME, HP.PARTY_NAME),
          DECODE(AP.INVOICE_TYPE_LOOKUP_CODE,
                 'PAYMENT REQUEST',
                 NULL,
                 APS.SEGMENT1),
          ALC.DISPLAYED_FIELD,
          AP.INVOICE_NUM,
          TO_CHAR(AP.INVOICE_DATE, 'YYYY-MM-DD'),
          TO_CHAR(AP.GL_DATE, 'YYYY-MM-DD'),
          AP.INVOICE_CURRENCY_CODE,
          AP.INVOICE_AMOUNT,
          ROUND(AP.INVOICE_AMOUNT *
                NGL_ACCT_DETAIL_ACC_PKG.GET_CONVERSION_RATE(AP.INVOICE_CURRENCY_CODE,
                                                            NAP_UNINVOICE_REPORT_OUTPUT_N.GET_CURRENCY_CODE(AP.ORG_ID),
                                                            AP.GL_DATE),
                2)
 ORDER BY AP.INVOICE_ID,
          AP.ATTRIBUTE15,
          TO_CHAR(AP.INVOICE_DATE, 'YYYY-MM-DD');

上面SQL返回0行,在一个报表的游标LOOP循环里面,要循环几百次,单次执行要30-50秒,最好的优化方法肯定是去掉LOOP循环,批量处理,但是这本身是一个迁移项目,非纯粹的优化项目,改动LOOP循环动作太大,划不来。而且我们改了循环,客户叫我们自己去验证数据,那不是自己给自己没事找事做吗哈哈,你懂的。所以目前最佳的解决方案是将SQL单次执行速度降下来,从单次执行30-50秒降低到秒级,那也达到了优化效果

这个SQL有3个地方慢,其中一处是这里:

SELECT TMP.INVOICE_ID
          FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
               (SELECT T.NUM1 INVOICE_ID
                  FROM NGL_UNIVERSAL_TMP_DXB T
                 WHERE T.TYPE_STR IN
                       ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
                   AND T.NUM2 = NVL(null, T.NUM2)) TMP
         WHERE TMP.INVOICE_ID = AID.INVOICE_ID
           AND AID.ACCRUAL_POSTED_FLAG <> 'Y'
           AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
           AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'P' ---这个EBS标准的自定义函数拖慢了整个SQL,去掉它0.0几秒,不去掉3秒
           AND (NOT EXISTS
                (SELECT 1
                   FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                  WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                    AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                    AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                    AND XTE.ENTITY_CODE = 'AP_INVOICES'
                    AND XE.EVENT_STATUS_CODE = 'U'
                    AND (XE.PROCESS_STATUS_CODE = 'U' OR
                        XE.PROCESS_STATUS_CODE = 'I')
                    AND XTE.APPLICATION_ID = 200
                    AND XE.APPLICATION_ID = 200) OR
                (AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND
                EXISTS (SELECT 1
                          FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                         WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                           AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                           AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                           AND XTE.ENTITY_CODE = 'AP_INVOICES'
                           AND XE.EVENT_STATUS_CODE = 'U'
                           AND XE.PROCESS_STATUS_CODE = 'U')) OR
                (EXISTS (SELECT 1
                           FROM AP_PREPAY_HISTORY_ALL APH
                          WHERE APH.INVOICE_ID = AID.INVOICE_ID
                            AND APH.POSTED_FLAG <> 'Y') AND NOT EXISTS
                 (SELECT 1
                    FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                   WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                     AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                     AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                     AND XTE.ENTITY_CODE = 'AP_INVOICES'
                     AND XE.EVENT_STATUS_CODE = 'U'
                     AND (XE.PROCESS_STATUS_CODE = 'U' OR
                         XE.PROCESS_STATUS_CODE = 'I')
                     AND XTE.APPLICATION_ID = 200
                     AND XE.APPLICATION_ID = 200)))

整个SQL最终返回0行数据,这个SQL也返回0行,按道理它应该秒杀啊,但是它要跑3秒,SQL慢在EBS标准的自定义函数上了

于是对SQL进行了改写:

SELECT TMP.INVOICE_ID
          FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
               (SELECT T.NUM1 INVOICE_ID
                  FROM NGL_UNIVERSAL_TMP_DXB T
                 WHERE T.TYPE_STR IN
                       ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
                   AND T.NUM2 = NVL(null, T.NUM2)) TMP
         WHERE TMP.INVOICE_ID = AID.INVOICE_ID
           AND AID.ACCRUAL_POSTED_FLAG <> 'Y'
           AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
          -- AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'P'
           AND EXISTS(SELECT /*+ NO_UNNEST */ NULL FROM DUAL WHERE AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'P') 
           AND (NOT EXISTS
                (SELECT 1
                   FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                  WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                    AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                    AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                    AND XTE.ENTITY_CODE = 'AP_INVOICES'
                    AND XE.EVENT_STATUS_CODE = 'U'
                    AND (XE.PROCESS_STATUS_CODE = 'U' OR
                        XE.PROCESS_STATUS_CODE = 'I')
                    AND XTE.APPLICATION_ID = 200
                    AND XE.APPLICATION_ID = 200) OR
                (AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND
                EXISTS (SELECT 1
                          FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                         WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                           AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                           AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                           AND XTE.ENTITY_CODE = 'AP_INVOICES'
                           AND XE.EVENT_STATUS_CODE = 'U'
                           AND XE.PROCESS_STATUS_CODE = 'U')) OR
                (EXISTS (SELECT 1
                           FROM AP_PREPAY_HISTORY_ALL APH
                          WHERE APH.INVOICE_ID = AID.INVOICE_ID
                            AND APH.POSTED_FLAG <> 'Y') AND NOT EXISTS
                 (SELECT 1
                    FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
                   WHERE XE.ENTITY_ID = XTE.ENTITY_ID
                     AND XE.APPLICATION_ID = XTE.APPLICATION_ID
                     AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
                     AND XTE.ENTITY_CODE = 'AP_INVOICES'
                     AND XE.EVENT_STATUS_CODE = 'U'
                     AND (XE.PROCESS_STATUS_CODE = 'U' OR
                         XE.PROCESS_STATUS_CODE = 'I')
                     AND XTE.APPLICATION_ID = 200
                     AND XE.APPLICATION_ID = 200)))

 -- AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'P'     ---将这个自定义函数注释掉,改成exists /*+ no_unnest */
   AND EXISTS(SELECT /*+ NO_UNNEST */ NULL FROM DUAL WHERE AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'P')

NO_UNNEST肯定会走FILTER,FILTER又是在最后阶段执行,意思就是让自定义函数最后执行,测试了一下,0.0几秒,之前3秒

另外一处是慢在这里:

SELECT TMP.INVOICE_ID
  FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
       (SELECT T.NUM1 INVOICE_ID
          FROM NGL_UNIVERSAL_TMP_DXB T
         WHERE T.TYPE_STR IN ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
           AND T.NUM2 = NVL(null, T.NUM2)) TMP
 WHERE TMP.INVOICE_ID = AID.INVOICE_ID
   AND AID.ACCRUAL_POSTED_FLAG = 'Y'
   AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
   AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y' ---EBS标准自定义函数拖慢整个SQL,去掉它0.0几秒,不去掉3秒
   AND ((AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND EXISTS
        (SELECT 'x'
            FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
           WHERE XE.ENTITY_ID = XTE.ENTITY_ID
             AND XE.APPLICATION_ID = XTE.APPLICATION_ID
             AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
             AND XE.EVENT_STATUS_CODE = 'U'
             AND XE.PROCESS_STATUS_CODE = 'U'
             AND XTE.ENTITY_CODE = 'AP_INVOICES'
             AND XTE.APPLICATION_ID = 200
             AND XE.APPLICATION_ID = 200)) OR
       (EXISTS (SELECT 1
                   FROM AP_PREPAY_HISTORY_ALL APH
                  WHERE APH.INVOICE_ID = AID.INVOICE_ID
                    AND APH.POSTED_FLAG <> 'Y')))

于是参考上面的改写方式,将SQL改了:

SELECT TMP.INVOICE_ID
  FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
       (SELECT T.NUM1 INVOICE_ID
          FROM NGL_UNIVERSAL_TMP_DXB T
         WHERE T.TYPE_STR IN ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
           AND T.NUM2 = NVL(null, T.NUM2)) TMP
 WHERE TMP.INVOICE_ID = AID.INVOICE_ID
   AND AID.ACCRUAL_POSTED_FLAG = 'Y'
   AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
  --- AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y' 
   AND EXISTS(SELECT /*+ NO_UNNEST */ NULL FROM DUAL WHERE AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y')
   AND ((AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND EXISTS
        (SELECT 'x'
            FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
           WHERE XE.ENTITY_ID = XTE.ENTITY_ID
             AND XE.APPLICATION_ID = XTE.APPLICATION_ID
             AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
             AND XE.EVENT_STATUS_CODE = 'U'
             AND XE.PROCESS_STATUS_CODE = 'U'
             AND XTE.ENTITY_CODE = 'AP_INVOICES'
             AND XTE.APPLICATION_ID = 200
             AND XE.APPLICATION_ID = 200)) OR
       (EXISTS (SELECT 1
                   FROM AP_PREPAY_HISTORY_ALL APH
                  WHERE APH.INVOICE_ID = AID.INVOICE_ID
                    AND APH.POSTED_FLAG <> 'Y')))

改完之后发现要跑1.5秒,之前是3秒,有提升,但是没能达到0.0几秒,分析了一下原因,发现 

 AND EXISTS(SELECT /*+ NO_UNNEST */ NULL FROM DUAL WHERE AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y')

会走FILTER,而后面还有个 exists OR exists也走FILTER,因为把它放在了exists OR exists前面了,导致了提前过滤,我改写的目的是最后过滤啊,所以挪了一下位置:

SELECT TMP.INVOICE_ID
  FROM AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
       (SELECT T.NUM1 INVOICE_ID
          FROM NGL_UNIVERSAL_TMP_DXB T
         WHERE T.TYPE_STR IN ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
           AND T.NUM2 = NVL(null, T.NUM2)) TMP
 WHERE TMP.INVOICE_ID = AID.INVOICE_ID
   AND AID.ACCRUAL_POSTED_FLAG = 'Y'
   AND NVL(AID.MATCH_STATUS_FLAG, 'N') <> 'N'
      -- AND AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y'
   AND ((AID.AMOUNT = 0 AND AID.LINE_TYPE_LOOKUP_CODE = 'PREPAY' AND EXISTS
        (SELECT 'x'
            FROM XLA.XLA_TRANSACTION_ENTITIES XTE, XLA_EVENTS XE
           WHERE XE.ENTITY_ID = XTE.ENTITY_ID
             AND XE.APPLICATION_ID = XTE.APPLICATION_ID
             AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
             AND XE.EVENT_STATUS_CODE = 'U'
             AND XE.PROCESS_STATUS_CODE = 'U'
             AND XTE.ENTITY_CODE = 'AP_INVOICES'
             AND XTE.APPLICATION_ID = 200
             AND XE.APPLICATION_ID = 200)) OR
       (EXISTS (SELECT 1
                   FROM AP_PREPAY_HISTORY_ALL APH
                  WHERE APH.INVOICE_ID = AID.INVOICE_ID
                    AND APH.POSTED_FLAG <> 'Y')) AND EXISTS
        (SELECT /*+ NO_UNNEST */
          NULL
           FROM DUAL
          WHERE AP_INVOICES_PKG.GET_POSTING_STATUS(TMP.INVOICE_ID) = 'Y'))

终于0.0几秒了,搞定,这么说来我第一个改写的位置也应该改一下哦。大家知道是这个意思就行了,我也懒得去改了。我这个人有时候会很认真去研究一些问题,有时候明知有些瑕疵,我也懒得去改,没办法我就是这种性格,我写文章,写书的目的是传播优化思想,不会太在意细节,你get到我的点就行了。

最后一处慢在这里:

SELECT TMP.INVOICE_ID
  FROM (SELECT T.NUM1 INVOICE_ID
          FROM NGL_UNIVERSAL_TMP_DXB T
         WHERE T.TYPE_STR IN ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
           AND T.NUM2 = NVL(null, T.NUM2)) TMP,
       AP_INVOICES_ALL AP,
       AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
       AP_PREPAY_HISTORY_ALL APH
 WHERE TMP.INVOICE_ID = AID.INVOICE_ID
   AND TMP.INVOICE_ID = AP.INVOICE_ID
   AND TMP.INVOICE_ID = APH.INVOICE_ID(+)
   AND AP_INVOICES_PKG.GET_APPROVAL_STATUS(AP.INVOICE_ID, ---EBS标准自定义函数
                                           AP.INVOICE_AMOUNT, ---EBS标准自定义函数
                                           AP.PAYMENT_STATUS_FLAG, ---EBS标准自定义函数
                                           AP.INVOICE_TYPE_LOOKUP_CODE) = 'APPROVED' ---EBS标准自定义函数
   AND AID.MATCH_STATUS_FLAG = 'N'
   AND EXISTS
 (SELECT 'x'
          FROM XLA.XLA_TRANSACTION_ENTITIES XTE,
               XLA_AE_HEADERS               AH,
               XLA_AE_LINES                 AL,
               XLA_EVENTS                   XE
         WHERE AH.AE_HEADER_ID = AL.AE_HEADER_ID
           AND AH.APPLICATION_ID = AL.APPLICATION_ID
           AND XE.ENTITY_ID = XTE.ENTITY_ID
           AND XE.APPLICATION_ID = XTE.APPLICATION_ID
           AND AH.EVENT_ID = XE.EVENT_ID
           AND AH.APPLICATION_ID = XE.APPLICATION_ID
           AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
           AND AH.ACCOUNTING_DATE >= to_date('2021-09-01', 'yyyy-mm-dd')
           AND AH.ACCOUNTING_DATE < to_date('2021-09-30', 'yyyy-mm-dd') + 1
           AND XTE.ENTITY_CODE = 'AP_INVOICES'
           AND AL.APPLICATION_ID = 200
           AND XTE.APPLICATION_ID = 200
           AND AH.APPLICATION_ID = 200
           AND XE.APPLICATION_ID = 200)

这里要跑近20秒,对其进行改写,将条件放到exists里面,改写完之后调整执行计划,加HINT优化:

SELECT /*+ use_nl(tmp,ap) use_nl(aid) use_nl(aph) index(ap AP_INVOICES_U1) */
 TMP.INVOICE_ID
  FROM (SELECT T.NUM1 INVOICE_ID
          FROM NGL_UNIVERSAL_TMP_DXB T
         WHERE T.TYPE_STR IN ('INVOICE_BASE_DATA1', 'INVOICE_BASE_DATA2')
           AND T.NUM2 = NVL(null, T.NUM2)) TMP,
       AP_INVOICES_ALL AP,
       AP.AP_INVOICE_DISTRIBUTIONS_ALL AID,
       AP_PREPAY_HISTORY_ALL APH
 WHERE TMP.INVOICE_ID = AID.INVOICE_ID
   AND TMP.INVOICE_ID = AP.INVOICE_ID
   AND TMP.INVOICE_ID = APH.INVOICE_ID(+)
      --  AND AP_INVOICES_PKG.GET_APPROVAL_STATUS(AP.INVOICE_ID,
      --                                          AP.INVOICE_AMOUNT,
      --                                          AP.PAYMENT_STATUS_FLAG,
      --                                          AP.INVOICE_TYPE_LOOKUP_CODE) =
      --      'APPROVED'
   AND AID.MATCH_STATUS_FLAG = 'N'
   AND EXISTS
 (SELECT /*+ no_unnest */
         'x'
          FROM XLA.XLA_TRANSACTION_ENTITIES XTE,
               XLA_AE_HEADERS               AH,
               XLA_AE_LINES                 AL,
               XLA_EVENTS                   XE
         WHERE AH.AE_HEADER_ID = AL.AE_HEADER_ID
           AND AH.APPLICATION_ID = AL.APPLICATION_ID
           AND XE.ENTITY_ID = XTE.ENTITY_ID
           AND XE.APPLICATION_ID = XTE.APPLICATION_ID
           AND AH.EVENT_ID = XE.EVENT_ID
           AND AH.APPLICATION_ID = XE.APPLICATION_ID
           AND XTE.SOURCE_ID_INT_1 = TMP.INVOICE_ID
           AND AH.ACCOUNTING_DATE >= to_date('2021-09-01', 'yyyy-mm-dd')
           AND AH.ACCOUNTING_DATE < to_date('2021-09-30', 'yyyy-mm-dd') + 1
           AND XTE.ENTITY_CODE = 'AP_INVOICES'
           AND AL.APPLICATION_ID = 200
           AND XTE.APPLICATION_ID = 200
           AND AH.APPLICATION_ID = 200
           AND XE.APPLICATION_ID = 200
           AND AP_INVOICES_PKG.GET_APPROVAL_STATUS(AP.INVOICE_ID,
                                                   AP.INVOICE_AMOUNT,
                                                   AP.PAYMENT_STATUS_FLAG,
                                                   AP.INVOICE_TYPE_LOOKUP_CODE) = 'APPROVED')

从之前10-20秒优化到0.0几秒,最终将改写完的SQL拼接起来,SQL单次执行秒杀,整个报表从之前几十分钟优化到几分钟,那就行了没必要继续优化了。

有些读者会问,为啥不将函数改了啊?我也想改,而且我更想把LOOP循环干掉。但是EBS开发说谁改的谁去做数据校验,这个锅我可不敢背,为了把一个SQL优化到极致去干那么多破事划不来,况且这不是一个专门的EBS优化项目,能够优化到这,已经ok了。

最近遇到一大堆坑:

1. LOOP 循环上百万次,单次执行很快,综合执行慢,要改LOOP,但是没人去改,改了之后要做数据校验非常麻烦,大家都不想背锅...
2. select ...里面套无数个标准/自定义函数,一旦SQL处理数据量大,要跑几千秒,把自定义函数改写为标量60秒...但是都不想去改,操难道要我去改,几百个报表,每个报表那么多SQL改锤子
3. LOOP循环里面套DBLINK,真2B,来回远端本地网络传输,不慢才怪,又要改代码,但是没人改啊...
4. 一大堆 NVL 问题,比如 AP.ORG_ID = NVL(:B1, AP.ORG_ID) 走错执行计划, FUCK ,虽然加HINT可以解决,但是太多了看不完啊....
5. SQL里面一大堆临时表,依赖临时表依赖临时表,想去优化,初始化临时表都要搞好久,真他妈折磨人心态
6. 各种垃圾SQL写法...

技术做久了,难免疲乏,哎,中个大乐透追加1800w吧,早点退休哈哈。

以上是关于今年一直搞Oracle EBS优化,脑壳痛的主要内容,如果未能解决你的问题,请参考以下文章

今年一直搞Oracle EBS优化,脑壳痛

今年一直搞Oracle EBS优化,脑壳痛

今年一直搞Oracle EBS优化,脑壳痛

Oracle EBS LOV速度优化

ORACLE EBS XML并发请求报表一直警告

循环在尝试通过 Oracle EBS 中的并发程序使用 PL/SQL 创建 XML 时提前结束