簡體   English   中英

避免使用 PL/SQL 表對 SELECT 進行全表掃描

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM