今年一直搞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优化,脑壳痛的主要内容,如果未能解决你的问题,请参考以下文章