Я не ищу оптимизацию следующего, просто объяснение. У меня есть этот запрос:
SELECT COUNT(LARGE_A.id_a), SUM(LARGE_A.b_integer)
FROM LARGE_A
INNER JOIN MEDIUM_A ON LARGE_A.id_a = MEDIUM_A.id
INNER JOIN MEDIUM_B ON LARGE_A.id_b = MEDIUM_B.id
WHERE
MEDIUM_A.a_varchar2 LIKE 'Example%' AND
EXTRACT(YEAR FROM MEDIUM_B.a_datetime) = 2000 AND
LARGE_A.a_integer BETWEEN 0 AND 1000;
Типы атрибутов записываются в их именах. Идентификаторы являются целыми числами. В таблице LARGE_A
1 000 000 строк, в средних по 100 000. Идентификаторы являются первичными ключами, однако таблица LARGE_A
имеет составной ключ (id_a, id_b)
.
Затем у меня есть следующий план выполнения, взятый из представления V$SQL
, потому что Autotrace лжет об этом.
SELECT STATEMENT
|SORT AGGREGATE
||HASH JOIN
|||NESTED LOOPS
||||NESTED LOOPS
|||||STATISTICS COLLECTOR
||||||NESTED LOOPS
|||||||TABLE ACCESS FULL LARGE_A
|||||||TABLE ACCESS BY INDEX ROWID MEDIUM_A
||||||||INDEX UNIQUE SCAN PK(MEDIUM_A)
|||||INDEX UNIQUE SCAN PK(MEDIUM_B)
||||TABLE ACCESS BY INDEX ROWID MEDIUM_B
|||TABLE ACCESS FULL MEDIUM_B
PK означает некластеризованный индекс по первичному ключу (целому числу) таблицы, указанной в скобках. Я не использую IOTs здесь.
Зачем оптимизатору сначала запрашивать записи из индекса одну за другой PK(MEDIUM_B)
, затем извлекать остальные строки, используя ROWID, а затем снова запускать ПОЛНОЕ сканирование таблицы?
@NickW Ну вот.
Наверное, неактуально, но это: EXTRACT(YEAR FROM MEDIUM_B.a_datetime) = 0
бессмысленно. Год не может быть равен 0 (в Oracle). Если вы попробуете это, например. to_date('01.01.0000', 'dd.mm.yyyy')
вы получите ORA-01841: (full) year must be between -4713 and +9999, and not be 0
. Итак, если у вас действительно есть это состояние, просто удалите его, так как оно никогда не может быть правдой.
@Littlefoot Извини, я исправлю. В реальном запросе, конечно, есть год от современности.
То, что вы видите, является полным планом, а не фактическим планом (в v$sql
разница между full_plan_hash_value
и plan_hash_value
). Начиная с версии 12c, у Oracle есть адаптивные планы, что дает ему возможность переходить с одного плана на другой в процессе выполнения, если во время выполнения он видит, что его догадки о количестве элементов далеки от ожидаемых. Полный план дает оба варианта — либо хеш-соединение + полное сканирование таблицы B, либо вложенные циклы + индексный доступ к таблице B. Все зависит от того, сколько строк он получает из таблицы A, чтобы выбрать лучший план.
Вы можете увидеть это лучше всего так:
EXPLAIN PLAN FOR ....
/
SELECT * FROM TABLE(dbms_xplan.display(format=>'ADAPTIVE'))
/
Вы увидите отметки -
рядом с отключенными строками плана, которые не будут использоваться по умолчанию... но они все еще являются частью плана, поэтому, если Oracle решит переключиться в середине выполнения, он может включить эти строки и отключить исходные значения по умолчанию. Чтобы увидеть, что на самом деле используется вашим кодом во время выполнения, вы должны наблюдать за этим, получив v$session.sql_id / sql_child_number
, а затем выполните:
SELECT * FROM TABLE(dbms_xplan.display_cursor([sqlid],[childnumber]))
/
Значит, Autotrace отображает правильный план? Тот, который фактически выполняется как последний (изменен на средний полет)?
Нет, автотрассировка показывает вам полный план, который включает шаги, которые отключены и не будут использоваться. Вы почти наверняка получите вложенные циклы + индексный доступ к таблице B, а полное сканирование таблицы + хэш-соединение не будет использоваться. Я предлагаю в будущем использовать dbms_xplan, а не автотрассировку.
Было бы полезно обновить ваш вопрос с помощью оператора SQL, который вы используете.