▼▼▼
create table t as select * from dba_objects;
--表t的object_id列有索引,其實(shí)這里的last_ddl_time也是有索引的,而且可以走索引,為了簡(jiǎn)化,不進(jìn)行模擬
create index idx_t on t(object_id);
exec dbms_stats.gather_table_stats(ownname=>user,tabname=>t,no_invalidate=>false);
SQL> select count(*) from t;
COUNT(*)
----------
261898
原始語(yǔ)句:
select *
from(
select rowid,t.*
from t where t.object_id in
(
select object_id
from(
select object_id
from t
where mod(object_id,10)=0
and status=VALID
and last_ddl_time > trunc(sysdate-200)
order by timestamp,last_ddl_time
) where rownum<=100
) and t.status=VALID
and t.last_ddl_time > trunc(sysdate-200)
order by last_ddl_time
) where rownum<=100;
本來(lái)在11.2.0.3上平穩(wěn)運(yùn)行,執(zhí)行計(jì)劃走NESTED LOOPS,子查詢結(jié)果作為驅(qū)動(dòng),然后驅(qū)動(dòng)外層表,從而走object_id索引。但是升級(jí)后的執(zhí)行計(jì)劃卻是這樣的:
▼▼▼
--執(zhí)行12分鐘還沒(méi)有出現(xiàn)結(jié)果
SQL> set autotrace traceonly
SQL> select *
2 from(
3 select rowid,t.*
4 from t where t.object_id in
5 (
6 select object_id
7 from(
8 select object_id
9 from t
10 where mod(object_id,10)=0
11 and status=VALID
12 and last_ddl_time > trunc(sysdate-200)
order by last_ddl_time
13 14 ) where rownum<=100
15 ) and t.status=VALID
16 and t.last_ddl_time > trunc(sysdate-200)
17 order by last_ddl_time
18 ) where rownum<=100;
^Cselect *
*
ERROR at line 1:
ORA-01013: user requested cancel of current operation
Elapsed: 00:12:05.73
--問(wèn)題執(zhí)行計(jì)劃如下:
Execution Plan
----------------------------------------------------------
Plan hash value: 3028954274
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 219 | 2100K (2)| 07:00:08 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 1 | 219 | 2100K (2)| 07:00:08 |
|* 3 | SORT ORDER BY STOPKEY | | 1 | 100 | 2100K (2)| 07:00:08 |
|* 4 | FILTER | | | | | |
|* 5 | TABLE ACCESS FULL | T | 4936 | 482K| 855 (3)| 00:00:11 |
|* 6 | FILTER | | | | | |
|* 7 | COUNT STOPKEY | | | | | |
| 8 | VIEW | | 49 | 637 | 851 (2)| 00:00:11 |
|* 9 | SORT ORDER BY STOPKEY| | 49 | 1960 | 851 (2)| 00:00:11 |
|* 10 | TABLE ACCESS FULL | T | 49 | 1960 | 850 (2)| 00:00:11 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=100)
3 - filter(ROWNUM<=100)
4 - filter( EXISTS ()
5 - filter("T"."STATUS"=VALID AND
"T"."LAST_DDL_TIME">TRUNC(SYSDATE@!-200))
6 - filter("OBJECT_ID"=:B1)
7 - filter(ROWNUM<=100)
9 - filter(ROWNUM<=100)
10 - filter("STATUS"=VALID AND MOD("OBJECT_ID",10)=0 AND
"LAST_DDL_TIME">TRUNC(SYSDATE@!-200))
▼▼▼
*****************************
Cost-Based Subquery Unnesting
*****************************
SU: Unnesting query blocks in query block SEL$1 (#1) that are valid to unnest.
Subquery removal for query block SEL$3 (#3)
RSW: Not valid for subquery removal SEL$3 (#3)
Subquery unchanged.
Subquery Unnesting on query block SEL$2 (#2)SU: Performing unnesting that does not require costing.
SU: Considering subquery unnest on query block SEL$2 (#2).
SU: Checking validity of unnesting subquery SEL$3 (#3)
SU: SU bypassed: Subquery in a view with rowid reference.
--含有ROWID的subquery unnest失敗
SU: Validity checks failed.
10053顯示因?yàn)樽硬樵兊囊晥D含有rowid導(dǎo)致subquery unnest失敗,遇到這種情況要么去MOS上看看是不是BUG,要么就是改寫語(yǔ)句。通過(guò)查詢MOS,發(fā)現(xiàn)含有rowid的查詢有不少問(wèn)題:
SYMPTOMS
▼▼▼
A query referencing a rowid from a subquery with a join fails with the following error:
SQL> select rowid
from
(
select e.empno
from emp e left outer join (select deptno from dept) d
on (e.deptno = d.deptno)
);
ORA-01445: cannot select ROWID from, or sample, a join view without a key-preserved table
CAUSE
▼▼▼
The error is observed in query with ANSI joins because the way the query is written.
A rowid is only defined for individual rows in a table, so it is not legal to select a rowid from a subquery unless each row from that subquery can be guaranteed to be uniquely associated with exactly one row in one table. Therefore, the subquery may have only one item in its FROM list; that item must also have a rowid; and the subquery must not use DISTINCT, GROUP BY, or anything else that might gather multiple rows into one.
▼▼▼
Reference the rowid when it is valid as an explicit select list item:
SQL> select rid as "ROWID"
from
( select e.empno, e.rowid as rid
from emp e left outer join (select deptno from dept) d
on (e.deptno = d.deptno)
);
可以按照上面的思路將rowid改寫成別名,再在最外層將別名改回來(lái),以保證SQL語(yǔ)句的查詢列名一致,改寫如下:
▼▼▼
——改寫方案
select rd as "ROWID",object_id,object_name,last_ddl_time
from(
select rowid rd,t.*
from t where t.object_id in
(
select object_id
from(
select object_id
from t
where mod(object_id,10)=0
and status=VALID
and last_ddl_time > trunc(sysdate-200)
order by timestamp,last_ddl_time
) where rownum<=100
) and t.status=VALID
and t.last_ddl_time > trunc(sysdate-200)
order by last_ddl_time
) where rownum<=100;
改寫后的執(zhí)行計(jì)劃正確,如下所示:
▼▼▼
——改寫方案
selectExecution Plan
----------------------------------------------------------
Plan hash value: 16082276
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 4900 | 931 (2)| 00:00:12 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 49 | 4900 | 931 (2)| 00:00:12 |
|* 3 | SORT ORDER BY STOPKEY | | 49 | 5145 | 931 (2)| 00:00:12 |
|* 4 | TABLE ACCESS BY INDEX ROWID| T | 1 | 100 | 2 (0)| 00:00:01 |
| 5 | NESTED LOOPS | | 49 | 5145 | 930 (2)| 00:00:12 |
| 6 | VIEW | VW_NSO_1 | 49 | 245 | 851 (2)| 00:00:11 |
| 7 | HASH UNIQUE | | 49 | 245 | | |
|* 8 | COUNT STOPKEY | | | | | |
| 9 | VIEW | | 49 | 245 | 851 (2)| 00:00:11 |
|* 10 | SORT ORDER BY STOPKEY| | 49 | 1960 | 851 (2)| 00:00:11 |
|* 11 | TABLE ACCESS FULL | T | 49 | 1960 | 850 (2)| 00:00:11 |
|* 12 | INDEX RANGE SCAN | IDX_T | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=100)
3 - filter(ROWNUM<=100)
4 - filter("T"."STATUS"=VALID AND "T"."LAST_DDL_TIME">TRUNC(SYSDATE@!-200))
8 - filter(ROWNUM<=100)
10 - filter(ROWNUM<=100)
11 - filter("STATUS"=VALID AND MOD("OBJECT_ID",10)=0 AND
"LAST_DDL_TIME">TRUNC(SYSDATE@!-200))
12 - access("T"."OBJECT_ID"="OBJECT_ID")
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
4026 consistent gets
0 physical reads
0 redo size
7402 bytes sent via SQL*Net to client
586 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
100 rows processed
) where rownum<=100;
現(xiàn)在執(zhí)行計(jì)劃正確走NESTED LOOPS,并且被驅(qū)動(dòng)表走OBJECT_ID索引。再次思考一個(gè)問(wèn)題,從SQL語(yǔ)句的語(yǔ)義和業(yè)務(wù)上分析,這個(gè)語(yǔ)句其實(shí)就是按照條件查詢?nèi)缓笈判颍樵兂銮?00行的rowid和指定列,也就是沒(méi)有必要用子查詢或關(guān)聯(lián)查詢,可以將語(yǔ)句進(jìn)一步簡(jiǎn)化:
▼▼▼
--其實(shí)原來(lái)的業(yè)務(wù)就是這么簡(jiǎn)單
select rowid,object_id,object_name,last_ddl_time
from (
select object_id
from t
where mod(object_id,10)=0
and status=VALID
and last_ddl_time > trunc(sysdate-200)
order by timestamp,last_ddl_time
)
) where rownum<=100;
通過(guò)業(yè)務(wù)分析后改寫的SQL很簡(jiǎn)單,其實(shí)本質(zhì)就是查詢之后排序,然后找前100行。由于原始語(yǔ)句寫成子查詢并且又包含了rowid,導(dǎo)致觸發(fā)優(yōu)化器的限制,從而子查詢無(wú)法unnest,最終走了FILTER操作影響執(zhí)行效率。
更多精彩干貨分享
點(diǎn)擊下方名片關(guān)注
IT那活兒
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/129912.html
摘要:微信公眾號(hào)后端進(jìn)階,專注后端技術(shù)分享框架分布式中間件服務(wù)治理等等。 微信公眾號(hào)「后端進(jìn)階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。 老司機(jī)傾囊相授,帶你一路進(jìn)階,來(lái)不及解釋了快上車! 公司的某些業(yè)務(wù)用到了數(shù)據(jù)庫(kù)的悲觀鎖 for update,但有些同事沒(méi)有把 for update 放在 Spring 事務(wù)中執(zhí)行,在并發(fā)場(chǎng)景下發(fā)生了嚴(yán)重的線程阻...
閱讀 1353·2023-01-11 13:20
閱讀 1699·2023-01-11 13:20
閱讀 1211·2023-01-11 13:20
閱讀 1904·2023-01-11 13:20
閱讀 4161·2023-01-11 13:20
閱讀 2751·2023-01-11 13:20
閱讀 1397·2023-01-11 13:20
閱讀 3664·2023-01-11 13:20