今天盖尔找我优化一条SQL,SQL如下:
SELECT DISTINCT b.organ_id, c.company_name as organ_name, a.distri_date, a.distri_type, d.TYPE_NAME Capital_name, b.policy_code, b.apply_code send_code, i.ATTRIBUTE10 total_code, f.pay_mode, j.type_name as policy_type_name, e.Internal_Id AS product_code, round(a.distri_amount, 2) AS fee_amount, decode(a.posted, 'Y', to_char(i.transaction_date, 'yyyy-mm-dd'), to_char(a.distri_date, 'yyyy-mm-dd')) As finish_time, F.DR_SEG1, F.DR_SEG2, F.DR_SEG3, F.DR_SEG4, F.DR_SEG5, F.DR_SEG6, f.dr_seg7, f.dr_seg8, f.dr_seg9, f.dr_seg10, f.cr_seg1, f.cr_seg2, f.cr_seg3, f.cr_seg4, f.cr_seg5, f.cr_seg6, f.cr_seg7, f.cr_seg8, f.cr_seg9, f.cr_seg10, f.je_posting_id as cred_id FROM T_CAPITAL_DISTRIBUTE a, t_contract_master b, t_channel_type j, t_company_organ c, t_capital_distri_type d, t_product_life e, t_contract_product f, (select * from T_BIZ_ACCOUNTING_INFO where DATA_TABLE = '7') F, T_GL_BIZ_INTERFACE i, (select organ_id from t_company_organ start with organ_id = '101' connect by parent_id = prior organ_id) o WHERE a.policy_id = b.policy_id and a.item_id = f.item_id(+) AND b.organ_id = c.Organ_Id AND a.distri_type = d.distri_type AND a.product_id = e.product_id and b.policy_type = j.INDIVIDUAL_GROUP AND A.capital_id = F.FEE_ID(+) AND A.cred_id = i.posting_id(+) and a.organ_id = i.segment1(+) and nvl(a.posted, 'N') = 'Y' and a.cred_id = 493997 and i.transaction_date >= to_date('2011-11-01', 'yyyy-MM-dd') and i.transaction_date < to_date('2011-11-30', 'yyyy-MM-dd') + 1 and a.distri_type = i.reference3(+) and i.segment1 = o.organ_id(+);
盖尔说这个SQL逻辑读有2千万,跑300s,返回9000条数据,SQL 执行计划如下:
SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 356 | 27 (0)| | 1 | SORT UNIQUE | | 1 | 356 | 27 (0)| |* 2 | HASH JOIN OUTER | | 1 | 356 | 12 (9)| | 3 | NESTED LOOPS | | 1 | 350 | 10 (10)| | 4 | NESTED LOOPS | | 1 | 338 | 9 (12)| | 5 | NESTED LOOPS OUTER | | 1 | 302 | 8 (13)| | 6 | NESTED LOOPS | | 1 | 171 | 7 (15)| | 7 | NESTED LOOPS | | 1 | 125 | 6 (17)| | 8 | NESTED LOOPS | | 1 | 100 | 5 (20)| | 9 | NESTED LOOPS OUTER | | 1 | 86 | 4 (25)| | 10 | NESTED LOOPS | | 1 | 76 | 3 (34)| | 11 | TABLE ACCESS BY INDEX ROWID| T_GL_BIZ_INTERFACE | 1 | 24 | 2 (50)| |* 12 | INDEX SKIP SCAN | IDX10 | 1 | | 3 (0)| |* 13 | TABLE ACCESS BY INDEX ROWID| T_CAPITAL_DISTRIBUTE | 1 | 52 | 2 (50)| |* 14 | INDEX RANGE SCAN | IDX_CAPITAL_DISTR__CRED_ORGAN | 15 | | 2 (0)| | 15 | TABLE ACCESS BY INDEX ROWID | T_CONTRACT_PRODUCT | 1 | 10 | 2 (50)| |* 16 | INDEX UNIQUE SCAN | PK_T_CONTRACT_PRODUCT | 1 | | 1 (0)| | 17 | TABLE ACCESS BY INDEX ROWID | T_PRODUCT_LIFE | 1 | 14 | 2 (50)| |* 18 | INDEX UNIQUE SCAN | PK_T_PRODUCT_LIFE | 1 | | | | 19 | TABLE ACCESS BY INDEX ROWID | T_CAPITAL_DISTRI_TYPE | 1 | 25 | 2 (50)| |* 20 | INDEX UNIQUE SCAN | PK_T_CAPITAL_DISTRI_TYPE | 1 | | | | 21 | TABLE ACCESS BY INDEX ROWID | T_CONTRACT_MASTER | 1 | 46 | 2 (50)| |* 22 | INDEX UNIQUE SCAN | PK_T_CONTRACT_MASTER | 1 | | 1 (0)| | 23 | TABLE ACCESS BY INDEX ROWID | T_BIZ_ACCOUNTING_INFO | 1 | 131 | 2 (50)| |* 24 | INDEX RANGE SCAN | IDX_BIZ_ACCOUNTING_INFO__FEE_ | 1 | | 2 (0)| | 25 | TABLE ACCESS BY INDEX ROWID | T_COMPANY_ORGAN | 1 | 36 | 2 (50)| |* 26 | INDEX UNIQUE SCAN | PK_T_COMPANY_ORGAN | 1 | | | | 27 | TABLE ACCESS BY INDEX ROWID | T_CHANNEL_TYPE | 1 | 12 | 2 (50)| |* 28 | INDEX UNIQUE SCAN | PK_T_CHANNEL_TYPE | 1 | | | | 29 | VIEW | | 7 | 42 | | |* 30 | CONNECT BY WITH FILTERING | | | | | | 31 | NESTED LOOPS | | | | | |* 32 | INDEX UNIQUE SCAN | PK_T_COMPANY_ORGAN | 1 | 6 | | | 33 | TABLE ACCESS BY USER ROWID | T_COMPANY_ORGAN | | | | | 34 | NESTED LOOPS | | | | | | 35 | BUFFER SORT | | 7 | 70 | | | 36 | CONNECT BY PUMP | | | | | |* 37 | INDEX RANGE SCAN | T_COMPANY_ORGAN_IDX_002 | 7 | 70 | 1 (0)| ------------------------------------------------------------------------------------------------------------- 2 - access("I"."SEGMENT1"="O"."ORGAN_ID"(+)) 12 - access("I"."TRANSACTION_DATE">=TO_DATE('2011-11-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') AND "I"."POSTING_ID"=493997 AND "I"."TRANSACTION_DATE"<TO_DATE('2011-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) filter("I"."POSTING_ID"=493997) 13 - filter("A"."DISTRI_TYPE"="I"."REFERENCE3") 14 - access("A"."CRED_ID"=493997 AND "A"."ORGAN_ID"="I"."SEGMENT1") filter(NVL("A"."POSTED",'N')='Y') 16 - access("A"."ITEM_ID"="F"."ITEM_ID"(+)) 18 - access("A"."PRODUCT_ID"="E"."PRODUCT_ID") 20 - access("A"."DISTRI_TYPE"="D"."DISTRI_TYPE") 22 - access("A"."POLICY_ID"="B"."POLICY_ID") 24 - access("A"."CAPITAL_ID"="T_BIZ_ACCOUNTING_INFO"."FEE_ID"(+) AND "T_BIZ_ACCOUNTING_INFO"."DATA_TABLE"(+)=7) 26 - access("B"."ORGAN_ID"="C"."ORGAN_ID") 28 - access("B"."POLICY_TYPE"="J"."INDIVIDUAL_GROUP") 30 - filter("T_COMPANY_ORGAN"."ORGAN_ID"='101') 32 - access("T_COMPANY_ORGAN"."ORGAN_ID"='101') 37 - access("T_COMPANY_ORGAN"."PARENT_ID"=NULL) 65 rows selected.
从执行计划上去看,这个SQL基本上没有技术上可以进一步优化的地方了,你可能会说这里不应该走INDEX SKIP SCAN ,应该走INDEX RANGE SCAN
但是这个都是小问题,它不是决定性因素,SQL 优化从技术上 不可行之后,就应该立马分析业务,请仔细观察这个SQL
它有很多的外连接,外连接很特殊,因为外连接的驱动表的顺序是固定的 比如
a left join b 那么 a就只能做驱动表(不管是走nested loops outer 或者hash join outer) 你没办法更改驱动表的顺序,哪怕你用leading ,order hint都不行
正是因为这个SQL里面有很多外连接,SQL的访问顺序给固定死了,所以没办法从技术上调优SQL了
我让盖尔把外连接的(+) 去掉,跑一下SQL ,SQL只需要30秒就能跑完 ,执行计划如下
---------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ---------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 354 | 27 (0)| | 1 | SORT UNIQUE | | 1 | 354 | 27 (0)| | 2 | TABLE ACCESS BY INDEX ROWID | T_BIZ_ACCOUNTING_INFO | 1 | 131 | 2 (50)| | 3 | NESTED LOOPS | | 1 | 354 | 12 (9)| | 4 | NESTED LOOPS | | 1 | 223 | 11 (10)| | 5 | NESTED LOOPS | | 1 | 209 | 10 (10)| | 6 | NESTED LOOPS | | 1 | 199 | 9 (12)| | 7 | NESTED LOOPS | | 1 | 174 | 8 (13)| | 8 | NESTED LOOPS | | 1 | 138 | 7 (15)| | 9 | NESTED LOOPS | | 1 | 126 | 6 (17)| |* 10 | HASH JOIN | | 1 | 80 | 5 (20)| | 11 | TABLE ACCESS BY INDEX ROWID | T_CAPITAL_DISTRIBUTE | 1 | 50 | 2 (50)| | 12 | NESTED LOOPS | | 1 | 74 | 3 (34)| | 13 | TABLE ACCESS BY INDEX ROWID | T_GL_BIZ_INTERFACE | 1 | 24 | 2 (50)| |* 14 | INDEX SKIP SCAN | IDX10 | 1 | | 3 (0)| |* 15 | INDEX RANGE SCAN | IDX14 | 1 | | 2 (0)| | 16 | VIEW | | 7 | 42 | | |* 17 | FILTER | | | | | |* 18 | CONNECT BY WITH FILTERING | | | | | | 19 | NESTED LOOPS | | | | | |* 20 | INDEX UNIQUE SCAN | PK_T_COMPANY_ORGAN | 1 | 6 | | | 21 | TABLE ACCESS BY USER ROWID| T_COMPANY_ORGAN | | | | | 22 | NESTED LOOPS | | | | | | 23 | BUFFER SORT | | 7 | 70 | | | 24 | CONNECT BY PUMP | | | | | |* 25 | INDEX RANGE SCAN | T_COMPANY_ORGAN_IDX_002 | 7 | 70 | 1 (0)| | 26 | TABLE ACCESS BY INDEX ROWID | T_CONTRACT_MASTER | 1 | 46 | 2 (50)| |* 27 | INDEX UNIQUE SCAN | PK_T_CONTRACT_MASTER | 1 | | 1 (0)| | 28 | TABLE ACCESS BY INDEX ROWID | T_CHANNEL_TYPE | 1 | 12 | 2 (50)| |* 29 | INDEX UNIQUE SCAN | PK_T_CHANNEL_TYPE | 1 | | | | 30 | TABLE ACCESS BY INDEX ROWID | T_COMPANY_ORGAN | 1 | 36 | 2 (50)| |* 31 | INDEX UNIQUE SCAN | PK_T_COMPANY_ORGAN | 1 | | | | 32 | TABLE ACCESS BY INDEX ROWID | T_CAPITAL_DISTRI_TYPE | 1 | 25 | 2 (50)| |* 33 | INDEX UNIQUE SCAN | PK_T_CAPITAL_DISTRI_TYPE | 1 | | | | 34 | TABLE ACCESS BY INDEX ROWID | T_CONTRACT_PRODUCT | 1 | 10 | 2 (50)| |* 35 | INDEX UNIQUE SCAN | PK_T_CONTRACT_PRODUCT | 1 | | 1 (0)| | 36 | TABLE ACCESS BY INDEX ROWID | T_PRODUCT_LIFE | 1 | 14 | 2 (50)| |* 37 | INDEX UNIQUE SCAN | PK_T_PRODUCT_LIFE | 1 | | | |* 38 | INDEX RANGE SCAN | IDX_BIZ_ACCOUNTING_INFO__FEE_ | 1 | | 2 (0)| ---------------------------------------------------------------------------------------------------------------- 10 - access("I"."SEGMENT1"="O"."ORGAN_ID") 14 - access("I"."TRANSACTION_DATE">=TO_DATE('2011-11-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') AND "I"."POSTING_ID"=493997 AND "I"."TRANSACTION_DATE"<TO_DATE('2011-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) filter("I"."POSTING_ID"=493997) 15 - access("A"."CRED_ID"=493997 AND "A"."ORGAN_ID"="I"."SEGMENT1" AND "A"."DISTRI_TYPE"="I"."REFERENCE3") filter(NVL("A"."POSTED",'N')='Y' AND "A"."DISTRI_TYPE"="I"."REFERENCE3") 17 - filter(TO_DATE('2011-11-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')<TO_DATE('2011-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') AND TO_DATE('2011-12-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')>TO_DATE('2011-11-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) 18 - filter("T_COMPANY_ORGAN"."ORGAN_ID"='101') 20 - access("T_COMPANY_ORGAN"."ORGAN_ID"='101') 25 - access("T_COMPANY_ORGAN"."PARENT_ID"=NULL) 27 - access("A"."POLICY_ID"="B"."POLICY_ID") 29 - access("B"."POLICY_TYPE"="J"."INDIVIDUAL_GROUP") 31 - access("B"."ORGAN_ID"="C"."ORGAN_ID") 33 - access("A"."DISTRI_TYPE"="D"."DISTRI_TYPE") 35 - access("A"."ITEM_ID"="F"."ITEM_ID") 37 - access("A"."PRODUCT_ID"="E"."PRODUCT_ID") 38 - access("A"."CAPITAL_ID"="T_BIZ_ACCOUNTING_INFO"."FEE_ID" AND "T_BIZ_ACCOUNTING_INFO"."DATA_TABLE"=7)
所以系统设计的时候,应该尽量避免出现 left outer join, right outer join ,尤其是大表,大表更应该尽量避它作为外连接的驱动表