▼▼▼
create table t as select * from dba_objects;
--表t的object_id列有索引,其實這里的last_ddl_time也是有索引的,而且可以走索引,為了簡化,不進行模擬
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
原始語句:
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;
本來在11.2.0.3上平穩運行,執行計劃走NESTED LOOPS,子查詢結果作為驅動,然后驅動外層表,從而走object_id索引。但是升級后的執行計劃卻是這樣的:
▼▼▼
--執行12分鐘還沒有出現結果
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
--問題執行計劃如下:
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顯示因為子查詢的視圖含有rowid導致subquery unnest失敗,遇到這種情況要么去MOS上看看是不是BUG,要么就是改寫語句。通過查詢MOS,發現含有rowid的查詢有不少問題:
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改寫成別名,再在最外層將別名改回來,以保證SQL語句的查詢列名一致,改寫如下:
▼▼▼
——改寫方案
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;
改寫后的執行計劃正確,如下所示:
▼▼▼
——改寫方案
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;
現在執行計劃正確走NESTED LOOPS,并且被驅動表走OBJECT_ID索引。再次思考一個問題,從SQL語句的語義和業務上分析,這個語句其實就是按照條件查詢然后排序,查詢出前100行的rowid和指定列,也就是沒有必要用子查詢或關聯查詢,可以將語句進一步簡化:
▼▼▼
--其實原來的業務就是這么簡單
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;
通過業務分析后改寫的SQL很簡單,其實本質就是查詢之后排序,然后找前100行。由于原始語句寫成子查詢并且又包含了rowid,導致觸發優化器的限制,從而子查詢無法unnest,最終走了FILTER操作影響執行效率。
更多精彩干貨分享
點擊下方名片關注
IT那活兒
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/129912.html
摘要:微信公眾號后端進階,專注后端技術分享框架分布式中間件服務治理等等。 微信公眾號「后端進階」,專注后端技術分享:Java、Golang、WEB框架、分布式中間件、服務治理等等。 老司機傾囊相授,帶你一路進階,來不及解釋了快上車! 公司的某些業務用到了數據庫的悲觀鎖 for update,但有些同事沒有把 for update 放在 Spring 事務中執行,在并發場景下發生了嚴重的線程阻...
閱讀 1346·2023-01-11 13:20
閱讀 1684·2023-01-11 13:20
閱讀 1132·2023-01-11 13:20
閱讀 1858·2023-01-11 13:20
閱讀 4100·2023-01-11 13:20
閱讀 2704·2023-01-11 13:20
閱讀 1385·2023-01-11 13:20
閱讀 3597·2023-01-11 13:20