Как встроить переменную в PL / SQL? - PullRequest
9 голосов
/ 18 марта 2011

Ситуация

У меня возникли проблемы с планом выполнения запроса для запроса среднего размера над большим объемом данных в Oracle 11.2.0.2.0.Чтобы ускорить процесс, я ввел фильтр диапазона, который примерно выглядит следующим образом:

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((org_from IS NULL) OR (org_from <= org.no))
   AND ((org_to   IS NULL) OR (org_to   >= org.no)))
  -- [...]

Как видите, я хочу ограничить JOIN из organisations, используя дополнительный диапазонорганизационные номера.Код клиента может вызывать DO_STUFF с (предположительно, быстрым) или без (очень медленным) ограничением.

Проблема

Проблема в том, что PL / SQL создаст переменные связывания длявыше org_from и org_to параметров, что я и ожидал в большинстве случаев:

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((:B1 IS NULL) OR (:B1 <= org.no))
   AND ((:B2 IS NULL) OR (:B2 >= org.no)))
  -- [...]

Обходной путь

Только в этом случае я измерил план выполнения запроса какнамного лучше, когда я просто встраиваю значения, то есть когда запрос, выполняемый Oracle, на самом деле выглядит примерно так:

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))
  -- [...]

Под словом "много" я подразумеваю в 5-10 раз быстрее.Обратите внимание, что запрос выполняется очень редко, то есть раз в месяц.Поэтому мне не нужно кэшировать план выполнения.

Мои вопросы

  • Как встроить значения в PL / SQL?Я знаю о EXECUTE IMMEDIATE , но я бы предпочел, чтобы PL / SQL компилировал мой запрос, а не делал конкатенацию строк.

  • Я только что измерил то, что произошлопо стечению обстоятельств или можно предположить, что встроенные переменные действительно лучше (в данном случае)?Причина, по которой я спрашиваю, заключается в том, что я думаю, что переменные связывания заставляют Oracle разрабатывать общий план выполнения, тогда как встроенные значения позволяют анализировать очень специфические столбцы и статистику индекса .Поэтому я могу представить, что это не просто совпадение.

  • Я что-то упустил?Может быть, есть совершенно другой способ добиться улучшения плана выполнения запроса, кроме встраивания переменных (заметьте, я тоже пробовал немало подсказок, но я не эксперт в этой области)?

Ответы [ 5 ]

7 голосов
/ 18 марта 2011

В одном из ваших комментариев вы сказали:

"Также я проверил различные значения связывания. С переменными связывания я получаю несколько полных сканирований, тогда как с жестко закодированными значениями план выглядит очень многолучше. "

Есть два пути.Если вы передаете NULL для параметров, то вы выбираете все записи.В этих условиях полное сканирование таблицы является наиболее эффективным способом получения данных.Если вы передаете значения, то индексированное чтение может быть более эффективным, потому что вы выбираете только небольшое подмножество информации.

Когда вы формулируете запрос, используя переменные связывания, оптимизатор должен принять решение: должен ли он предполагать, что большую часть времени вы будете передавать значения или что вы будете передавать нули?Сложно.Посмотрите на это по-другому: неэффективнее ли выполнять полное сканирование таблицы, когда вам нужно только выбрать подмножество записей, или выполнять индексированное чтение, когда вам нужно выбрать все записи?

Похоже, что оптимизатор отключил полное сканирование таблицы как наименее неэффективную операцию, чтобы охватить все возможные случаи.

В то время как при жестком кодировании значений оптимизатору сразу становится известно, что 10 IS NULLоценивается как ЛОЖЬ, и поэтому он может взвесить преимущества использования индексированных операций чтения для поиска нужных записей подмножества.


Итак, что делать?Поскольку вы говорите, что этот запрос выполняется только один раз в месяц, я думаю, что для выполнения отдельных запросов потребуется лишь небольшое изменение бизнес-процессов: один для всех организаций и один для подмножества организаций.


"Кстати, удаление предложения: R1 IS NULL не сильно меняет план выполнения, что оставляет меня с другой стороны условия OR: R1 <= org.no, где NULL в любом случае не имеет смысла, поскольку org.no не равен NULL "</p>

