背景是在一台11gR2的机器上,开发反映一个批处理比以前慢了3倍。经过仔细查看该SQL的执行计划,发现由于SQL中使用了or,导致CBO走出了一个非常糟糕的CONCATENATION路径。
no_expand提示的说明是

The NO_EXPAND hint prevents the cost-based optimizer from considering OR-expansion for queries having OR conditions or IN-lists in the WHERE clause. Usually,

the optimizer considers using OR expansion and uses this method if it decides that the cost is lower than not using it.

use_concat提示的说明是

The USE_CONCAT hint forces combined OR conditions in the WHERE clause of a query to be transformed into a compound query using the UNION ALL set operator.

Generally, this transformation occurs only if the cost of the query using the concatenations is cheaper than the cost without them.

为了重现这个问题,必须使用/*+ use_concat */来模拟。

explain plan for SELECT  /*+ use_concat */
20130620,
B.mgr_code ,
B.mgr_name ,
B.cur_name ,
'3.4.2' ,
nvl(sum(ADJUST_AMT_AF),0) as acct_bal ,
nvl(sum(D_CMP_BAL),0) ,
nvl(sum(M_CMP_BAL),0) ,
nvl(sum(Y_CMP_BAL),0) ,
nvl(sum(Y_avg_af),0) as Y_avg_bal,
B.cur_code,
B.unit1_code,
B.unit2_code,
B.unit3_code,
B.dept1_code,
A.idx_name
FROM S_PM_IDX_CODE A,T_PM_ACCT_DTL_AF B
where B.ACCT_FLAG in('OPEN','LOAN')
AND A.ROWID='AABK8vAAyAAGg/2AAs'
AND SUBSTR(B.FLAG,1,1) IN ('3')
AND SUBSTR(B.FLAG,2,1) IN ('2')
AND SUBSTR(B.FLAG,3,1) IN ('2')
AND 1=1
AND 1=1
AND 1=1
AND 1=1
AND 1=1
AND 1=1
AND 1=1
and ((A.term_flag is null)
or (A.term_flag is not null and A.begin_term<B.term
and A.end_term >= B.term and B.term_flag = A.term_flag))
AND B.data_date = 20130620
group by B.mgr_code, B.mgr_name,
B.cur_code, B.cur_name,
B.unit1_code,B.unit2_code,
B.unit3_code,
B.dept1_code,
A.IDX_NAME; Plan hash value: 542663423 ------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 240 | 47236 (1)| 00:09:27 | | |
| 1 | HASH GROUP BY | | 1 | 240 | | | | |
| 2 | CONCATENATION | | | | | | | |
| 3 | NESTED LOOPS | | 1 | 240 | 23609 (1)| 00:04:44 | | |
|* 4 | TABLE ACCESS BY USER ROWID| S_PM_IDX_CODE | 1 | 97 | 1 (0)| 00:00:01 | | |
| 5 | PARTITION LIST SINGLE | | 1 | 143 | 23608 (1)| 00:04:44 | KEY | KEY |
|* 6 | TABLE ACCESS FULL | T_PM_ACCT_DTL_AF | 1 | 143 | 23608 (1)| 00:04:44 | 549 | 549 |
| 7 | NESTED LOOPS | | 1 | 240 | 23625 (1)| 00:04:44 | | |
|* 8 | TABLE ACCESS BY USER ROWID| S_PM_IDX_CODE | 1 | 97 | 1 (0)| 00:00:01 | | |
| 9 | PARTITION LIST SINGLE | | 1 | 143 | 23624 (1)| 00:04:44 | KEY | KEY |
|* 10 | TABLE ACCESS FULL | T_PM_ACCT_DTL_AF | 1 | 143 | 23624 (1)| 00:04:44 | 549 | 549 |
------------------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id):
--------------------------------------------------- 4 - filter("A"."TERM_FLAG" IS NOT NULL)
6 - filter("A"."BEGIN_TERM"<"B"."TERM" AND "A"."END_TERM">="B"."TERM" AND
"B"."TERM_FLAG"="A"."TERM_FLAG" AND ("B"."ACCT_FLAG"='LOAN' OR "B"."ACCT_FLAG"='OPEN') AND
SUBSTR("B"."FLAG",2,1)='2' AND SUBSTR("B"."FLAG",3,1)='2' AND SUBSTR("B"."FLAG",1,1)='3' AND
"B"."DATA_DATE"=20130620)
8 - filter("A"."TERM_FLAG" IS NULL)
10 - filter(("B"."ACCT_FLAG"='LOAN' OR "B"."ACCT_FLAG"='OPEN') AND SUBSTR("B"."FLAG",2,1)='2' AND
SUBSTR("B"."FLAG",3,1)='2' AND SUBSTR("B"."FLAG",1,1)='3' AND "B"."DATA_DATE"=20130620 AND
(LNNVL("B"."TERM_FLAG"="A"."TERM_FLAG") OR LNNVL("A"."BEGIN_TERM"<"B"."TERM") OR
LNNVL("A"."END_TERM">="B"."TERM") OR LNNVL("A"."TERM_FLAG" IS NOT NULL))) Note
-----
- dynamic sampling used for this statement (level=2) explain plan for
SELECT
20130620,
B.mgr_code ,
B.mgr_name ,
B.cur_name ,
'3.4.2' ,
nvl(sum(ADJUST_AMT_AF),0) as acct_bal ,
nvl(sum(D_CMP_BAL),0) ,
nvl(sum(M_CMP_BAL),0) ,
nvl(sum(Y_CMP_BAL),0) ,
nvl(sum(Y_avg_af),0) as Y_avg_bal,
B.cur_code,
B.unit1_code,
B.unit2_code,
B.unit3_code,
B.dept1_code,
A.idx_name
FROM S_PM_IDX_CODE A,T_PM_ACCT_DTL_AF B
where B.ACCT_FLAG in('OPEN','LOAN')
AND A.ROWID='AABK8vAAyAAGg/2AAs'
AND SUBSTR(B.FLAG,1,1) IN ('3')
AND SUBSTR(B.FLAG,2,1) IN ('2')
AND SUBSTR(B.FLAG,3,1) IN ('2')
AND 1=1
AND 1=1
AND 1=1
AND 1=1
AND 1=1
AND 1=1
AND 1=1
and ((A.term_flag is null)
or (A.term_flag is not null and A.begin_term<B.term
and A.end_term >= B.term and B.term_flag = A.term_flag))
AND B.data_date = 20130620
group by B.mgr_code, B.mgr_name,
B.cur_code, B.cur_name,
B.unit1_code,B.unit2_code,
B.unit3_code,
B.dept1_code,
A.IDX_NAME;
select * from table(dbms_xplan.display());
Plan hash value: 2396922436 ------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 240 | 8058 (1)| 00:01:37 | | |
| 1 | HASH GROUP BY | | 1 | 240 | 8058 (1)| 00:01:37 | | |
| 2 | NESTED LOOPS | | 1 | 240 | 8057 (1)| 00:01:37 | | |
| 3 | TABLE ACCESS BY USER ROWID | S_PM_IDX_CODE | 1 | 97 | 1 (0)| 00:00:01 | | |
| 4 | PARTITION LIST SINGLE | | 1 | 143 | 8056 (1)| 00:01:37 | KEY | KEY |
|* 5 | TABLE ACCESS BY LOCAL INDEX ROWID| T_PM_ACCT_DTL_AF | 1 | 143 | 8056 (1)| 00:01:37 | 549 | 549 |
|* 6 | INDEX RANGE SCAN | T_PM_ACCT_DTL_AF_IDX1 | 35022 | | 136 (1)| 00:00:02 | 549 | 549 |
------------------------------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id):
--------------------------------------------------- 5 - filter(("B"."ACCT_FLAG"='LOAN' OR "B"."ACCT_FLAG"='OPEN') AND SUBSTR("B"."FLAG",2,1)='2' AND
SUBSTR("B"."FLAG",3,1)='2' AND ("A"."TERM_FLAG" IS NULL OR "B"."TERM_FLAG"="A"."TERM_FLAG" AND
"A"."BEGIN_TERM"<"B"."TERM" AND "A"."END_TERM">="B"."TERM" AND "A"."TERM_FLAG" IS NOT NULL))
6 - access(SUBSTR("FLAG",1,1)='3') Note
-----
- dynamic sampling used for this statement (level=2)
05-11 13:41