上周五要下班的时候,盖尔发来一个SQL
select tpc.policy_id,
tcm.policy_code,
tpf.organ_id,
to_char(tpf.insert_time, 'YYYY-MM-DD') As insert_time,
tpc.change_id,
d.policy_code,
e.company_name,
f.real_name,
tpf.fee_type,
sum(tpf.pay_balance) as pay_balance,
c.actual_type,
tpc.notice_code,
d.policy_type,
g.mode_name as pay_mode
from t_policy_change tpc,
t_contract_master tcm,
t_policy_fee tpf,
t_fee_type c,
t_contract_master d,
t_company_customer e,
t_customer f,
t_pay_mode g
where tpc.change_id = tpf.change_id
and tpf.policy_id = d.policy_id
and tcm.policy_id = tpc.policy_id
and tpf.receiv_status = 1
and tpf.fee_status = 1
and tpf.payment_id is null
and tpf.fee_type = c.type_id
and tpf.pay_mode = g.mode_id
and d.company_id = e.company_id(+)
and d.applicant_id = f.customer_id(+)
and tpf.organ_id in
(select
organ_id
from t_company_organ
start with organ_id = '101'
connect by prior organ_id = parent_id)
group by tpc.policy_id,
tpc.change_id,
tpf.fee_type,
to_char(tpf.insert_time, 'YYYY-MM-DD'),
c.actual_type,
d.policy_code,
g.mode_name,
e.company_name,
f.real_name,
tpc.notice_code,
d.policy_type,
tpf.organ_id,
tcm.policy_code
order by change_id, fee_type
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)|
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 45962 | 11M| | 45650 (0)|
| 1 | SORT GROUP BY | | 45962 | 11M| 23M| 45650 (0)|
|* 2 | HASH JOIN | | 45962 | 11M| | 43908 (0)|
| 3 | INDEX FULL SCAN | T_FEE_TYPE_IDX_003 | 106 | 636 | | 1 (0)|
| 4 | NESTED LOOPS OUTER | | 45962 | 11M| | 43906 (0)|
|* 5 | HASH JOIN | | 45962 | 7271K| 6824K| 43905 (0)|
| 6 | NESTED LOOPS | | 45961 | 6283K| | 42312 (0)|
|* 7 | HASH JOIN SEMI | | 45961 | 5655K| 50M| 33120 (1)|
|* 8 | HASH JOIN OUTER | | 400K| 45M| 44M| 32315 (1)|
|* 9 | HASH JOIN | | 400K| 39M| 27M| 26943 (0)|
|* 10 | HASH JOIN | | 400K| 23M| | 16111 (0)|
| 11 | TABLE ACCESS FULL | T_PAY_MODE | 25 | 525 | | 2 (0)|
|* 12 | TABLE ACCESS FULL | T_POLICY_FEE | 400K| 15M| | 16107 (0)|
| 13 | TABLE ACCESS FULL | T_CONTRACT_MASTER | 1136K| 46M| | 9437 (0)|
| 14 | VIEW | index_join_007 | 2028K| 30M| | |
|* 15 | HASH JOIN | | 400K| 45M| 44M| 32315 (1)|
| 16 | INDEX FAST FULL SCAN | PK_T_CUSTOMER | 2028K| 30M| | 548 (0)|
| 17 | INDEX FAST FULL SCAN | IDX_CUSTOMER__BIR_REAL_GEN | 2028K| 30M| | 548 (0)|
| 18 | VIEW | VW_NSO_1 | 7 | 42 | | |
|* 19 | CONNECT BY WITH FILTERING | | | | | |
| 20 | NESTED LOOPS | | | | | |
|* 21 | INDEX UNIQUE SCAN | PK_T_COMPANY_ORGAN | 1 | 6 | | |
| 22 | TABLE ACCESS BY USER ROWID| T_COMPANY_ORGAN | | | | |
| 23 | NESTED LOOPS | | | | | |
| 24 | BUFFER SORT | | 7 | 70 | | |
| 25 | CONNECT BY PUMP | | | | | |
|* 26 | INDEX RANGE SCAN | T_COMPANY_ORGAN_IDX_002 | 7 | 70 | | 1 (0)|
| 27 | TABLE ACCESS BY INDEX ROWID | T_POLICY_CHANGE | 1 | 14 | | 2 (50)|
|* 28 | INDEX UNIQUE SCAN | PK_T_POLICY_CHANGE | 1 | | | 1 (0)|
| 29 | INDEX FAST FULL SCAN | IDX1_ACCEPT_DATE | 1136K| 23M| | 899 (0)|
| 30 | TABLE ACCESS BY INDEX ROWID | T_COMPANY_CUSTOMER | 1 | 90 | | 2 (50)|
|* 31 | INDEX UNIQUE SCAN | PK_T_COMPANY_CUSTOMER | 1 | | | |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("TPF"."FEE_TYPE"="C"."TYPE_ID")
5 - access("TCM"."POLICY_ID"="TPC"."POLICY_ID")
7 - access("TPF"."ORGAN_ID"="VW_NSO_1"."$nso_col_1")
8 - access("D"."APPLICANT_ID"="F"."CUSTOMER_ID"(+))
9 - access("TPF"."POLICY_ID"="D"."POLICY_ID")
10 - access("TPF"."PAY_MODE"="G"."MODE_ID")
12 - filter("TPF"."CHANGE_ID" IS NOT NULL AND TO_NUMBER("TPF"."RECEIV_STATUS")=1 AND "TPF"."FEE_STATUS"=1 AND
"TPF"."PAYMENT_ID" IS NULL)
15 - access("indexjoin_alias_012".ROWID="indexjoin_alias_011".ROWID)
19 - filter("T_COMPANY_ORGAN"."ORGAN_ID"='101')
21 - access("T_COMPANY_ORGAN"."ORGAN_ID"='101')
26 - access("T_COMPANY_ORGAN"."PARENT_ID"=NULL)
28 - access("TPC"."CHANGE_ID"="TPF"."CHANGE_ID")
31 - access("D"."COMPANY_ID"="E"."COMPANY_ID"(+))
55 rows selected
Statistics
----------------------------------------------------------
21 recursive calls
0 db block gets
125082 consistent gets
21149 physical reads
0 redo size
2448 bytes sent via SQL*Net to client
656 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
11 rows processed
这个SQL要21秒才能跑完,逻辑读12W左右,问我能不能优化。优化这个SQL我只花了1分钟左右的时间,因为太简单了
你们看这个SQL是典型的JOIN,对付这种SQL肯定要让表走索引,但是从执行计划上看有个1千万行的表T_CONTRACT_MASTER走的是全表扫描,
T_POLICY_FEE 这个400W行的表也是走全表扫描,那么它不慢才怪呢,然后SQL的过滤条件有个 in 子查询
(select
organ_id
from t_company_organ
start with organ_id = '101'
connect by prior organ_id = parent_id)
从执行计划上看,CBO对这儿子查询进行了unnest,因为通常情况下CBO认为子查询被unnest之后性能好于filter
于是我让盖尔查询 子查询返回多少行
select organ_id
from t_company_organ
start with organ_id = '101'
connect by prior organ_id = parent_id ---盖尔说它返回1行
对于子查询,如果它返回数据很少(这里返回1行),那么可以让它走filter, 而且filter基本上是在SQL最后去阶段执行,这样t_policy_fee就可以走索引了
所以我给这个子查询加了个HINT,禁止子查询扩展
select tpc.policy_id,
tcm.policy_code,
tpf.organ_id,
to_char(tpf.insert_time, 'YYYY-MM-DD') As insert_time,
tpc.change_id,
d.policy_code,
e.company_name,
f.real_name,
tpf.fee_type,
sum(tpf.pay_balance) as pay_balance,
c.actual_type,
tpc.notice_code,
d.policy_type,
g.mode_name as pay_mode
from t_policy_change tpc,
t_contract_master tcm,
t_policy_fee tpf,
t_fee_type c,
t_contract_master d,
t_company_customer e,
t_customer f,
t_pay_mode g
where tpc.change_id = tpf.change_id
and tpf.policy_id = d.policy_id
and tcm.policy_id = tpc.policy_id
and tpf.receiv_status = '1' ---这里原来没引号,是开发那SB搞忘了写'',我让盖尔添加上了,不添加上就没法用索引
and tpf.fee_status = 1
and tpf.payment_id is null
and tpf.fee_type = c.type_id
and tpf.pay_mode = g.mode_id
and d.company_id = e.company_id(+)
and d.applicant_id = f.customer_id(+)
and tpf.organ_id in
(select /*+ no_unnest */ --此处的HINT后加的
organ_id
from t_company_organ
start with organ_id = '101'
connect by prior organ_id = parent_id)
group by tpc.policy_id,
tpc.change_id,
tpf.fee_type,
to_char(tpf.insert_time, 'YYYY-MM-DD'),
c.actual_type,
d.policy_code,
g.mode_name,
e.company_name,
f.real_name,
tpc.notice_code,
d.policy_type,
tpf.organ_id,
tcm.policy_code
order by change_id, fee_type
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)|
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20026 | 4928K| | 68615 (30)|
| 1 | SORT GROUP BY | | 20026 | 4928K| 10M| 28563 (0)|
|* 2 | FILTER | | | | | |
| 3 | NESTED LOOPS | | 20026 | 4928K| | 27812 (0)|
| 4 | NESTED LOOPS | | 20026 | 4498K| | 23807 (0)|
| 5 | NESTED LOOPS OUTER | | 20026 | 4224K| | 19802 (0)|
| 6 | NESTED LOOPS OUTER | | 20026 | 3911K| | 15797 (0)|
| 7 | NESTED LOOPS | | 20026 | 2151K| | 15796 (0)|
|* 8 | HASH JOIN | | 20026 | 1310K| | 11791 (0)|
| 9 | INDEX FULL SCAN | T_FEE_TYPE_IDX_003 | 106 | 636 | | 1 (0)|
|* 10 | HASH JOIN | | 20026 | 1192K| | 11789 (0)|
| 11 | TABLE ACCESS FULL | T_PAY_MODE | 25 | 525 | | 2 (0)|
|* 12 | TABLE ACCESS BY INDEX ROWID| T_POLICY_FEE | 20026 | 782K| | 11786 (0)|
|* 13 | INDEX RANGE SCAN | IDX_POLICY_FEE__RECEIV_STATUS | 1243K| | | 10188 (0)|
| 14 | TABLE ACCESS BY INDEX ROWID | T_CONTRACT_MASTER | 1 | 43 | | 2 (50)|
|* 15 | INDEX UNIQUE SCAN | PK_T_CONTRACT_MASTER | 1 | | | 1 (0)|
| 16 | TABLE ACCESS BY INDEX ROWID | T_COMPANY_CUSTOMER | 1 | 90 | | 2 (50)|
|* 17 | INDEX UNIQUE SCAN | PK_T_COMPANY_CUSTOMER | 1 | | | |
| 18 | TABLE ACCESS BY INDEX ROWID | T_CUSTOMER | 1 | 16 | | 2 (50)|
|* 19 | INDEX UNIQUE SCAN | PK_T_CUSTOMER | 1 | | | 1 (0)|
| 20 | TABLE ACCESS BY INDEX ROWID | T_POLICY_CHANGE | 1 | 14 | | 2 (50)|
|* 21 | INDEX UNIQUE SCAN | PK_T_POLICY_CHANGE | 1 | | | 1 (0)|
| 22 | TABLE ACCESS BY INDEX ROWID | T_CONTRACT_MASTER | 1 | 22 | | 2 (50)|
|* 23 | INDEX UNIQUE SCAN | PK_T_CONTRACT_MASTER | 1 | | | 1 (0)|
|* 24 | FILTER | | | | | |
|* 25 | CONNECT BY WITH FILTERING | | | | | |
| 26 | NESTED LOOPS | | | | | |
|* 27 | INDEX UNIQUE SCAN | PK_T_COMPANY_ORGAN | 1 | 6 | | |
| 28 | TABLE ACCESS BY USER ROWID | T_COMPANY_ORGAN | | | | |
| 29 | NESTED LOOPS | | | | | |
| 30 | BUFFER SORT | | 7 | 70 | | |
| 31 | CONNECT BY PUMP | | | | | |
|* 32 | INDEX RANGE SCAN | T_COMPANY_ORGAN_IDX_002 | 7 | 70 | | 1 (0)|
--------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "T_COMPANY_ORGAN" "T_COMPANY_ORGAN" WHERE
"T_COMPANY_ORGAN"."PARENT_ID"=NULL AND ("T_COMPANY_ORGAN"."ORGAN_ID"=:B1)))
8 - access("SYS_ALIAS_1"."FEE_TYPE"="C"."TYPE_ID")
10 - access("SYS_ALIAS_1"."PAY_MODE"="G"."MODE_ID")
12 - filter("SYS_ALIAS_1"."CHANGE_ID" IS NOT NULL AND "SYS_ALIAS_1"."FEE_STATUS"=1 AND "SYS_ALIAS_1"."PAYMENT_ID"
IS NULL)
13 - access("SYS_ALIAS_1"."RECEIV_STATUS"='1')
15 - access("SYS_ALIAS_1"."POLICY_ID"="D"."POLICY_ID")
17 - access("D"."COMPANY_ID"="E"."COMPANY_ID"(+))
19 - access("D"."APPLICANT_ID"="F"."CUSTOMER_ID"(+))
21 - access("TPC"."CHANGE_ID"="SYS_ALIAS_1"."CHANGE_ID")
23 - access("TCM"."POLICY_ID"="TPC"."POLICY_ID")
24 - filter("T_COMPANY_ORGAN"."ORGAN_ID"=:B1)
25 - filter("T_COMPANY_ORGAN"."ORGAN_ID"='101')
27 - access("T_COMPANY_ORGAN"."ORGAN_ID"='101')
32 - access("T_COMPANY_ORGAN"."PARENT_ID"=NULL)
58 rows selected.
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
2817 consistent gets
0 physical reads
0 redo size
2268 bytes sent via SQL*Net to client
656 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
40 sorts (memory)
0 sorts (disk)
9 rows processed
最终这个SQL能在1秒以内跑完,逻辑读下降到2817 ,到此我就没继续优化了,这个时候停止优化吧,别的了强迫优化症
这个优化案例很简单,我都不好意思贴在博客上,通过这个文章你要学到的就是,如果子查询返回数据很少,那么不妨让它走filter