Хорошо, значит, у вас есть пара переменных связывания, которые задают диапазон .В зависимости от распределения значений разные диапазоны могут соответствовать разным планам выполнения.То есть этот диапазон (вероятно) подойдет для сканирования с индексированным диапазоном ...

WHERE org.id BETWEEN 10 AND 11

... тогда как он, скорее всего, будет более приспособлен для сканирования полной таблицы ...

WHERE org.id BETWEEN 10 AND 1199999

Именно здесь вступает в игру Bind Variable Peeking.

(конечно, в зависимости от распределения значений).

4 голосов
/ 18 марта 2011

Поскольку планы запросов на самом деле постоянно отличаются, это означает, что оценки кардинальности оптимизатора по какой-то причине отключены. Можете ли вы подтвердить из планов запросов, что оптимизатор ожидает, что условия будут недостаточно избирательными при использовании переменных связывания? Поскольку вы используете 11.2, Oracle должен использовать адаптивное совместное использование курсоров , поэтому это не должно быть проблемой просмотра переменных связывания (при условии, что вы вызываете версию с переменными связывания много раз с разными значениями NO в ваше тестирование.

Правильны ли оценки мощности в хорошем плане? Я знаю, что вы сказали, что статистика в столбце NO является точной, но я бы с подозрением отнесся к случайной гистограмме, которая может не обновляться, например, в процессе регулярного сбора статистики.

Вы всегда можете использовать подсказку в запросе для принудительного использования определенного индекса (хотя с точки зрения долгосрочного обслуживания предпочтительнее было бы использовать сохраненный контур или стабильность плана оптимизатора ). Любой из этих вариантов предпочтительнее, чем использование динамического SQL.

Тем не менее, одним из дополнительных тестов будет замена синтаксиса соединения SQL 99 старым синтаксисом Oracle, т.е.

SELECT <<something>>
  FROM <<some other table>> cust,
       organization org
 WHERE cust.org_id = org.id
   AND (    ((org_from IS NULL) OR (org_from <= org.no)) 
        AND ((org_to   IS NULL) OR (org_to   >= org.no)))

Это, очевидно, не должно ничего менять, но были проблемы с синтаксическим анализатором в синтаксисе SQL 99, так что это кое-что нужно проверить.

3 голосов
/ 18 марта 2011

Это похоже на необходимость адаптивного совместного использования курсора в сочетании со стабильностью SQLPlan. Я думаю, что происходит, что capture_sql_plan_baselines parameter is true. И то же самое для use_sql_plan_baselines. Если это правда, происходит следующее:

  1. При первом анализе запроса он анализируется и получает новый план.
  2. Во второй раз этот план сохраняется в sql_plan_baselines как принятый план.
  3. Все последующие запуски этого запроса используют этот план, независимо от того, каковы переменные связывания.

Если Adaptive Cursor Sharing уже активен, оптимизатор сгенерирует новый / лучший план, сохранит его в sql_plan_baselines, но не сможет его использовать, пока кто-то не примет этот более новый план в качестве приемлемого альтернативного плана. Проверьте dba_sql_plan_baselines и посмотрите, есть ли в вашем запросе записи с accepted = 'NO' and verified = null Вы можете использовать dbms_spm.evolve для разработки нового плана и автоматически принимать его, если выполнение плана по крайней мере в 1,5 раза лучше, чем без нового плана.

Надеюсь, это поможет.

3 голосов
/ 18 марта 2011

Пахнет как Bind Peeking , но я только на Oracle 10, поэтому я не могу утверждать, что такая же проблема существует в 11.

2 голосов
/ 18 марта 2011

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

Кажется, что в вашей организации есть столбец № (org.no), который определяется какчисло.В вашем жестко запрограммированном примере вы используете числа для сравнения.

JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))

В вашей процедуре вы передаете varchar2 :

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

Таким образом, чтобы сравнить varchar2 с числом, Oracle придется выполнитьпреобразования , так что это может привести к полному сканированию.

Решение: изменить proc для передачи чисел

...