0

Я столкнулся с загадочной ситуацией. У запроса был хороший план выполнения. Но когда этот запрос использовался как внутренний запрос в более крупном запросе, этот план изменился. Я пытаюсь понять, почему это может быть так.Выполнение плана внутреннего запроса отличается при запуске как часть более крупного запроса

Это было на Oracle 11g. Мой запрос был:

SELECT * FROM YFS_SHIPMENT_H  
WHERE SHIPMENT_KEY IN 
    (
     SELECT DISTINCT SHIPMENT_KEY 
     FROM YFS_SHIPMENT_LINE_H 
     WHERE ORDER_HEADER_KEY = '20150113083918815889858' 
     OR (ORDER_LINE_KEY IN ( '20150113084438815896336')) 
    ); 

Как вы можете видеть, есть внутренний запрос здесь, что:

SELECT DISTINCT SHIPMENT_KEY 
FROM YFS_SHIPMENT_LINE_H 
WHERE ORDER_HEADER_KEY = '20150113083918815889858' 
OR (ORDER_LINE_KEY IN ( '20150113084438815896336')) 

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

PLAN_TABLE_OUTPUT 
======================================================================================================== 
SQL_ID 3v82m4j5tv1k3, child number 0 
===================================== 
SELECT DISTINCT SHIPMENT_KEY FROM YFS_SHIPMENT_LINE_H WHERE 
ORDER_HEADER_KEY = '20150113083918815889858' OR (ORDER_LINE_KEY IN (
'20150113084438815896336')) 

Plan hash value: 3691773903 

======================================================================================================== 
| Id | Operation      | Name     | Rows | Bytes | Cost (%CPU)| Time  | 
======================================================================================================== 
| 0 | SELECT STATEMENT    |      |  |  | 10 (100)|   | 
| 1 | HASH UNIQUE     |      |  7 | 525 | 10 (10)| 00:00:01 | 
| 2 | CONCATENATION    |      |  |  |   |   | 
| 3 | TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_LINE_H |  1 | 75 |  4 (0)| 00:00:01 | 
|* 4 |  INDEX RANGE SCAN   | YFS_SHIPMENT_LINE_H_I4 |  1 |  |  3 (0)| 00:00:01 | 
|* 5 | TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_LINE_H |  6 | 450 |  5 (0)| 00:00:01 | 
|* 6 |  INDEX RANGE SCAN   | YFS_SHIPMENT_LINE_H_I6 |  6 |  |  3 (0)| 00:00:01 | 
======================================================================================================== 

Predicate Information (identified by operation id): 
=================================================== 

    4 = access("ORDER_LINE_KEY"='20150113084438815896336') 
    5 = filter(LNNVL("ORDER_LINE_KEY"='20150113084438815896336')) 
    6 = access("ORDER_HEADER_KEY"='20150113083918815889858') 

план выполнения показывает, что таблица YFS_SHIPMENT_LINE_H доступен с двумя индексами YFS_SHIPMENT_LINE _H_I4 и YFS_SHIPMENT_LINE_H_I6; и затем результаты конкатенируются. Этот план кажется прекрасным, и время ответа на запрос велико.

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

PLAN_TABLE_OUTPUT 
======================================================================================================= 
SQL_ID dk1bp8p9g3vzx, child number 0 
===================================== 
SELECT * FROM YFS_SHIPMENT_H WHERE SHIPMENT_KEY IN (SELECT DISTINCT 
SHIPMENT_KEY FROM YFS_SHIPMENT_LINE_H WHERE ORDER_HEADER_KEY = 
'20150113083918815889858' OR (ORDER_LINE_KEY IN (
'20150113084438815896336'))) 

Plan hash value: 3651083773 

======================================================================================================= 
| Id | Operation     | Name     | Rows | Bytes | Cost (%CPU)| Time  | 
======================================================================================================= 
| 0 | SELECT STATEMENT    |      |  |  | 12593 (100)|   | 
| 1 | NESTED LOOPS    |      |  |  |   |   | 
| 2 | NESTED LOOPS    |      |  7 | 6384 | 12593 (1)| 00:02:32 | 
| 3 | SORT UNIQUE    |      |  7 | 525 | 12587 (1)| 00:02:32 | 
|* 4 |  INDEX FAST FULL SCAN  | YFS_SHIPMENT_LINE_H_I2 |  7 | 525 | 12587 (1)| 00:02:32 | 
|* 5 | INDEX UNIQUE SCAN   | YFS_SHIPMENT_H_PK  |  1 |  |  1 (0)| 00:00:01 | 
| 6 | TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_H   |  1 | 837 |  2 (0)| 00:00:01 | 
======================================================================================================= 

Predicate Information (identified by operation id): 
=================================================== 

    4 = filter(("ORDER_HEADER_KEY"='20150113083918815889858' OR 
       "ORDER_LINE_KEY"='20150113084438815896336')) 
    5 = access("SHIPMENT_KEY"="SHIPMENT_KEY") 

Пожалуйста, обратите внимание, что YFS_SHIPMENT_LINE_H теперь доступен с различным индексом (YFS_SHIPMENT_LINE_H_I2). Как оказалось, это не очень хороший индекс, и время ответа на запрос страдает.

Мой вопрос: Почему внутренний план выполнения запроса изменяется, когда он выполняется как часть более крупного запроса? Как только оптимизатор разработал лучший способ доступа к YFS_SHIPMENT_LINE_H, почему бы ему не продолжать использовать тот же план выполнения, даже если он является частью более крупного запроса?

