Oracle и возможные постоянные предикаты в предложении WHERE

У меня есть общая проблема с ORACLE в следующем примере кода:

create or replace procedure usp_test
(
  p_customerId number,
  p_eventTypeId number,
  p_out OUT SYS_REFCURSOR
)
as
begin

  open p_out for
    select e.Id from eventstable e
    where 
      (p_customerId is null or e.CustomerId = p_customerId)
      and
      (p_eventTypeId is null or e.EventTypeId = p_eventTypeId)
    order by Id asc;

end usp_test;

«ИЛИ» в «(p_customerId is null or e.CustomerId = p_customerId)» убивает производительность процедуры, потому что оптимизатор не будет оптимально использовать индекс (надеюсь, для поиска по индексу) в столбце «CustomerId», что приводит к сканированию вместо поиска. Индекс «CustomerId» имеет множество различных значений.

При работе с MSSQL 2008 R2 (последний SP) или MSSQL 2012 я могу подсказать запрос с помощью «option (recompile)», который будет:

  1. Перекомпилируйте только этот запрос
  2. Разрешить значения для всех переменных (они известны после вызова sproc)
  3. Замените все разрешенные переменные константами и устраните константные предикатные части.

Например: если я передам p_customerId = 1000, то выражение «1000 равно null» всегда будет ложным, поэтому оптимизатор его проигнорирует. Это добавит некоторую нагрузку на ЦП, но в основном используется для редко вызываемых процедур массивных отчетов, так что здесь проблем нет.

Есть ли способ сделать это в Oracle? Dynamic-SQL не вариант.

Добавляет

Та же процедура, только без «p_customerId is null» и «p_eventTypeId is null» выполняется в течение ~ 0,041 секунды, а верхняя выполняется в течение ~ 0,448 секунды (у меня ~ 5 000 000 строк).


person infideltfo    schedule 26.12.2012    source источник
comment
Но если вы передадите процедуре 1000, то вы хотите сканировать таблицу... ваша процедура выглядит нормально, и она должна использовать индекс. Как ваша статистика?   -  person Ben    schedule 26.12.2012
comment
Не рекомендуется ставить перед именами параметров префикс p_. Просто используйте соответствующее имя, а когда вы используете имя переменной в операторе SQL, добавляйте к нему префикс с именем процедуры или функции. например. usp_test.customerid.   -  person David Aldridge    schedule 26.12.2012
comment
Динамический SQL — это не только реальный вариант, но, вполне возможно, и лучший, поскольку он дает оптимизатору наилучшие шансы предоставить вам оптимальный план выполнения запроса. Я не знаю, почему вы решили не принимать его.   -  person David Aldridge    schedule 26.12.2012
comment
В случае именования p_ это просто пример sproc со случайным именем. О сканировании: я не уверен, что именно здесь делает ORACLE (сканирует некластеризованный индекс или сканирует кластеризованный индекс), потому что кажется, что я не могу искать план выполнения для PL/SQL. Но по сравнению с той же процедурой без части param is null она выполняется в 10 и более раз медленнее.   -  person infideltfo    schedule 26.12.2012
comment
Я новичок в ORACLE, но в MSSQL не использую динамический sql из-за отсутствия статической проверки кода. Рефакторинг также проблематичен.   -  person infideltfo    schedule 26.12.2012
comment
О сканировании: нет, я не ожидаю сканирования индекса, потому что избирательность очень низкая. У меня есть много различных значений в нем. Так что я ожидаю искать. Но в случае как Oracle, так и MSSQL (без подсказок) я получаю сканирование. (он использует правильный индекс, но, к сожалению, сканирует его).   -  person infideltfo    schedule 26.12.2012
comment
@DavidAldridge - не рекомендуется ставить перед именами параметров префикс p_. Действительно? Использование префиксов для указания области объявления переменных невероятно распространено (хотя некоторые стандарты PL/SQL предпочитают вместо этого указывать режим i_, o_ и io_).   -  person APC    schedule 26.12.2012
comment
@APC, да, я согласен, что это распространенная, но популярная != лучшая практика (например, коэффициент попаданий в кэш буфера, явные курсоры, несвязанные переменные). Почему Oracle Corp не следует одной из этих популярных практик? Они поддерживают чистоту своих API, избегая их, а префикс с меткой блока или именем процедуры/функции является наиболее надежным способом разрешения конфликтов пространств имен между SQL и PL/SQL.   -  person David Aldridge    schedule 27.12.2012


Ответы (2)


Индекс одного столбца не может помочь, поскольку он не хранится в определении индекса. Разрешено ли создание индекса (идентификатор клиента, идентификатор события, идентификатор)? Таким образом, все необходимые столбцы находятся в индексе...

person igr    schedule 26.12.2012
comment
У меня есть индекс по CustomerId, EventTypeId и как по CustomerId, так и по EventTypeId. Это не имеет значения. Процедура с p_customerId равна null выполняется в 10 и более раз медленнее, чем та же процедура без такого предиката. - person infideltfo; 26.12.2012

person    schedule
comment
Ух ты. Это работает нормально. Интересное решение, спасибо! (мне нужно заменить NVL(p_customerId, 'x') на NVL(p_customerId, -1), но идея в порядке) - person infideltfo; 26.12.2012
comment
пожалуйста! Индексы, которые я предложил, были для общего ответа, я думаю, вы могли бы также немного настроить свою модель, если у вас уже есть индексы в таблице. - person Sebas; 26.12.2012
comment
Если я правильно понимаю, NVL (CustomerId, -1) просто заменит все нули на -1 в индексе? Не увеличит ли это вес индекса и не нарушит селективность? - person infideltfo; 26.12.2012
comment
Это исправление повышает производительность запросов более чем в два раза: с 0,500 до 0,200 секунды. Но до 0,041 секунды для запроса без OR или NVL еще далеко. - person infideltfo; 27.12.2012
comment
Oracle проиндексирует результат NVL(customerid, -1), чтобы его можно было использовать для фильтрации. Для вашей проблемы с производительностью, если вы удалите фильтр, выбор индексов будет более очевидным, проверьте, подходит ли план объяснения или нет. - person Sebas; 27.12.2012
comment
Это намного более запутанно, чем метод, основанный на динамическом sql, и почти наверняка не работает так же хорошо. - person David Aldridge; 27.12.2012
comment
dynamic sql не имеет ничего общего с оптимизацией запросов. Однако он несет в себе присущее падение производительности из-за времени интерпретации, к счастью, компенсируется правильной предварительной компиляцией сценария, которая действительно привела бы к глобальной оптимизации сценария. (знание того, является ли p_customerId si null заранее, поможет) - person Sebas; 27.12.2012
comment
Так же должен добавить, что эту оптимизацию можно было провести и без динамического sql, просто открывая 2 разных курсора в зависимости от значений параметров. Пожалуйста, избегайте динамического sql, кроме случаев крайней необходимости. Писать комментарии после клубной жизни сложно. Ваше здоровье. - person Sebas; 27.12.2012