本案例发生在 2010 年,当时作者罗老师在惠普担任开发 DBA,支撑宝洁公司的数据仓库项目。ETL 开发人员需要帮助调查一个 long running 的 JOB,该 JOB 执行了 7 个小时还没执行完。
数据库环境为 11.1.0.7(RAC,4 节点)。
select * from v$version;
BANNER
-------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production
数据块大小为 16k。
show parameter db_block_size
NAME TYPE VALUE
------------------------------------ --------------------------------- ------
db_block_size integer 16384
执行得慢的 JOB 是一个 insert into …select …语句。一般情况下,如果 select 语句跑得快,那么整个 JOB 也就跑得快,因此我们应该把主要精力放在 select 语句上面。select 部分的 SQL 语句如下,这是一个接近 400 行的 SQL(因为 SQL 实在太长,所以没有对 SQL 格式化)。
SELECT ACTVY_SKID,FUND_SKID,PRMTN_SKID,PROD_SKID,DATE_SKID,
ACCT_SKID,BUS_UNIT_SKID,FY_DATE_SKID,ESTMT_VAR_COST_AMT,ESTMT_FIXED_COST_AMT,
REVSD_ESTMT_VAR_COST_AMT,ACTL_VAR_COST_AMT,ACTL_FIXED_COST_AMT,COST_PLAN_AMT,
COST_CMMT_AMT,COST_BOOK_AMT,ESTMT_COST_OVRRD_AMT,LA_TOT_BOOK_AMT,
MANUL_COST_OVRRD_AMT,ACTL_COST_AMT
FROM (SELECT ACTVY_SKID,FUND_SKID,PROD_SKID,PRMTN_SKID,DATE_SKID,ACCT_SKID,
BUS_UNIT_SKID,FY_DATE_SKID,ESTMT_VAR_COST_AMT,ESTMT_FIXED_COST_AMT,
REVSD_ESTMT_VAR_COST_AMT,0 as ACTL_COST_AMT,ACTL_VAR_COST_AMT,ACTL_FIXED_COST_AMT,
MANUL_COST_OVRRD_AMT,ESTMT_COST_OVRRD_AMT,COST_BOOK_AMT,
-- Updated by Luke for QC3369
-- If the committed amount on Activity level <0 then return 0
(CASE WHEN SUM(ESTMT_COST_OVRRD_AMT - ACTL_VAR_COST_AMT -
ACTL_FIXED_COST_AMT) OVER(PARTITION BY ACTVY_SKID) < 0 THEN 0
ELSE COST_CMMT_AMT END) AS COST_CMMT_AMT,
-- Updated by Luke for QC3369
(CASE WHEN SUM(ESTMT_COST_OVRRD_AMT - ACTL_VAR_COST_AMT -
ACTL_FIXED_COST_AMT) OVER(PARTITION BY ACTVY_SKID) < 0 THEN 0
ELSE COST_PLAN_AMT END) AS COST_PLAN_AMT,LA_TOT_BOOK_AMT
FROM (SELECT ACTVY_SKID,FUND_SKID,PROD_SKID,PRMTN_SKID,
DATE_SKID,ACCT_SKID,BUS_UNIT_SKID,FY_DATE_SKID,ESTMT_VAR_COST_AMT,
ESTMT_FIXED_COST_AMT,REVSD_ESTMT_VAR_COST_AMT,ACTL_VAR_COST_AMT,
ACTL_FIXED_COST_AMT,MANUL_COST_OVRRD_AMT,
(CASE WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'E' THEN
ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'R' THEN ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,REVSD_ESTMT_VAR_COST_AMT,
--Ax Revised Estimated Variable Cost REVSD_BPT_COST_AMT) --BPT Revised Cost
WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'M' THEN MANUL_COST_OVRRD_AMT
WHEN ESTMT_COST_IND IS NULL THEN DECODE(CORP_PRMTN_TYPE_CODE,
'Annual Agreement',ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,
REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost
REVSD_BPT_COST_AMT), --BPT Revised Cost
ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END) AS ESTMT_COST_OVRRD_AMT,
(ACTL_VAR_COST_AMT + ACTL_FIXED_COST_AMT) AS COST_BOOK_AMT,
DECODE(PRMTN_STTUS_CODE,'Confirmed',
--Estimate Total Cost - Actual Cost
--Add the logic of Activity Stop date and Pyment allow IND
--For Defect 2913 Luke 2010-5-5
(CASE WHEN (ACTVY_STOP_DATE IS NULL OR ACTVY_STOP_DATE > SYSDATE OR
NVL(PYMT_ALLWD_STOP_IND, 'N') = 'Y') THEN (CASE WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'E' THEN ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'R' THEN ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,REVSD_ESTMT_VAR_COST_AMT,
--Ax Revised Estimated Variable Cost
REVSD_BPT_COST_AMT) --BPT Revised Cost
WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'M' THEN MANUL_COST_OVRRD_AMT
WHEN ESTMT_COST_IND IS NULL THEN DECODE(CORP_PRMTN_TYPE_CODE,'Annual Agreement',
ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,REVSD_ESTMT_VAR_COST_AMT,
--Ax Revised Estimated Variable Cost
REVSD_BPT_COST_AMT), --BPT Revised Cost
ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END) - (ACTL_VAR_COST_AMT + ACTL_FIXED_COST_AMT)
ELSE 0 END), 0) AS COST_CMMT_AMT,(CASE WHEN (PRMTN_STTUS_CODE IN ('Planned', 'Revised') AND NVL(APPRV_STTUS_CODE, 'Nothing') <> 'Rejected' AND
--Add the logic of Activity Stop date and Pyment allow IND
--For Defect 2913 Luke 2010-5-5
(ACTVY_STOP_DATE IS NULL OR ACTVY_STOP_DATE > SYSDATE OR NVL(PYMT_ALLWD_STOP_IND, 'N') = 'Y'))
THEN (CASE WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'E' THEN ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT
WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'R' THEN ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0, REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost REVSD_BPT_COST_AMT) --BPT Revised Cost
WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'M' THEN MANUL_COST_OVRRD_AMT WHEN ESTMT_COST_IND IS NULL THEN DECODE(CORP_PRMTN_TYPE_CODE,'Annual Agreement',ESTMT_FIXED_COST_AMT +DECODE(REVSD_BPT_COST_AMT,0,
REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost
REVSD_BPT_COST_AMT), --BPT Revised Cost
ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END) - (ACTL_VAR_COST_AMT + ACTL_FIXED_COST_AMT) ELSE 0 END) AS COST_PLAN_AMT,(CASE WHEN MTH_START_DATE > TRUNC(SYSDATE, 'MM') AND PRMTN_STTUS_CODE IN ('Planned', 'Confirmed', 'Revised') THEN (CASE WHEN SUBSTR(ESTMT_COST_IND, 1, 1)= 'E' THEN ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'R' THEN ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,REVSD_ESTMT_VAR_COST_AMT,
--Ax Revised Estimated Variable Cost
REVSD_BPT_COST_AMT) --BPT Revised Cost
WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'M' THEN MANUL_COST_OVRRD_AMT WHEN ESTMT_COST_IND IS NULL THEN DECODE(CORP_PRMTN_TYPE_CODE,'Annual Agreement',ESTMT_FIXED_COST_AMT +DECODE(REVSD_BPT_COST_AMT,0,
REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost
REVSD_BPT_COST_AMT), --BPT Revised Cost
ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END)
WHEN MTH_START_DATE <= TRUNC(SYSDATE, 'MM') THEN (ACTL_VAR_COST_AMT + ACTL_FIXED_COST_AMT) ELSE 0 END) AS LA_TOT_BOOK_AMT FROM (SELECT ACTVY_MTH_GTIN.ACTVY_SKID,ACTVY_MTH_GTIN.FUND_SKID,ACTVY_MTH_GTIN.PROD_SKID,
ACTVY_MTH_GTIN.PRMTN_SKID,ACTVY_MTH_GTIN.MTH_SKID AS DATE_SKID,ACTVY_MTH_GTIN.ACCT_SKID,ACTVY_MTH_GTIN.BUS_UNIT_SKID,
ACTVY_MTH_GTIN.FY_DATE_SKID,PRMTN.PRMTN_STTUS_CODE,
PRMTN.APPRV_STTUS_CODE,ACTVY.ESTMT_COST_IND,ACTVY.CORP_PRMTN_TYPE_CODE,ACTVY.ACTVY_STOP_DATE,
ACTVY.PYMT_ALLWD_STOP_IND,CAL.MTH_START_DATE,ROUND(NVL(DECODE(ACTVY.COST_TYPE_CODE,'% Fund',(ACTVY_MTH_GTIN.ESTMT_VAR_COST * -- added by Rita for defect 3105 in R10
ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE),DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,
'AnnualAgreement',AA.ESTMT_VAR_COST_AMT,ESTMT_VAR_COST.ESTMT_VAR_COST_AMT)),0),7) AS ESTMT_VAR_COST_AMT,
-- Modified by Simon For CR389 in R10 on 2010-3-18
ROUND(NVL(DECODE(ACTVY.COST_TYPE_CODE,
-- % Fund
'% Fund',ACTVY_MTH_GTIN.ESTMT_FIX_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
-- Fixed
'Fixed',ACTVY_MTH_GTIN.ESTMT_FIX_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
-- Not % Fund or Fixed
DECODE(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',
SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),
SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)),
0,ACTVY_MTH_GTIN.ESTMT_FIX_COST * BRAND_MTH_RATE,ACTVY_MTH_GTIN.ESTMT_FIX_COST * NVL(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'AnnualAgreement',AA.ESTMT_VAR_COST_AMT,ESTMT_VAR_COST.ESTMT_VAR_COST_AMT),0) / DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))
OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))
OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)))),0),7) AS ESTMT_FIXED_COST_AMT,
-- Change in R10 for Revised Cost logic
ROUND(NVL(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',AA.REVSD_ESTMT_VAR_COST_AMT,
REVSD_VAR_COST.REVSD_ESTMT_VAR_COST_AMT),0),7) AS REVSD_ESTMT_VAR_COST_AMT,
ROUND(NVL(ESTMT_VAR_COST.REVSD_BPT_COST_AMT, 0), 7) AS REVSD_BPT_COST_AMT,
ROUND(NVL((ACTVY_MTH_GTIN.ACTL_VAR_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ACTL_WGHT_RATE),0),7)
AS ACTL_VAR_COST_AMT,ROUND(NVL((ACTVY_MTH_GTIN.ACTL_FIX_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ACTL_WGHT_RATE),0),7) AS ACTL_FIXED_COST_AMT,ROUND(NVL(DECODE(ACTVY.COST_TYPE_CODE,'% Fund',
ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
'Fixed',ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
DECODE(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))
OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))
OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)),0,ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT *
BRAND_MTH_RATE,ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT *
NVL(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',AA.ESTMT_VAR_COST_AMT,
ESTMT_VAR_COST.ESTMT_VAR_COST_AMT),0) /DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,
'Annual Agreement',SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))
OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))
OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)))),0),7) AS MANUL_COST_OVRRD_AMT
FROM OPT_ACTVY_DIM ACTVY,OPT_PRMTN_DIM PRMTN,OPT_CAL_MASTR_DIM CAL,
(SELECT ACTVY.ACTVY_SKID,ACTVY_GTIN_BRAND.ACTVY_ID,ACTVY.FUND_SKID,
ACTVY.ACCT_PRMTN_SKID AS ACCT_SKID,ACTVY_GTIN_BRAND.PROD_SKID,ACTVY_GTIN_BRAND.PROD_ID,
ACTVY_GTIN_BRAND.PRMTN_SKID,ACTVY.BUS_UNIT_SKID,ACTVY_GTIN_BRAND.MTH_SKID,
ACTVY_GTIN_BRAND.FY_DATE_SKID,ACTVY.VAR_COST_ESTMT_AMT AS ESTMT_VAR_COST,
ACTVY.PRDCT_FIXED_COST_AMT AS ESTMT_FIX_COST,ACTVY.CALC_INDEX_NUM AS ACTL_FIX_COST,
ACTVY.ACTL_VAR_COST_NUM AS ACTL_VAR_COST,ACTVY.ESTMT_COST_OVRRD_AMT,ACTVY.MANUL_COST_OVRRD_AMT,
ACTVY_GTIN_BRAND.ACTVY_GTIN_ACTL_WGHT_RATE,ACTVY_GTIN_BRAND.ACTVY_GTIN_ESTMT_WGHT_RATE,
ACTVY_GTIN_BRAND.BRAND_MTH_RATE FROM OPT_ACTVY_FCT ACTVY,
OPT_ACTVY_GTIN_BRAND_SFCT ACTVY_GTIN_BRAND, OPT_ACCT_DIM ACCT
WHERE ACTVY.ACTVY_SKID = ACTVY_GTIN_BRAND.ACTVY_SKID AND ACCT.ACCT_SKID = ACTVY.ACCT_PRMTN_SKID
-- Optima11, B018, 9-Oct-2010, Kingham, filter out TSP account
AND ACCT.FUND_FRCST_MODEL_DESC not like 'TSP%') ACTVY_MTH_GTIN,
--Estamate variable cost aggregated to brand level
(SELECT ESTMT.ACTVY_ID AS ACTVY_ID,BRAND_HIER.BRAND_ID AS PROD_ID,
ESTMT.DATE_SKID AS DATE_SKID,ESTMT.BUS_UNIT_SKID AS BUS_UNIT_SKID,
SUM(ESTMT.ESTMT_VAR_COST_AMT) AS ESTMT_VAR_COST_AMT,
SUM(ESTMT.REVSD_BPT_COST_AMT) AS REVSD_BPT_COST_AMT
FROM OPT_ACTVY_GTIN_ESTMT_SFCT ESTMT, -- add by rita
OPT_PROD_BRAND_ASSOC_DIM BRAND_HIER,CAL_MASTR_DIM CAL
WHERE ESTMT.PROD_ID = BRAND_HIER.PROD_ID AND ESTMT.DATE_SKID = CAL.CAL_MASTR_SKID
AND CAL.FISC_YR_SKID = BRAND_HIER.FY_DATE_SKID GROUP BY ESTMT.ACTVY_ID,
BRAND_HIER.BRAND_ID,ESTMT.DATE_SKID,ESTMT.BUS_UNIT_SKID) ESTMT_VAR_COST,
--Revised variable cost aggregated to brand level
(SELECT REVSD.ACTVY_ID AS ACTVY_ID,BRAND_HIER.BRAND_ID AS PROD_ID,
REVSD.DATE_SKID AS DATE_SKID,REVSD.BUS_UNIT_SKID AS BUS_UNIT_SKID,
SUM(REVSD.REVSD_ESTMT_VAR_COST_AMT) AS REVSD_ESTMT_VAR_COST_AMT
FROM OPT_ACTVY_GTIN_REVSD_SFCT REVSD,OPT_PROD_BRAND_ASSOC_DIM BRAND_HIER,
CAL_MASTR_DIM CAL WHERE REVSD.PROD_ID = BRAND_HIER.PROD_ID
AND REVSD.DATE_SKID = CAL.CAL_MASTR_SKID AND CAL.FISC_YR_SKID = BRAND_HIER.FY_DATE_SKID
GROUP BY REVSD.ACTVY_ID,
BRAND_HIER.BRAND_ID,REVSD.DATE_SKID,REVSD.BUS_UNIT_SKID) REVSD_VAR_COST,
--AA Variable Cost aggregated to Brand Level
(SELECT AA.ACTVY_ID AS ACTVY_ID,BRAND_HIER.BRAND_ID AS PROD_ID,AA.MTH_SKID AS DATE_SKID,
AA.BUS_UNIT_SKID AS BUS_UNIT_SKID,SUM(AA.ESTMT_VAR_COST_AMT) AS ESTMT_VAR_COST_AMT,
SUM(AA.REVSD_VAR_ESTMT_COST_AMT) AS REVSD_ESTMT_VAR_COST_AMT FROM OPT_ACTVY_BUOM_GTIN_COST_TFADS AA,
OPT_PROD_BRAND_ASSOC_DIM BRAND_HIER WHERE AA.BUOM_GTIN_PROD_SKID = BRAND_HIER.PROD_SKID
AND BRAND_HIER.FY_DATE_SKID = AA.FY_DATE_SKID GROUP BY AA.ACTVY_ID,
BRAND_HIER.BRAND_ID,AA.MTH_SKID,AA.BUS_UNIT_SKID) AA
WHERE ACTVY_MTH_GTIN.ACTVY_ID = ESTMT_VAR_COST.ACTVY_ID(+)
AND ACTVY_MTH_GTIN.MTH_SKID = ESTMT_VAR_COST.DATE_SKID(+)
AND ACTVY_MTH_GTIN.PROD_ID = ESTMT_VAR_COST.PROD_ID(+)
AND ACTVY_MTH_GTIN.ACTVY_ID = REVSD_VAR_COST.ACTVY_ID(+)
AND ACTVY_MTH_GTIN.MTH_SKID = REVSD_VAR_COST.DATE_SKID(+)
AND ACTVY_MTH_GTIN.PROD_ID = REVSD_VAR_COST.PROD_ID(+)
AND ACTVY_MTH_GTIN.ACTVY_ID = AA.ACTVY_ID(+)
AND ACTVY_MTH_GTIN.MTH_SKID = AA.DATE_SKID(+)
AND ACTVY_MTH_GTIN.PROD_ID = AA.PROD_ID(+)
AND ACTVY_MTH_GTIN.ACTVY_SKID = ACTVY.ACTVY_SKID
AND ACTVY_MTH_GTIN.PRMTN_SKID = PRMTN.PRMTN_SKID
AND ACTVY_MTH_GTIN.MTH_SKID = CAL.CAL_MASTR_SKID))
SQL 的执行计划如下(为了方便排版,我们删除了部分无关紧要的信息)。
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
Plan hash value: 2005223222
-------------------------------------------------------------------------------------
| Id |Operation |Name |Rows |
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 1|
| 1 | VIEW | | 1|
| 2 | WINDOW BUFFER | | 1|
| 3 | VIEW | | 1|
| 4 | WINDOW SORT | | 1|
| 5 | NESTED LOOPS | | |
| 6 | NESTED LOOPS | | 1|
| 7 | NESTED LOOPS | | 1|
|* 8 | HASH JOIN OUTER | | 1|
|* 9 | HASH JOIN OUTER | | 1|
|*10 | HASH JOIN OUTER | | 1|
|*11 | HASH JOIN | | 1|
| 12 | NESTED LOOPS | | |
| 13 | NESTED LOOPS | | 1|
|*14 | HASH JOIN | | 1|
| 15 | PARTITION LIST ALL | | 1|
|*16 | TABLE ACCESS FULL |OPT_ACCT_DIM | 1|
| 17 | PARTITION LIST ALL | | 114K|
| 18 | TABLE ACCESS FULL |OPT_ACTVY_FCT | 114K|
|*19 | INDEX RANGE SCAN |OPT_ACTVY_DIM_PK | 1|
| 20 | TABLE ACCESS BY GLOBAL INDEX ROWID|OPT_ACTVY_DIM | 1|
| 21 | PARTITION LIST ALL | | 19M|
| 22 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_BRAND_SFCT | 19M|
| 23 | VIEW | | 1|
| 24 | HASH GROUP BY | | 1|
| 25 | NESTED LOOPS | | |
| 26 | NESTED LOOPS | | 1|
| 27 | TABLE ACCESS FULL |OPT_ACTVY_BUOM_GTIN_COST_TFADS| 1|
|*28 | INDEX RANGE SCAN |OPT_PROD_BRAND_ASSOC_DIM_PK | 1|
| 29 | TABLE ACCESS BY GLOBAL INDEX ROWID|OPT_PROD_BRAND_ASSOC_DIM | 1|
| 30 | VIEW | | 718|
| 31 | HASH GROUP BY | | 718|
|*32 | HASH JOIN | | 718|
|*33 | HASH JOIN | | 872|
| 34 | PARTITION LIST ALL | | 872|
| 35 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_REVSD_SFCT | 872|
| 36 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
| 37 | PARTITION LIST ALL | | 671K|
| 38 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
| 39 | VIEW | | 6174|
| 40 | HASH GROUP BY | | 6174|
|*41 | HASH JOIN | | 6174|
|*42 | HASH JOIN | | 8998|
| 43 | PARTITION LIST ALL | | 8998|
| 44 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_ESTMT_SFCT | 8998|
| 45 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
| 46 | PARTITION LIST ALL | | 671K|
| 47 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
| 48 | TABLE ACCESS BY INDEX ROWID |OPT_CAL_MASTR_DIM | 1|
|*49 | INDEX UNIQUE SCAN |OPT_CAL_MASTR_DIM_PK | 1|
|*50 | INDEX RANGE SCAN |OPT_PRMTN_DIM_PK | 1|
| 51 | TABLE ACCESS BY GLOBAL INDEX ROWID |OPT_PRMTN_DIM | 1|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
8 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="ESTMT_VAR_COST"."ACTVY_ID"(+) AND
"ACTVY_GTIN_BRAND"."MTH_SKID"="ESTMT_VAR_COST"."DATE_SKID"(+) AND
"ACTVY_GTIN_BRAND"."PROD_ID"="ESTMT_VAR_COST"."PROD_ID"(+))
9 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="REVSD_VAR_COST"."ACTVY_ID"(+) AND
"ACTVY_GTIN_BRAND"."MTH_SKID"="REVSD_VAR_COST"."DATE_SKID"(+) AND
"ACTVY_GTIN_BRAND"."PROD_ID"="REVSD_VAR_COST"."PROD_ID"(+))
10 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="AA"."ACTVY_ID"(+) AND
"ACTVY_GTIN_BRAND"."MTH_SKID"="AA"."DATE_SKID"(+) AND
"ACTVY_GTIN_BRAND"."PROD_ID"="AA"."PROD_ID"(+))
11 - access("ACTVY"."ACTVY_SKID"="ACTVY_GTIN_BRAND"."ACTVY_SKID")
14 - access("ACCT"."ACCT_SKID"="ACTVY"."ACCT_PRMTN_SKID")
16 - filter("ACCT"."FUND_FRCST_MODEL_DESC" NOT LIKE 'TSP%')
19 - access("ACTVY"."ACTVY_SKID"="ACTVY"."ACTVY_SKID")
28 - access("AA"."BUOM_GTIN_PROD_SKID"="BRAND_HIER"."PROD_SKID" AND
"BRAND_HIER"."FY_DATE_SKID"="AA"."FY_DATE_SKID")
32 - access("REVSD"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
"CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
33 - access("REVSD"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
41 - access("ESTMT"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
"CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
42 - access("ESTMT"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
49 - access("ACTVY_GTIN_BRAND"."MTH_SKID"="CAL"."CAL_MASTR_SKID")
50 - access("ACTVY_GTIN_BRAND"."PRMTN_SKID"="PRMTN"."PRMTN_SKID")
该 SQL 是用来做数据清洗的(ETL),需要处理大量数据。处理大量数据应该走 HASH 连接,因此该执行计划是错误的,因为执行计划中有大量的嵌套循环。
注意观察执行计划,执行计划中 Id=16 和 Id=27 优化器评估只返回 1 行数据,因此怀疑 OPT_ACCT_DIM 和 OPT_ACTVY_BUOM_GTIN_COST_TFADS 这两个表统计信息有问题。对这两个表收集完统计信息之后,我们再来看一下执行计划。
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
Plan hash value: 183294992
-------------------------------------------------------------------------------------
| Id |Operation |Name |Rows |
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 19M|
| 1 | VIEW | | 19M|
| 2 | WINDOW BUFFER | | 19M|
| 3 | VIEW | | 19M|
| 4 | WINDOW SORT | | 19M|
|* 5 | HASH JOIN | | 19M|
| 6 | PARTITION LIST ALL | |37880|
| 7 | TABLE ACCESS FULL |OPT_PRMTN_DIM |37880|
|* 8 | HASH JOIN | | 19M|
| 9 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
|*10 | HASH JOIN RIGHT OUTER | | 19M|
| 11 | VIEW | | 6174|
| 12 | HASH GROUP BY | | 6174|
|*13 | HASH JOIN | | 6174|
|*14 | HASH JOIN | | 8998|
| 15 | PARTITION LIST ALL | | 8998|
| 16 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_ESTMT_SFCT | 8998|
| 17 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
| 18 | PARTITION LIST ALL | | 671K|
| 19 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
|*20 | HASH JOIN RIGHT OUTER | | 19M|
| 21 | VIEW | | 718|
| 22 | HASH GROUP BY | | 718|
|*23 | HASH JOIN | | 718|
|*24 | HASH JOIN | | 872|
| 25 | PARTITION LIST ALL | | 872|
| 26 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_REVSD_SFCT | 872|
| 27 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
| 28 | PARTITION LIST ALL | | 671K|
| 29 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
|*30 | HASH JOIN RIGHT OUTER | | 19M|
| 31 | VIEW | | 1|
| 32 | HASH GROUP BY | | 1|
| 33 | NESTED LOOPS | | |
| 34 | NESTED LOOPS | | 1|
| 35 | TABLE ACCESS FULL |OPT_ACTVY_BUOM_GTIN_COST_TFADS| 1|
|*36 | INDEX RANGE SCAN |OPT_PROD_BRAND_ASSOC_DIM_PK | 1|
| 37 | TABLE ACCESS BY GLOBAL INDEX ROWID|OPT_PROD_BRAND_ASSOC_DIM | 1|
|*38 | HASH JOIN | | 19M|
|*39 | HASH JOIN | | 114K|
| 40 | PARTITION LIST ALL | | 115K|
| 41 | TABLE ACCESS FULL |OPT_ACTVY_DIM | 115K|
|*42 | HASH JOIN | | 114K|
| 43 | PARTITION LIST ALL | |94478|
|*44 | TABLE ACCESS FULL |OPT_ACCT_DIM |94478|
| 45 | PARTITION LIST ALL | | 114K|
| 46 | TABLE ACCESS FULL |OPT_ACTVY_FCT | 114K|
| 47 | PARTITION LIST ALL | | 19M|
| 48 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_BRAND_SFCT | 19M|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("ACTVY_GTIN_BRAND"."PRMTN_SKID"="PRMTN"."PRMTN_SKID")
8 - access("ACTVY_GTIN_BRAND"."MTH_SKID"="CAL"."CAL_MASTR_SKID")
10 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="ESTMT_VAR_COST"."ACTVY_ID"(+)
AND "ACTVY_GTIN_BRAND"."MTH_SKID"="ESTMT_VAR_COST"."DATE_SKID"(+) AND
"ACTVY_GTIN_BRAND"."PROD_ID"="ESTMT_VAR_COST"."PROD_ID"(+))
13 - access("ESTMT"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
"CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
14 - access("ESTMT"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
20 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="REVSD_VAR_COST"."ACTVY_ID"(+)
AND "ACTVY_GTIN_BRAND"."MTH_SKID"="REVSD_VAR_COST"."DATE_SKID"(+) AND
"ACTVY_GTIN_BRAND"."PROD_ID"="REVSD_VAR_COST"."PROD_ID"(+))
23 - access("REVSD"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
"CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
24 - access("REVSD"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
30 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="AA"."ACTVY_ID"(+) AND
"ACTVY_GTIN_BRAND"."MTH_SKID"="AA"."DATE_SKID"(+) AND
"ACTVY_GTIN_BRAND"."PROD_ID"="AA"."PROD_ID"(+))
36 - access("AA"."BUOM_GTIN_PROD_SKID"="BRAND_HIER"."PROD_SKID" AND
"BRAND_HIER"."FY_DATE_SKID"="AA"."FY_DATE_SKID")
38 - access("ACTVY"."ACTVY_SKID"="ACTVY_GTIN_BRAND"."ACTVY_SKID")
39 - access("ACTVY"."ACTVY_SKID"="ACTVY"."ACTVY_SKID")
42 - access("ACCT"."ACCT_SKID"="ACTVY"."ACCT_PRMTN_SKID")
44 - filter("ACCT"."FUND_FRCST_MODEL_DESC" NOT LIKE 'TSP%')
执行计划中,除了 Id=35 和 Id=37 两个表没有走 HASH 连接之外,其余表都走了 HASH 连接。Id=35 的表 OPT_ACTVY_BUOM_GTIN_COST_TFADS 之前已经收集过统计信息,因此 Id=35 和 Id=37 的表走嵌套循环没有问题,那么整个 SQL 的执行计划现在是正确的。纠正完执行计划之后,笔者将 SQL 放在后台运行了大概两小时,发现 SQL 还没执行完毕。起初,笔者认为 SQL 执行 7 个小时还没跑完是因为 SQL 执行计划错误导致的,但是现在纠正了 SQL 的执行计划,SQL 执行了两小时还是没有跑完,于是监控 SQL 的等待事件,看 SQL 究竟在等什么。
select inst_id,sid,serial#,event,p1,p2,p3
from gv$session where osuser='luobi';
INST_ID SID SERIAL# EVENT P1 P2 P3
--------- ---------- ---------- ---------------------- ------ ---------- ----------
2 4754 10050 direct path write temp 20025 857328 7
SQL> /
INST_ID SID SERIAL# EVENT P1 P2 P3
-------- ---------- ---------- ----------------------- ------ ---------- ----------
2 4754 10050 direct path write temp 20025 406768 7
SQL> /
INST_ID SID SERIAL# EVENT P1 P2 P3
-------- ---------- ---------- ----------------------- ------ ---------- ----------
2 4754 10050 direct path write temp 20007 2849264 7
SQL> /
INST_ID SID SERIAL# EVENT P1 P2 P3
-------- ---------- ---------- ----------------------- ------ ---------- ----------
2 4754 10050 direct path write temp 20007 115341 7
SQL> /
INST_ID SID SERIAL# EVENT P1 P2 P3
-------- ---------- ---------- ------------------------ ------ ---------- ---------
2 4754 10050 direct path write temp 20007 81029 7
我们监控到该 SQL 的等待事件为 direct path write temp,该等待事件表示当前 SQL 正在进行排序或者正在进行 HASH 连接,但是因为 PGA 不够大,不能完全容纳需要排序或者需要 HASH 的数据,导致有部分数据被写入 temp 表空间。
为了追查究竟是因为排序还是因为 HASH 而引发的 direct path write temp 等待,使用以下脚本查看临时段数据类型。
select a.inst_id, a.sid, a.serial#, a.sql_id, b.tablespace, b.blocks*
(select value from v$parameter where name='db_block_size')/1024/1024 "Size(M)",b.segtype
from gv$session a, gv$tempseg_usage b where a.inst_id=b.inst_id and a.saddr = b.session_addr
and a.inst_id=2 and a.sid=4754;
INST_ID SID SERIAL# SQL_ID TABLESPACE Size(M) SEGTYPE
------- ---------- ------------- ------------- --------------------- ---------
2 4754 10050 6qsuc8mafy20m TEMP 1 DATA
2 4754 10050 6qsuc8mafy20m TEMP 1 LOB_DATA
2 4754 10050 6qsuc8mafy20m TEMP 1 INDEX
2 4754 10050 6qsuc8mafy20m TEMP 1 LOB_DATA
2 4754 10050 6qsuc8mafy20m TEMP 3304 HASH
从 SQL 查询中我们看到,临时段数据类型为 HASH,耗费了 3 304MB 的 temp 表空间,这表示 SQL 是因为 HASH 连接引发的 direct path write temp 等待。
大家请仔细观察等待事件 P3,它的值一直为 7,这表示 Oracle 一次只写入 7 个块到 temp 表空间,而且是一直只写入 7 个块到 temp 表空间。笔者在第 4 章中讲到,绝大多数的操作系统,一次 I/O 最多只能读取或者写入 1MB 数据。这里的数据块大小为 16KB,正常情况下应该是每次 I/O 写入 64 个块到 temp 表空间,但是每次 I/O 只写了 7 个块。于是怀疑是 PGA 中 work area 不够导致出现了该问题。
PGA 在自动管理的情况下,单个 PGA 进程的 work area 不能超过 1GB(想要超过 1GB 需要修改隐含参数,但是本书主题是 SQL 优化,因此不想太多涉及到 Oracle 内部原理),如果 PGA 是手动管理,单个 PGA 进程的 work area 可以接近 2GB,但是不能超过 2GB。
alter session set workarea_size_policy = manual;
Session altered.
alter session set hash_area_size = 2147483648; ---2GB
alter session set hash_area_size = 2147483648
ERROR at line 1:
ORA-02017: integer value required
alter session set hash_area_size = 2147483647;
Session altered.
将 PGA 的 work area 设置为接近 2GB 之后,重新运行了 SQL 并且监控等待事件。
select inst_id,sid,serial#,event,p1,p2,p3
from gv$session where osuser='luobi';
INST_ID SID SERIAL# EVENT P1 P2 P3
-------- ---------- ---------- ------------------------------- ---------- ----------
2 4885 11759 direct path write temp 20012 71053 64
将 PGA 的 work area 设置为接近 2GB 之后,笔者发现 P3 可以达到 64,相比之前一次只能写入 7 个块速度提升了 9 倍。
有 direct path write temp 等待必然会出现 direct path read temp 等待,在没修改 PGA 的 work area 之前,不仅仅是单次 I/O 只能写入 7 个块,单次 I/O 读取也是只能读取 7 个块,因此,将 PGA 的 work area 设置为接近 2GB 之后,整个 SQL 的性能应该提升了 18 倍。
最后,经过对比测试,手动设置 work area 的 SQL 只需要 56 分钟左右就能执行完毕。
6889440 rows selected.
Elapsed: 00:56:36.08
而自动 work area 管理的 SQL 还在一直等待 direct path write temp,估计该 SQL 如果不手动设置 work area 可能跑一天一夜都跑不完。
优化完上述 SQL 之后,我们发现当时整个平台已经瘫痪,整个平台都出现了 P3=7 的问题,最后经过与 Oracle 确认,发现该问题是 11.1.0.7 版本在 HPUX 平台下的一个 bug。Oracle 开发补丁需要一定的时间,在此期间,使用本书给出的方法临时解决了项目中遇到的问题,确保项目不会因此延期。