Примечание: Я не слишком обеспокоен тем, что будет правильным путем доступа или используемым индексом; и, следовательно, не дает здесь всех индексов на столе; и мощность данных. Моя озабоченность связана с изменением при выполнении отдельно по сравнению с частью другого запроса.

Спасибо.

- Параг

ответ

0

Я не уверен, почему оптимизатор Oracle решает изменить путь исполнения. Но, я думаю, что это лучший способ, чтобы написать запрос:

SELECT s.* 
FROM YFS_SHIPMENT_H s 
WHERE s.SHIPMENT_KEY IN (SELECT sl.SHIPMENT_KEY 
         FROM YFS_SHIPMENT_LINE_H sl 
         WHERE sl.ORDER_HEADER_KEY = '20150113083918815889858' 
         ) OR 
     s.SHIPMENT_KEY IN (SELECT sl.SHIPMENT_KEY 
         FROM YFS_SHIPMENT_LINE_H sl 
         WHERE sl.ORDER_LINE_KEY IN ('20150113084438815896336') 
         ); 

Примечание:

  • Там нет необходимости иметь SELECT DISTINCT в подзапросе для IN. Я почти уверен, что Oracle игнорирует его, но он может добавить накладные расходы.
  • Разделение логики на два запроса делает более вероятным, что Oracle может использовать индексы для запроса (лучшие из них находятся на YFS_SHIPMENT_LINE_H(ORDER_HEADER_KEY, SHIPMENT_KEY) и YFS_SHIPMENT_LINE_H(ORDER_LINE_KEY, SHIPMENT_KEY)).
+0

Вы правы, запрос может действительно быть сделано по-другому. Этот случай оказывается в коде, который я не могу изменить. ...... Кроме того, вы делаете хороший вывод о том, что вам не нужно использовать 'distinct' как часть предложения' in'. –

0

В первом запросе (не используется в качестве подзапроса), базовая таблица доступ на основе условий в пункте where. Индексы в двух задействованных столбцах используются для доступа к строкам.

В сложном запросе вы выполняете полусоединение. Оптимизатор, по праву или ошибочно, решил, что лучше сначала прочитать строки из таблицы shipment, прочитать shipment_key и использовать индекс на shipment_key в таблице shipment_line, чтобы получить строки, чтобы узнать, соответствуют ли они. Условия предложения where в таблице shipment_line теперь являются только предикатами фильтра, они не используются для определения того, какие строки будут извлекаться из таблицы.

Если вы считаете, что оптимизатор ошибается (возможно, хотя и не часто с относительно простыми запросами, такими как этот), убедитесь, что статистика актуальна. Что будет иметь значение здесь, это размер каждой таблицы, сколько строк в среднем имеют одинаковые shipment_key в shipment_line и выборочность условий в предложении where в подзапросе. Имейте в виду, что для внешнего запроса нет необходимости полностью вычислить подзапрос (и, скорее всего, Oracle не вычислит его полностью); для каждой строки из таблицы shipment, как только будет найдена соответствующая строка в таблице shipment_line, которая удовлетворяет условию where, поиск этого shipment_key в shipment_line останавливается.

Одна вещь, которую вы можете сделать, если вы действительно думаете, что оптимизатор ошибается, - это увидеть, что произойдет, если вы используете подсказки. Например, вы можете сказать оптимизатору не использовать индекс I2 на shipment_line (притворяйтесь, что его не существует) - посмотрите, какой план он придумает.

+0

Вы упомянули, что в сложном запросе оракул сначала считывает строки из таблицы 'пересылки', а затем использует' shipment_key' для чтения таблицы «отгрузка». Но, глядя на план выполнения, я чувствовал, что оракул сначала читает «линию отгрузки» (используя быстрое полное сканирование на 'yfs_shipment_line_h_i2'). Не могли бы вы рассказать? Благодарю. –

0

Соединение на shipment_key заставляет оптимизатор использовать наиболее избирательный индекс, в данном случае индекс YFS_SHIPMENT_LINE_H_I2. Стерлинг создал этот индекс для этого запроса, и он НЕПРАВИЛЬНО. Отбросьте его (или сделайте невидимым) и посмотрите, как ваш запрос подбирает правильный план. Если вы не решаетесь отказаться от индекса, так как он является частью продукта Sterling, используйте базовые линии управления SQL Plan.

YFS_SHIPMENT_LINE_H_I2 SHIPMENT_KEY 1 YFS_SHIPMENT_LINE_H_I2 ORDER_HEADER_KEY 2 YFS_SHIPMENT_LINE_H_I2 ORDER_RELEASE_KEY 3 YFS_SHIPMENT_LINE_H_I2 ORDER_LINE_KEY 4 YFS_SHIPMENT_LINE_H_I2 REQUESTED_TAG_NUMBER 5

+0

Можете ли вы добавить краткое описание того, что означает блок текста в нижней части вашего ответа? –

+0

Почему, по-вашему, этот индекс «неправильный»? Кроме того, вы говорите о «соединении» - вы понимаете, что запрос не делает соединение, не так ли? – mathguy

+0

Питер, спасибо. Стив/Матхуи, Питер ответил, основываясь на знании продукта, который делает звонок, и не ограничивается только информацией, представленной в вопросе. Стив, блок текста, добавленный Петром в конце, - это столбцы, используемые индексом YFS_SHIPMENT_LINE_H_I2. Я предполагаю, что это была табличная форма для начала (имя индекса, имя столбца, позиция столбца), но каким-то образом потеряла форматирование. –

Смежные вопросы