[英]Avoid full table scan with SELECT using PL/SQL table
測試數據
CREATE TABLE parent AS ( SELECT ROWNUM AS id, 'XXX' AS dummy FROM dual CONNECT BY ROWNUM <= 1000 );
CREATE UNIQUE INDEX idx_parent ON parent(id);
CREATE TABLE child AS ( SELECT CEIL(ROWNUM/5) AS id, 'XXX' AS dummy FROM dual CONNECT BY ROWNUM <= 5000 );
CREATE INDEX idx_child ON child(id);
EXEC dbms_stats.gather_table_stats(USER, 'parent');
EXEC dbms_stats.gather_table_stats(USER, 'child');
問題
即使考慮了CARDINALITY提示,以下查詢也會對子級進行全表掃描(包括 12.1 和 19.0)。
當然,真正的查詢需要來自孩子的一些額外數據。
SELECT child.id
FROM parent
JOIN
(
SELECT child.id
FROM child
GROUP BY child.id
) child ON ( child.id = parent.id )
WHERE parent.id IN ( SELECT /*+ CARDINALITY( tab 1 ) */ COLUMN_VALUE FROM TABLE (sys.odcinumberlist(1) ) tab );
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 35 (3)| 00:00:01 |
|* 1 | HASH JOIN RIGHT SEMI | | 1 | 19 | 35 (3)| 00:00:01 |
| 2 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 1 | 2 | 29 (0)| 00:00:01 |
| 3 | NESTED LOOPS | | 1000 | 17000 | 6 (17)| 00:00:01 |
| 4 | VIEW | | 1000 | 13000 | 6 (17)| 00:00:01 |
| 5 | HASH GROUP BY | | 1000 | 4000 | 6 (17)| 00:00:01 |
| 6 | TABLE ACCESS FULL | CHILD | 5000 | 20000 | 5 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | IDX_PARENT | 1 | 4 | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
如果我將WHERE子句替換為以下內容,則兩個索引都會按預期使用:
WHERE parent.id IN ( 1 );
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 35 | 2 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 5 | 35 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | IDX_PARENT | 1 | 4 | 1 (0)| 00:00:01 |
| 3 | VIEW | | 5 | 15 | 1 (0)| 00:00:01 |
| 4 | SORT GROUP BY | | 5 | 20 | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN| IDX_CHILD | 5 | 20 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------
當我刪除GROUP BY
時它也有效。
知道如何解決這個問題嗎?
問題是 ID 列可以包含 NULL 值。 如果將列定義為 NOT NULL,則使用索引。
該索引不包含 NULL 值。 但 GROUP BY 必須包含此數據。
當您在示例中將 parent.id 限制為 1 時,數據庫可以將索引用於具體值。
您可以使用MERGE
提示獲得所需的行為
SELECT child.id
FROM parent
JOIN
(
SELECT /*+ MERGE */ child.id ---<<<<< merge the subquery
FROM child
GROUP BY child.id
) child ON ( child.id = parent.id )
WHERE parent.id IN ( SELECT /*+ CARDINALITY( tab 1 ) */ COLUMN_VALUE FROM TABLE (sys.odcinumberlist(1) ) tab );
執行計划如下
--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 70 | 32 (7)| 00:00:01 |
| 1 | HASH GROUP BY | | 5 | 70 | 32 (7)| 00:00:01 |
| 2 | NESTED LOOPS | | 5 | 70 | 31 (4)| 00:00:01 |
| 3 | NESTED LOOPS | | 1 | 10 | 30 (4)| 00:00:01 |
| 4 | SORT UNIQUE | | 1 | 2 | 29 (0)| 00:00:01 |
| 5 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 1 | 2 | 29 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | IDX_PARENT | 1 | 8 | 0 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | IDX_CHILD | 5 | 20 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("PARENT"."ID"=VALUE(KOKBF$))
7 - access("CHILD"."ID"="PARENT"."ID")
我猜你的 child table 太小了,CBO 不認為這個計划是最好的; 但它也可能有其他原因。
附加說明
謂詞之間存在相當大的差異
parent.id IN ( subquery ) and
parent.id IN ( 1 )
在后一種情況下 Oracle simple 可以通過子查詢將謂詞( access("CHILD"."ID"=1)
)推送到group by
。 (見提示PUSH_PRED )。
但是無論如何,如果您1)知道子查詢僅返回一行,並且2)您對謂詞有所幫助,Oracle CBO 可以在沒有提示的情況下正確執行
這里根據 1) 和 2) 稍微改變了查詢 - 見評論
SELECT child.id
FROM parent
JOIN
(
SELECT child.id
FROM child
GROUP BY child.id
) child ON ( child.id = parent.id )
WHERE child.id /* 2) match with *child.id* to help Oracle to unnest */
= /* 1) use equal predicate as there is ony one row */
( SELECT /*+ CARDINALITY( tab 1 ) */ COLUMN_VALUE FROM TABLE (sys.odcinumberlist(1) ) tab );
計划
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 85 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 5 | 85 | 3 (0)| 00:00:01 |
| 2 | VIEW | | 5 | 65 | 3 (0)| 00:00:01 |
| 3 | HASH GROUP BY | | 5 | 25 | 3 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IDX_CHILD | 5 | 25 | 3 (0)| 00:00:01 |
| 5 | COLLECTION ITERATOR CONSTRUCTOR FETCH| | 1 | 2 | 29 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | IDX_PARENT | 1 | 4 | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("CHILD"."ID"= (SELECT /*+ OPT_ESTIMATE (TABLE "TAB"@"SEL$4" ROWS=1.000000 ) */
VALUE(KOKBF$) FROM TABLE() "KOKBF$0"))
6 - access("CHILD"."ID"="PARENT"."ID")
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.