Как предотвратить рекурсивную проверку с помощью полного сканирования таблицы в операторе start - PullRequest
0 голосов
/ 19 июня 2019

У меня есть древовидная структура базы данных с id и parent_id, и я хочу создать представление, которое возвращает все старшие для данного дочернего идентификатора.Это всегда приводит к плану выполнения с полным сканированием таблицы.

Добавление подсказок или расчет статистики не помогли.Проблема воспроизводима даже при просмотре записей.Если я добавлю условие запуска непосредственно в оператор запуска, индекс будет использоваться и производительность будет хорошей.

-- Oracle Database 12c Enterprise Edition Release 12.1.0.2.0


CREATE TABLE t AS (
  SELECT   10 parent_id,   1 child_id FROM dual UNION ALL
  SELECT  100 parent_id,  10 child_id FROM dual UNION ALL
  SELECT NULL parent_id, 100 child_id FROM dual 
);


CREATE INDEX child_idx ON t (child_id);

CREATE OR REPLACE VIEW parents_v AS
WITH recu(
  start_id, child_id, parent_id
)
AS(
  -- start 
  SELECT 
     child_id start_id, child_id, parent_id
  FROM  t
  UNION ALL
  SELECT
      recu.child_id, pre.child_id, pre.parent_id
  FROM  recu, t pre
  WHERE recu.parent_id = pre.child_id
)
SELECT * FROM recu;

Запрос на тестирование индекса:

  SELECT * FROM t WHERE child_id = 1;

Объяснение плана:

SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR);

-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T         |     1 |    26 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | CHILD_IDX |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Результат ОК, индекс CHILD_IDX используется

Запрос на проверку рекурсии:

SELECT * FROM parents_v WHERE start_id = 1;

Объяснить план:

SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR);
--------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |      |       |       |     7 (100)|          |
|*  1 |  VIEW                                     |      |     6 |   234 |     7  (15)| 00:00:01 |
|   2 |   UNION ALL (RECURSIVE WITH) BREADTH FIRST|      |       |       |            |          |
|   3 |    TABLE ACCESS FULL                      | T    |     3 |    78 |     2   (0)| 00:00:01 |
|*  4 |    HASH JOIN                              |      |     3 |   156 |     5  (20)| 00:00:01 |
|   5 |     RECURSIVE WITH PUMP                   |      |       |       |            |          |
|   6 |     TABLE ACCESS FULL                     | T    |     3 |    78 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Результат не ОК, индексCHILD_IDX используется , а не , всегда выполняется полное сканирование таблицы.

1 Ответ

0 голосов
/ 22 июня 2019

Похоже, что невозможно сказать БД, что столбец рекурсии имеет отношение один к одному с "выбором начала" рекурсии. Единственный обходной путь, который я нашел, - это использование оператора select с параметрами вместо представления. В операторе выбора ограничение start_id может быть добавлено непосредственно к «запуску выбора».

Просмотр и star_id_parameter (неправильное время выполнения)

БД формирует полное будущее данных в памяти и фильтрует в конце.

CREATE OR REPLACE VIEW parents_v AS
WITH recu(
  start_id, child_id, parent_id
)
AS(
  -- start 
  SELECT 
     child_id start_id, child_id, parent_id
  FROM  t
  UNION ALL
  SELECT
      recu.child_id, pre.child_id, pre.parent_id
  FROM  recu, t pre
  WHERE recu.parent_id = pre.child_id
)
SELECT * FROM recu;


SELECT * 
FROM parents_v 
WHERE start_id = start_id_parameter;

star_id_parameter в начале выбора рекурсии (быстрое выполнение)

БД использует индекс и запрашивает только записи представлений в пути к дереву.

WITH recu(
  start_id, child_id, parent_id
)
AS(
  -- start 
  SELECT 
     child_id start_id, child_id, parent_id
  FROM  t
  -- CHANGE start_id CONSTRAINT ADDED
  WHERE child_id = &start_id_parameter
  --
  UNION ALL
  SELECT
      recu.child_id, pre.child_id, pre.parent_id
  FROM  recu, t pre
  WHERE recu.parent_id = pre.child_id
)
SELECT * FROM recu;

-- CHANGE NO VIEW ANY MORE
-- SELECT * 
-- FROM parents_v 
-- WHERE start_id = start_id_parameter;
...