本案例发生在 2010 年,当时作者罗老师在惠普担任开发 DBA,支撑宝洁公司的数据仓库项目。ETL 开发人员需要帮助调查一个 long running 的 JOB,该 JOB 执行了 7 个小时还没执行完。

    数据库环境为 11.1.0.7(RAC,4 节点)。

    1. select * from v$version;
    1. BANNER
    2. -------------------------------------------------------------------------------
    3. Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production

    数据块大小为 16k。

    1. show parameter db_block_size
    1. NAME TYPE VALUE
    2. ------------------------------------ --------------------------------- ------
    3. db_block_size integer 16384

    执行得慢的 JOB 是一个 insert into …select …语句。一般情况下,如果 select 语句跑得快,那么整个 JOB 也就跑得快,因此我们应该把主要精力放在 select 语句上面。select 部分的 SQL 语句如下,这是一个接近 400 行的 SQL(因为 SQL 实在太长,所以没有对 SQL 格式化)。

    1. SELECT ACTVY_SKID,FUND_SKID,PRMTN_SKID,PROD_SKID,DATE_SKID,
    2. ACCT_SKID,BUS_UNIT_SKID,FY_DATE_SKID,ESTMT_VAR_COST_AMT,ESTMT_FIXED_COST_AMT,
    3. REVSD_ESTMT_VAR_COST_AMT,ACTL_VAR_COST_AMT,ACTL_FIXED_COST_AMT,COST_PLAN_AMT,
    4. COST_CMMT_AMT,COST_BOOK_AMT,ESTMT_COST_OVRRD_AMT,LA_TOT_BOOK_AMT,
    5. MANUL_COST_OVRRD_AMT,ACTL_COST_AMT
    6. FROM (SELECT ACTVY_SKID,FUND_SKID,PROD_SKID,PRMTN_SKID,DATE_SKID,ACCT_SKID,
    7. BUS_UNIT_SKID,FY_DATE_SKID,ESTMT_VAR_COST_AMT,ESTMT_FIXED_COST_AMT,
    8. REVSD_ESTMT_VAR_COST_AMT,0 as ACTL_COST_AMT,ACTL_VAR_COST_AMT,ACTL_FIXED_COST_AMT,
    9. MANUL_COST_OVRRD_AMT,ESTMT_COST_OVRRD_AMT,COST_BOOK_AMT,
    10. -- Updated by Luke for QC3369
    11. -- If the committed amount on Activity level <0 then return 0
    12. (CASE WHEN SUM(ESTMT_COST_OVRRD_AMT - ACTL_VAR_COST_AMT -
    13. ACTL_FIXED_COST_AMT) OVER(PARTITION BY ACTVY_SKID) < 0 THEN 0
    14. ELSE COST_CMMT_AMT END) AS COST_CMMT_AMT,
    15. -- Updated by Luke for QC3369
    16. (CASE WHEN SUM(ESTMT_COST_OVRRD_AMT - ACTL_VAR_COST_AMT -
    17. ACTL_FIXED_COST_AMT) OVER(PARTITION BY ACTVY_SKID) < 0 THEN 0
    18. ELSE COST_PLAN_AMT END) AS COST_PLAN_AMT,LA_TOT_BOOK_AMT
    19. FROM (SELECT ACTVY_SKID,FUND_SKID,PROD_SKID,PRMTN_SKID,
    20. DATE_SKID,ACCT_SKID,BUS_UNIT_SKID,FY_DATE_SKID,ESTMT_VAR_COST_AMT,
    21. ESTMT_FIXED_COST_AMT,REVSD_ESTMT_VAR_COST_AMT,ACTL_VAR_COST_AMT,
    22. ACTL_FIXED_COST_AMT,MANUL_COST_OVRRD_AMT,
    23. (CASE WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'E' THEN
    24. 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,
    25. --Ax Revised Estimated Variable Cost REVSD_BPT_COST_AMT) --BPT Revised Cost
    26. WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'M' THEN MANUL_COST_OVRRD_AMT
    27. WHEN ESTMT_COST_IND IS NULL THEN DECODE(CORP_PRMTN_TYPE_CODE,
    28. 'Annual Agreement',ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,
    29. REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost
    30. REVSD_BPT_COST_AMT), --BPT Revised Cost
    31. ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END) AS ESTMT_COST_OVRRD_AMT,
    32. (ACTL_VAR_COST_AMT + ACTL_FIXED_COST_AMT) AS COST_BOOK_AMT,
    33. DECODE(PRMTN_STTUS_CODE,'Confirmed',
    34. --Estimate Total Cost - Actual Cost
    35. --Add the logic of Activity Stop date and Pyment allow IND
    36. --For Defect 2913 Luke 2010-5-5
    37. (CASE WHEN (ACTVY_STOP_DATE IS NULL OR ACTVY_STOP_DATE > SYSDATE OR
    38. 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,
    39. --Ax Revised Estimated Variable Cost
    40. REVSD_BPT_COST_AMT) --BPT Revised Cost
    41. WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'M' THEN MANUL_COST_OVRRD_AMT
    42. WHEN ESTMT_COST_IND IS NULL THEN DECODE(CORP_PRMTN_TYPE_CODE,'Annual Agreement',
    43. ESTMT_FIXED_COST_AMT + DECODE(REVSD_BPT_COST_AMT,0,REVSD_ESTMT_VAR_COST_AMT,
    44. --Ax Revised Estimated Variable Cost
    45. REVSD_BPT_COST_AMT), --BPT Revised Cost
    46. ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END) - (ACTL_VAR_COST_AMT + ACTL_FIXED_COST_AMT)
    47. ELSE 0 END), 0) AS COST_CMMT_AMT,(CASE WHEN (PRMTN_STTUS_CODE IN ('Planned', 'Revised') AND NVL(APPRV_STTUS_CODE, 'Nothing') <> 'Rejected' AND
    48. --Add the logic of Activity Stop date and Pyment allow IND
    49. --For Defect 2913 Luke 2010-5-5
    50. (ACTVY_STOP_DATE IS NULL OR ACTVY_STOP_DATE > SYSDATE OR NVL(PYMT_ALLWD_STOP_IND, 'N') = 'Y'))
    51. THEN (CASE WHEN SUBSTR(ESTMT_COST_IND, 1, 1) = 'E' THEN ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT
    52. 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
    53. 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,
    54. REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost
    55. REVSD_BPT_COST_AMT), --BPT Revised Cost
    56. 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,
    57. --Ax Revised Estimated Variable Cost
    58. REVSD_BPT_COST_AMT) --BPT Revised Cost
    59. 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,
    60. REVSD_ESTMT_VAR_COST_AMT, --Ax Revised Estimated Variable Cost
    61. REVSD_BPT_COST_AMT), --BPT Revised Cost
    62. ESTMT_FIXED_COST_AMT + ESTMT_VAR_COST_AMT) END)
    63. 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,
    64. ACTVY_MTH_GTIN.PRMTN_SKID,ACTVY_MTH_GTIN.MTH_SKID AS DATE_SKID,ACTVY_MTH_GTIN.ACCT_SKID,ACTVY_MTH_GTIN.BUS_UNIT_SKID,
    65. ACTVY_MTH_GTIN.FY_DATE_SKID,PRMTN.PRMTN_STTUS_CODE,
    66. PRMTN.APPRV_STTUS_CODE,ACTVY.ESTMT_COST_IND,ACTVY.CORP_PRMTN_TYPE_CODE,ACTVY.ACTVY_STOP_DATE,
    67. 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
    68. ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE),DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,
    69. 'AnnualAgreement',AA.ESTMT_VAR_COST_AMT,ESTMT_VAR_COST.ESTMT_VAR_COST_AMT)),0),7) AS ESTMT_VAR_COST_AMT,
    70. -- Modified by Simon For CR389 in R10 on 2010-3-18
    71. ROUND(NVL(DECODE(ACTVY.COST_TYPE_CODE,
    72. -- % Fund
    73. '% Fund',ACTVY_MTH_GTIN.ESTMT_FIX_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
    74. -- Fixed
    75. 'Fixed',ACTVY_MTH_GTIN.ESTMT_FIX_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
    76. -- Not % Fund or Fixed
    77. DECODE(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',
    78. SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),
    79. SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)),
    80. 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))
    81. OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))
    82. OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)))),0),7) AS ESTMT_FIXED_COST_AMT,
    83. -- Change in R10 for Revised Cost logic
    84. ROUND(NVL(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',AA.REVSD_ESTMT_VAR_COST_AMT,
    85. REVSD_VAR_COST.REVSD_ESTMT_VAR_COST_AMT),0),7) AS REVSD_ESTMT_VAR_COST_AMT,
    86. ROUND(NVL(ESTMT_VAR_COST.REVSD_BPT_COST_AMT, 0), 7) AS REVSD_BPT_COST_AMT,
    87. ROUND(NVL((ACTVY_MTH_GTIN.ACTL_VAR_COST * ACTVY_MTH_GTIN.ACTVY_GTIN_ACTL_WGHT_RATE),0),7)
    88. 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',
    89. ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
    90. 'Fixed',ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT * ACTVY_MTH_GTIN.ACTVY_GTIN_ESTMT_WGHT_RATE,
    91. DECODE(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))
    92. OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))
    93. OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)),0,ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT *
    94. BRAND_MTH_RATE,ACTVY_MTH_GTIN.MANUL_COST_OVRRD_AMT *
    95. NVL(DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,'Annual Agreement',AA.ESTMT_VAR_COST_AMT,
    96. ESTMT_VAR_COST.ESTMT_VAR_COST_AMT),0) /DECODE(ACTVY.CORP_PRMTN_TYPE_CODE,
    97. 'Annual Agreement',SUM(NVL(AA.ESTMT_VAR_COST_AMT,0))
    98. OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID),SUM(NVL(ESTMT_VAR_COST.ESTMT_VAR_COST_AMT,0))
    99. OVER(PARTITION BY ACTVY_MTH_GTIN.ACTVY_SKID)))),0),7) AS MANUL_COST_OVRRD_AMT
    100. FROM OPT_ACTVY_DIM ACTVY,OPT_PRMTN_DIM PRMTN,OPT_CAL_MASTR_DIM CAL,
    101. (SELECT ACTVY.ACTVY_SKID,ACTVY_GTIN_BRAND.ACTVY_ID,ACTVY.FUND_SKID,
    102. ACTVY.ACCT_PRMTN_SKID AS ACCT_SKID,ACTVY_GTIN_BRAND.PROD_SKID,ACTVY_GTIN_BRAND.PROD_ID,
    103. ACTVY_GTIN_BRAND.PRMTN_SKID,ACTVY.BUS_UNIT_SKID,ACTVY_GTIN_BRAND.MTH_SKID,
    104. ACTVY_GTIN_BRAND.FY_DATE_SKID,ACTVY.VAR_COST_ESTMT_AMT AS ESTMT_VAR_COST,
    105. ACTVY.PRDCT_FIXED_COST_AMT AS ESTMT_FIX_COST,ACTVY.CALC_INDEX_NUM AS ACTL_FIX_COST,
    106. ACTVY.ACTL_VAR_COST_NUM AS ACTL_VAR_COST,ACTVY.ESTMT_COST_OVRRD_AMT,ACTVY.MANUL_COST_OVRRD_AMT,
    107. ACTVY_GTIN_BRAND.ACTVY_GTIN_ACTL_WGHT_RATE,ACTVY_GTIN_BRAND.ACTVY_GTIN_ESTMT_WGHT_RATE,
    108. ACTVY_GTIN_BRAND.BRAND_MTH_RATE FROM OPT_ACTVY_FCT ACTVY,
    109. OPT_ACTVY_GTIN_BRAND_SFCT ACTVY_GTIN_BRAND, OPT_ACCT_DIM ACCT
    110. WHERE ACTVY.ACTVY_SKID = ACTVY_GTIN_BRAND.ACTVY_SKID AND ACCT.ACCT_SKID = ACTVY.ACCT_PRMTN_SKID
    111. -- Optima11, B018, 9-Oct-2010, Kingham, filter out TSP account
    112. AND ACCT.FUND_FRCST_MODEL_DESC not like 'TSP%') ACTVY_MTH_GTIN,
    113. --Estamate variable cost aggregated to brand level
    114. (SELECT ESTMT.ACTVY_ID AS ACTVY_ID,BRAND_HIER.BRAND_ID AS PROD_ID,
    115. ESTMT.DATE_SKID AS DATE_SKID,ESTMT.BUS_UNIT_SKID AS BUS_UNIT_SKID,
    116. SUM(ESTMT.ESTMT_VAR_COST_AMT) AS ESTMT_VAR_COST_AMT,
    117. SUM(ESTMT.REVSD_BPT_COST_AMT) AS REVSD_BPT_COST_AMT
    118. FROM OPT_ACTVY_GTIN_ESTMT_SFCT ESTMT, -- add by rita
    119. OPT_PROD_BRAND_ASSOC_DIM BRAND_HIER,CAL_MASTR_DIM CAL
    120. WHERE ESTMT.PROD_ID = BRAND_HIER.PROD_ID AND ESTMT.DATE_SKID = CAL.CAL_MASTR_SKID
    121. AND CAL.FISC_YR_SKID = BRAND_HIER.FY_DATE_SKID GROUP BY ESTMT.ACTVY_ID,
    122. BRAND_HIER.BRAND_ID,ESTMT.DATE_SKID,ESTMT.BUS_UNIT_SKID) ESTMT_VAR_COST,
    123. --Revised variable cost aggregated to brand level
    124. (SELECT REVSD.ACTVY_ID AS ACTVY_ID,BRAND_HIER.BRAND_ID AS PROD_ID,
    125. REVSD.DATE_SKID AS DATE_SKID,REVSD.BUS_UNIT_SKID AS BUS_UNIT_SKID,
    126. SUM(REVSD.REVSD_ESTMT_VAR_COST_AMT) AS REVSD_ESTMT_VAR_COST_AMT
    127. FROM OPT_ACTVY_GTIN_REVSD_SFCT REVSD,OPT_PROD_BRAND_ASSOC_DIM BRAND_HIER,
    128. CAL_MASTR_DIM CAL WHERE REVSD.PROD_ID = BRAND_HIER.PROD_ID
    129. AND REVSD.DATE_SKID = CAL.CAL_MASTR_SKID AND CAL.FISC_YR_SKID = BRAND_HIER.FY_DATE_SKID
    130. GROUP BY REVSD.ACTVY_ID,
    131. BRAND_HIER.BRAND_ID,REVSD.DATE_SKID,REVSD.BUS_UNIT_SKID) REVSD_VAR_COST,
    132. --AA Variable Cost aggregated to Brand Level
    133. (SELECT AA.ACTVY_ID AS ACTVY_ID,BRAND_HIER.BRAND_ID AS PROD_ID,AA.MTH_SKID AS DATE_SKID,
    134. AA.BUS_UNIT_SKID AS BUS_UNIT_SKID,SUM(AA.ESTMT_VAR_COST_AMT) AS ESTMT_VAR_COST_AMT,
    135. SUM(AA.REVSD_VAR_ESTMT_COST_AMT) AS REVSD_ESTMT_VAR_COST_AMT FROM OPT_ACTVY_BUOM_GTIN_COST_TFADS AA,
    136. OPT_PROD_BRAND_ASSOC_DIM BRAND_HIER WHERE AA.BUOM_GTIN_PROD_SKID = BRAND_HIER.PROD_SKID
    137. AND BRAND_HIER.FY_DATE_SKID = AA.FY_DATE_SKID GROUP BY AA.ACTVY_ID,
    138. BRAND_HIER.BRAND_ID,AA.MTH_SKID,AA.BUS_UNIT_SKID) AA
    139. WHERE ACTVY_MTH_GTIN.ACTVY_ID = ESTMT_VAR_COST.ACTVY_ID(+)
    140. AND ACTVY_MTH_GTIN.MTH_SKID = ESTMT_VAR_COST.DATE_SKID(+)
    141. AND ACTVY_MTH_GTIN.PROD_ID = ESTMT_VAR_COST.PROD_ID(+)
    142. AND ACTVY_MTH_GTIN.ACTVY_ID = REVSD_VAR_COST.ACTVY_ID(+)
    143. AND ACTVY_MTH_GTIN.MTH_SKID = REVSD_VAR_COST.DATE_SKID(+)
    144. AND ACTVY_MTH_GTIN.PROD_ID = REVSD_VAR_COST.PROD_ID(+)
    145. AND ACTVY_MTH_GTIN.ACTVY_ID = AA.ACTVY_ID(+)
    146. AND ACTVY_MTH_GTIN.MTH_SKID = AA.DATE_SKID(+)
    147. AND ACTVY_MTH_GTIN.PROD_ID = AA.PROD_ID(+)
    148. AND ACTVY_MTH_GTIN.ACTVY_SKID = ACTVY.ACTVY_SKID
    149. AND ACTVY_MTH_GTIN.PRMTN_SKID = PRMTN.PRMTN_SKID
    150. AND ACTVY_MTH_GTIN.MTH_SKID = CAL.CAL_MASTR_SKID))

    SQL 的执行计划如下(为了方便排版,我们删除了部分无关紧要的信息)。

    1. select * from table(dbms_xplan.display);
    1. PLAN_TABLE_OUTPUT
    2. -------------------------------------------------------------------------------------
    3. Plan hash value: 2005223222
    4. -------------------------------------------------------------------------------------
    5. | Id |Operation |Name |Rows |
    6. -------------------------------------------------------------------------------------
    7. | 0 |SELECT STATEMENT | | 1|
    8. | 1 | VIEW | | 1|
    9. | 2 | WINDOW BUFFER | | 1|
    10. | 3 | VIEW | | 1|
    11. | 4 | WINDOW SORT | | 1|
    12. | 5 | NESTED LOOPS | | |
    13. | 6 | NESTED LOOPS | | 1|
    14. | 7 | NESTED LOOPS | | 1|
    15. |* 8 | HASH JOIN OUTER | | 1|
    16. |* 9 | HASH JOIN OUTER | | 1|
    17. |*10 | HASH JOIN OUTER | | 1|
    18. |*11 | HASH JOIN | | 1|
    19. | 12 | NESTED LOOPS | | |
    20. | 13 | NESTED LOOPS | | 1|
    21. |*14 | HASH JOIN | | 1|
    22. | 15 | PARTITION LIST ALL | | 1|
    23. |*16 | TABLE ACCESS FULL |OPT_ACCT_DIM | 1|
    24. | 17 | PARTITION LIST ALL | | 114K|
    25. | 18 | TABLE ACCESS FULL |OPT_ACTVY_FCT | 114K|
    26. |*19 | INDEX RANGE SCAN |OPT_ACTVY_DIM_PK | 1|
    27. | 20 | TABLE ACCESS BY GLOBAL INDEX ROWID|OPT_ACTVY_DIM | 1|
    28. | 21 | PARTITION LIST ALL | | 19M|
    29. | 22 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_BRAND_SFCT | 19M|
    30. | 23 | VIEW | | 1|
    31. | 24 | HASH GROUP BY | | 1|
    32. | 25 | NESTED LOOPS | | |
    33. | 26 | NESTED LOOPS | | 1|
    34. | 27 | TABLE ACCESS FULL |OPT_ACTVY_BUOM_GTIN_COST_TFADS| 1|
    35. |*28 | INDEX RANGE SCAN |OPT_PROD_BRAND_ASSOC_DIM_PK | 1|
    36. | 29 | TABLE ACCESS BY GLOBAL INDEX ROWID|OPT_PROD_BRAND_ASSOC_DIM | 1|
    37. | 30 | VIEW | | 718|
    38. | 31 | HASH GROUP BY | | 718|
    39. |*32 | HASH JOIN | | 718|
    40. |*33 | HASH JOIN | | 872|
    41. | 34 | PARTITION LIST ALL | | 872|
    42. | 35 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_REVSD_SFCT | 872|
    43. | 36 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
    44. | 37 | PARTITION LIST ALL | | 671K|
    45. | 38 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
    46. | 39 | VIEW | | 6174|
    47. | 40 | HASH GROUP BY | | 6174|
    48. |*41 | HASH JOIN | | 6174|
    49. |*42 | HASH JOIN | | 8998|
    50. | 43 | PARTITION LIST ALL | | 8998|
    51. | 44 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_ESTMT_SFCT | 8998|
    52. | 45 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
    53. | 46 | PARTITION LIST ALL | | 671K|
    54. | 47 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
    55. | 48 | TABLE ACCESS BY INDEX ROWID |OPT_CAL_MASTR_DIM | 1|
    56. |*49 | INDEX UNIQUE SCAN |OPT_CAL_MASTR_DIM_PK | 1|
    57. |*50 | INDEX RANGE SCAN |OPT_PRMTN_DIM_PK | 1|
    58. | 51 | TABLE ACCESS BY GLOBAL INDEX ROWID |OPT_PRMTN_DIM | 1|
    59. -------------------------------------------------------------------------------------
    60. Predicate Information (identified by operation id):
    61. ---------------------------------------------------
    62. 8 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="ESTMT_VAR_COST"."ACTVY_ID"(+) AND
    63. "ACTVY_GTIN_BRAND"."MTH_SKID"="ESTMT_VAR_COST"."DATE_SKID"(+) AND
    64. "ACTVY_GTIN_BRAND"."PROD_ID"="ESTMT_VAR_COST"."PROD_ID"(+))
    65. 9 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="REVSD_VAR_COST"."ACTVY_ID"(+) AND
    66. "ACTVY_GTIN_BRAND"."MTH_SKID"="REVSD_VAR_COST"."DATE_SKID"(+) AND
    67. "ACTVY_GTIN_BRAND"."PROD_ID"="REVSD_VAR_COST"."PROD_ID"(+))
    68. 10 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="AA"."ACTVY_ID"(+) AND
    69. "ACTVY_GTIN_BRAND"."MTH_SKID"="AA"."DATE_SKID"(+) AND
    70. "ACTVY_GTIN_BRAND"."PROD_ID"="AA"."PROD_ID"(+))
    71. 11 - access("ACTVY"."ACTVY_SKID"="ACTVY_GTIN_BRAND"."ACTVY_SKID")
    72. 14 - access("ACCT"."ACCT_SKID"="ACTVY"."ACCT_PRMTN_SKID")
    73. 16 - filter("ACCT"."FUND_FRCST_MODEL_DESC" NOT LIKE 'TSP%')
    74. 19 - access("ACTVY"."ACTVY_SKID"="ACTVY"."ACTVY_SKID")
    75. 28 - access("AA"."BUOM_GTIN_PROD_SKID"="BRAND_HIER"."PROD_SKID" AND
    76. "BRAND_HIER"."FY_DATE_SKID"="AA"."FY_DATE_SKID")
    77. 32 - access("REVSD"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
    78. "CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
    79. 33 - access("REVSD"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
    80. 41 - access("ESTMT"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
    81. "CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
    82. 42 - access("ESTMT"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
    83. 49 - access("ACTVY_GTIN_BRAND"."MTH_SKID"="CAL"."CAL_MASTR_SKID")
    84. 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 这两个表统计信息有问题。对这两个表收集完统计信息之后,我们再来看一下执行计划。

    1. select * from table(dbms_xplan.display);
    1. PLAN_TABLE_OUTPUT
    2. -------------------------------------------------------------------------------------
    3. Plan hash value: 183294992
    4. -------------------------------------------------------------------------------------
    5. | Id |Operation |Name |Rows |
    6. -------------------------------------------------------------------------------------
    7. | 0 |SELECT STATEMENT | | 19M|
    8. | 1 | VIEW | | 19M|
    9. | 2 | WINDOW BUFFER | | 19M|
    10. | 3 | VIEW | | 19M|
    11. | 4 | WINDOW SORT | | 19M|
    12. |* 5 | HASH JOIN | | 19M|
    13. | 6 | PARTITION LIST ALL | |37880|
    14. | 7 | TABLE ACCESS FULL |OPT_PRMTN_DIM |37880|
    15. |* 8 | HASH JOIN | | 19M|
    16. | 9 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
    17. |*10 | HASH JOIN RIGHT OUTER | | 19M|
    18. | 11 | VIEW | | 6174|
    19. | 12 | HASH GROUP BY | | 6174|
    20. |*13 | HASH JOIN | | 6174|
    21. |*14 | HASH JOIN | | 8998|
    22. | 15 | PARTITION LIST ALL | | 8998|
    23. | 16 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_ESTMT_SFCT | 8998|
    24. | 17 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
    25. | 18 | PARTITION LIST ALL | | 671K|
    26. | 19 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
    27. |*20 | HASH JOIN RIGHT OUTER | | 19M|
    28. | 21 | VIEW | | 718|
    29. | 22 | HASH GROUP BY | | 718|
    30. |*23 | HASH JOIN | | 718|
    31. |*24 | HASH JOIN | | 872|
    32. | 25 | PARTITION LIST ALL | | 872|
    33. | 26 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_REVSD_SFCT | 872|
    34. | 27 | TABLE ACCESS FULL |OPT_CAL_MASTR_DIM |36826|
    35. | 28 | PARTITION LIST ALL | | 671K|
    36. | 29 | TABLE ACCESS FULL |OPT_PROD_BRAND_ASSOC_DIM | 671K|
    37. |*30 | HASH JOIN RIGHT OUTER | | 19M|
    38. | 31 | VIEW | | 1|
    39. | 32 | HASH GROUP BY | | 1|
    40. | 33 | NESTED LOOPS | | |
    41. | 34 | NESTED LOOPS | | 1|
    42. | 35 | TABLE ACCESS FULL |OPT_ACTVY_BUOM_GTIN_COST_TFADS| 1|
    43. |*36 | INDEX RANGE SCAN |OPT_PROD_BRAND_ASSOC_DIM_PK | 1|
    44. | 37 | TABLE ACCESS BY GLOBAL INDEX ROWID|OPT_PROD_BRAND_ASSOC_DIM | 1|
    45. |*38 | HASH JOIN | | 19M|
    46. |*39 | HASH JOIN | | 114K|
    47. | 40 | PARTITION LIST ALL | | 115K|
    48. | 41 | TABLE ACCESS FULL |OPT_ACTVY_DIM | 115K|
    49. |*42 | HASH JOIN | | 114K|
    50. | 43 | PARTITION LIST ALL | |94478|
    51. |*44 | TABLE ACCESS FULL |OPT_ACCT_DIM |94478|
    52. | 45 | PARTITION LIST ALL | | 114K|
    53. | 46 | TABLE ACCESS FULL |OPT_ACTVY_FCT | 114K|
    54. | 47 | PARTITION LIST ALL | | 19M|
    55. | 48 | TABLE ACCESS FULL |OPT_ACTVY_GTIN_BRAND_SFCT | 19M|
    56. -------------------------------------------------------------------------------------
    57. Predicate Information (identified by operation id):
    58. ---------------------------------------------------
    59. 5 - access("ACTVY_GTIN_BRAND"."PRMTN_SKID"="PRMTN"."PRMTN_SKID")
    60. 8 - access("ACTVY_GTIN_BRAND"."MTH_SKID"="CAL"."CAL_MASTR_SKID")
    61. 10 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="ESTMT_VAR_COST"."ACTVY_ID"(+)
    62. AND "ACTVY_GTIN_BRAND"."MTH_SKID"="ESTMT_VAR_COST"."DATE_SKID"(+) AND
    63. "ACTVY_GTIN_BRAND"."PROD_ID"="ESTMT_VAR_COST"."PROD_ID"(+))
    64. 13 - access("ESTMT"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
    65. "CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
    66. 14 - access("ESTMT"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
    67. 20 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="REVSD_VAR_COST"."ACTVY_ID"(+)
    68. AND "ACTVY_GTIN_BRAND"."MTH_SKID"="REVSD_VAR_COST"."DATE_SKID"(+) AND
    69. "ACTVY_GTIN_BRAND"."PROD_ID"="REVSD_VAR_COST"."PROD_ID"(+))
    70. 23 - access("REVSD"."PROD_ID"="BRAND_HIER"."PROD_ID" AND
    71. "CAL"."FISC_YR_SKID"="BRAND_HIER"."FY_DATE_SKID")
    72. 24 - access("REVSD"."DATE_SKID"="CAL"."CAL_MASTR_SKID")
    73. 30 - access("ACTVY_GTIN_BRAND"."ACTVY_ID"="AA"."ACTVY_ID"(+) AND
    74. "ACTVY_GTIN_BRAND"."MTH_SKID"="AA"."DATE_SKID"(+) AND
    75. "ACTVY_GTIN_BRAND"."PROD_ID"="AA"."PROD_ID"(+))
    76. 36 - access("AA"."BUOM_GTIN_PROD_SKID"="BRAND_HIER"."PROD_SKID" AND
    77. "BRAND_HIER"."FY_DATE_SKID"="AA"."FY_DATE_SKID")
    78. 38 - access("ACTVY"."ACTVY_SKID"="ACTVY_GTIN_BRAND"."ACTVY_SKID")
    79. 39 - access("ACTVY"."ACTVY_SKID"="ACTVY"."ACTVY_SKID")
    80. 42 - access("ACCT"."ACCT_SKID"="ACTVY"."ACCT_PRMTN_SKID")
    81. 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 究竟在等什么。

    1. select inst_id,sid,serial#,event,p1,p2,p3
    2. from gv$session where osuser='luobi';
    1. INST_ID SID SERIAL# EVENT P1 P2 P3
    2. --------- ---------- ---------- ---------------------- ------ ---------- ----------
    3. 2 4754 10050 direct path write temp 20025 857328 7
    4. SQL> /
    5. INST_ID SID SERIAL# EVENT P1 P2 P3
    6. -------- ---------- ---------- ----------------------- ------ ---------- ----------
    7. 2 4754 10050 direct path write temp 20025 406768 7
    8. SQL> /
    9. INST_ID SID SERIAL# EVENT P1 P2 P3
    10. -------- ---------- ---------- ----------------------- ------ ---------- ----------
    11. 2 4754 10050 direct path write temp 20007 2849264 7
    12. SQL> /
    13. INST_ID SID SERIAL# EVENT P1 P2 P3
    14. -------- ---------- ---------- ----------------------- ------ ---------- ----------
    15. 2 4754 10050 direct path write temp 20007 115341 7
    16. SQL> /
    17. INST_ID SID SERIAL# EVENT P1 P2 P3
    18. -------- ---------- ---------- ------------------------ ------ ---------- ---------
    19. 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 等待,使用以下脚本查看临时段数据类型。

    1. select a.inst_id, a.sid, a.serial#, a.sql_id, b.tablespace, b.blocks*
    2. (select value from v$parameter where name='db_block_size')/1024/1024 "Size(M)",b.segtype
    3. from gv$session a, gv$tempseg_usage b where a.inst_id=b.inst_id and a.saddr = b.session_addr
    4. and a.inst_id=2 and a.sid=4754;
    1. INST_ID SID SERIAL# SQL_ID TABLESPACE Size(M) SEGTYPE
    2. ------- ---------- ------------- ------------- --------------------- ---------
    3. 2 4754 10050 6qsuc8mafy20m TEMP 1 DATA
    4. 2 4754 10050 6qsuc8mafy20m TEMP 1 LOB_DATA
    5. 2 4754 10050 6qsuc8mafy20m TEMP 1 INDEX
    6. 2 4754 10050 6qsuc8mafy20m TEMP 1 LOB_DATA
    7. 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。

    1. alter session set workarea_size_policy = manual;
    1. Session altered.
    1. alter session set hash_area_size = 2147483648; ---2GB
    2. alter session set hash_area_size = 2147483648
    1. ERROR at line 1:
    2. ORA-02017: integer value required
    1. alter session set hash_area_size = 2147483647;
    1. Session altered.

    将 PGA 的 work area 设置为接近 2GB 之后,重新运行了 SQL 并且监控等待事件。

    1. select inst_id,sid,serial#,event,p1,p2,p3
    2. from gv$session where osuser='luobi';
    1. INST_ID SID SERIAL# EVENT P1 P2 P3
    2. -------- ---------- ---------- ------------------------------- ---------- ----------
    3. 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 分钟左右就能执行完毕。

    1. 6889440 rows selected.
    2. 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 开发补丁需要一定的时间,在此期间,使用本书给出的方法临时解决了项目中遇到的问题,确保项目不会因此延期。