Избегайте полного сканирования таблицы с использованием подзапроса или аналитической функции - PullRequest
0 голосов
/ 24 января 2019

Я могу воспроизвести следующее поведение как с Oracle 11 (см. SQL Fiddle ), так и с Oracle 12.

CREATE TYPE my_tab IS TABLE OF NUMBER(3);

CREATE TABLE test AS SELECT ROWNUM AS id FROM dual CONNECT BY ROWNUM <= 1000;
CREATE UNIQUE INDEX idx_test ON test( id );

CREATE VIEW my_view AS
  SELECT id, COUNT(1) OVER ( PARTITION BY id ) AS cnt
  FROM test;

В следующем случае использует индекс какожидается:

SELECT * FROM my_view
WHERE id IN ( 1, 2 );

---------------------------------------------------------------------------------                                                                                                                       
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)| Time     |                                                                                                                       
---------------------------------------------------------------------------------                                                                                                                       
|   0 | SELECT STATEMENT     |          |     2 |    52 |     2   (0)| 00:00:01 |                                                                                                                       
|   1 |  VIEW                | MY_VIEW  |     2 |    52 |     2   (0)| 00:00:01 |                                                                                                                       
|   2 |   WINDOW BUFFER      |          |     2 |     8 |     2   (0)| 00:00:01 |                                                                                                                       
|   3 |    INLIST ITERATOR   |          |       |       |            |          |                                                                                                                       
|*  4 |     INDEX UNIQUE SCAN| IDX_TEST |     2 |     8 |     2   (0)| 00:00:01 |                                                                                                                       
---------------------------------------------------------------------------------                                                                                                                       

В следующем случае индекс не используется , даже если указана подсказка количества элементов:

SELECT * FROM my_view
WHERE id IN ( SELECT /*+ CARDINALITY( tab 2 ) */ COLUMN_VALUE
              FROM TABLE( NEW my_tab( 1, 2 ) ) tab );

--------------------------------------------------------------------------------------------------                                                                                                      
| Id  | Operation                              | Name    | Rows  | Bytes | Cost (%CPU)| Time     |                                                                                                      
--------------------------------------------------------------------------------------------------                                                                                                      
|   0 | SELECT STATEMENT                       |         |     1 |    28 |    33   (4)| 00:00:01 |                                                                                                      
|*  1 |  HASH JOIN RIGHT SEMI                  |         |     1 |    28 |    33   (4)| 00:00:01 |                                                                                                      
|   2 |   COLLECTION ITERATOR CONSTRUCTOR FETCH|         |     2 |     4 |    29   (0)| 00:00:01 |                                                                                                      
|   3 |   VIEW                                 | MY_VIEW |  1000 | 26000 |     4  (25)| 00:00:01 |                                                                                                      
|   4 |    WINDOW SORT                         |         |  1000 |  4000 |     4  (25)| 00:00:01 |                                                                                                      
|   5 |     TABLE ACCESS FULL                  | TEST    |  1000 |  4000 |     3   (0)| 00:00:01 |                                                                                                      
--------------------------------------------------------------------------------------------------                                                                                                      

Редактировать:

При использовании встроенного просмотра и JOIN вместо IN используется аналогичный план:

SELECT /*+ CARDINALITY( tab, 2 ) */ *
FROM ( SELECT id, COUNT(1) OVER ( PARTITION BY id ) AS cnt FROM test ) t
JOIN TABLE( NEW my_tab( 1, 2 ) ) tab ON ( tab.COLUMN_VALUE = t.id );

-----------------------------------------------------------------------------------------------                                                                                                         
| Id  | Operation                              | Name | Rows  | Bytes | Cost (%CPU)| Time     |                                                                                                         
-----------------------------------------------------------------------------------------------                                                                                                         
|   0 | SELECT STATEMENT                       |      |     2 |    56 |    33   (4)| 00:00:01 |                                                                                                         
|*  1 |  HASH JOIN                             |      |     2 |    56 |    33   (4)| 00:00:01 |                                                                                                         
|   2 |   COLLECTION ITERATOR CONSTRUCTOR FETCH|      |     2 |     4 |    29   (0)| 00:00:01 |                                                                                                         
|   3 |   VIEW                                 |      |  1000 | 26000 |     4  (25)| 00:00:01 |                                                                                                         
|   4 |    WINDOW SORT                         |      |  1000 |  4000 |     4  (25)| 00:00:01 |                                                                                                         
|   5 |     TABLE ACCESS FULL                  | TEST |  1000 |  4000 |     3   (0)| 00:00:01 |                                                                                                         
-----------------------------------------------------------------------------------------------                                                                                                         

Замена аналитической функции на LEFT JOIN на GROUP BYтоже не помогает:

SELECT *
FROM ( SELECT t.id, s.cnt
       FROM test t
       LEFT JOIN ( SELECT id, COUNT(*) AS cnt
                   FROM test
                   GROUP BY id
                 ) s ON ( s.id = t.id )
     )
WHERE id IN ( SELECT /*+ CARDINALITY( tab 2 ) */ COLUMN_VALUE
              FROM TABLE( NEW my_tab( 1, 2 ) ) tab );

-----------------------------------------------------------------------------------------------------                                                                                                   
| Id  | Operation                                | Name     | Rows  | Bytes | Cost (%CPU)| Time     |                                                                                                   
-----------------------------------------------------------------------------------------------------                                                                                                   
|   0 | SELECT STATEMENT                         |          |     2 |    64 |    34   (6)| 00:00:01 |                                                                                                   
|*  1 |  HASH JOIN OUTER                         |          |     2 |    64 |    34   (6)| 00:00:01 |                                                                                                   
|   2 |   NESTED LOOPS                           |          |     2 |    12 |    30   (4)| 00:00:01 |                                                                                                   
|   3 |    SORT UNIQUE                           |          |     2 |     4 |    29   (0)| 00:00:01 |                                                                                                   
|   4 |     COLLECTION ITERATOR CONSTRUCTOR FETCH|          |     2 |     4 |    29   (0)| 00:00:01 |                                                                                                   
|*  5 |    INDEX UNIQUE SCAN                     | IDX_TEST |     1 |     4 |     0   (0)| 00:00:01 |                                                                                                   
|   6 |   VIEW                                   |          |  1000 | 26000 |     4  (25)| 00:00:01 |                                                                                                   
|   7 |    HASH GROUP BY                         |          |  1000 |  4000 |     4  (25)| 00:00:01 |                                                                                                   
|   8 |     TABLE ACCESS FULL                    | TEST     |  1000 |  4000 |     3   (0)| 00:00:01 |                                                                                                   
-----------------------------------------------------------------------------------------------------                                                                                                   

Замена коллекции PL / SQL подвыбором также, похоже, не помогает.Рассматривается подсказка CARDINALITY (план содержит 2 строки), но индекс по-прежнему игнорируется.

SELECT *
FROM ( SELECT id, cnt FROM my_view )
WHERE id IN ( SELECT /*+ CARDINALITY( tab 2 ) */ id FROM test tab );

---------------------------------------------------------------------------------                                                                                                                       
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)| Time     |                                                                                                                       
---------------------------------------------------------------------------------                                                                                                                       
|   0 | SELECT STATEMENT     |          |     2 |    60 |     4  (25)| 00:00:01 |                                                                                                                       
|   1 |  NESTED LOOPS        |          |     2 |    60 |     4  (25)| 00:00:01 |                                                                                                                       
|   2 |   VIEW               | MY_VIEW  |  1000 | 26000 |     4  (25)| 00:00:01 |                                                                                                                       
|   3 |    WINDOW SORT       |          |  1000 |  4000 |     4  (25)| 00:00:01 |                                                                                                                       
|   4 |     TABLE ACCESS FULL| TEST     |  1000 |  4000 |     3   (0)| 00:00:01 |                                                                                                                       
|*  5 |   INDEX UNIQUE SCAN  | IDX_TEST |     1 |     4 |     0   (0)| 00:00:01 |                                                                                                                       
---------------------------------------------------------------------------------                                                                                                                       

При добавлении WHERE tab.id <= 2 в подзапрос in-list используется индекс, поэтому оптимизатор, по-видимому, "не воспринимает подсказку CARDINALITY достаточно серьезным" при выборе из представления с аналитическими функциями (или другим подвыбором)) и фильтрация по списку значений.


Как я могу заставить эти запросы использовать индекс, как и ожидалось?

1 Ответ

0 голосов
/ 24 января 2019

Я думаю, одна проблема может заключаться в том, что оптимизатор отказывается объединять представление (и учитывать любые индексы в базовых таблицах), когда внешний блок запроса содержит функции PL / SQL (например, TABLE()).

Если вы вручную развернете представление и запросите таблицу напрямую, он сможет получить точный доступ к индексу:

SELECT id, COUNT(1) OVER ( PARTITION BY id ) AS cnt
  FROM test
  WHERE id IN ( SELECT COLUMN_VALUE
              FROM TABLE( NEW my_tab( 1, 2 ) ) tab )
 ;

----------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |          |     1 |     6 |    31   (4)| 00:00:01 |
|   1 |  WINDOW SORT                            |          |     1 |     6 |    31   (4)| 00:00:01 |
|*  2 |   HASH JOIN SEMI                        |          |     1 |     6 |    30   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN                      | IDX_TEST |  1000 |  4000 |     1   (0)| 00:00:01 |
|   4 |    COLLECTION ITERATOR CONSTRUCTOR FETCH|          |  8168 | 16336 |    29   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------

Я не уверен, есть ли способпереопределить это поведение, или если это ограничение в оптимизаторе.Я попытался переместить функцию TABLE в CTE, но это, похоже, не помогает.

...