Oracle 8i функция даты медленно - PullRequest
3 голосов
/ 15 февраля 2011

Я пытаюсь запустить следующий PL / SQL на сервере Oracle 8i (старый, я знаю):

select
    -- stuff --
from
    s_doc_quote d,
    s_quote_item i,
    s_contact c,
    s_addr_per a,
    cx_meter_info m
where
    d.row_id = i.sd_id
    and d.con_per_id = c.row_id
    and i.ship_per_addr_id = a.row_id(+)
    and i.x_meter_info_id = m.row_id(+)
    and d.x_move_type in ('Move In','Move Out','Move Out / Move In')
    and i.prod_id in ('1-QH6','1-QH8')
    and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate
;

Однако выполнение выполняется невероятно медленно.Поскольку сервер отключается около полуночи каждую ночь, он часто не завершается вовремя.

План выполнения выглядит следующим образом:

SELECT STATEMENT   1179377
 NESTED LOOPS   1179377
  NESTED LOOPS OUTER  959695
   NESTED LOOPS OUTER  740014
    NESTED LOOPS   520332
     INLIST ITERATOR
      TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 157132
       INDEX RANGE SCAN S_QUOTE_ITEM_IDX8 8917
     TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1
      INDEX UNIQUE SCAN S_DOC_QUOTE_P1 1
    TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1
     INDEX UNIQUE SCAN S_ADDR_PER_P1 1
   TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1
    INDEX UNIQUE SCAN CX_METER_INFO_P1 1
  TABLE ACCESS BY INDEX ROWID S_CONTACT 1
   INDEX UNIQUE SCAN S_CONTACT_P1 1

Если я изменю следующее условие where, однако:

and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate

Для статического значения, такого как:

and d.created between to_date('20110101','yyyymmdd') and sysdate

план выполнения становится:

SELECT STATEMENT   5
 NESTED LOOPS   5
  NESTED LOOPS OUTER  4
   NESTED LOOPS OUTER  3
    NESTED LOOPS   2
     TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1
      INDEX RANGE SCAN S_DOC_QUOTE_IDX1 3
     INLIST ITERATOR
      TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 1
       INDEX RANGE SCAN S_QUOTE_ITEM_IDX4 4
    TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1
     INDEX UNIQUE SCAN S_ADDR_PER_P1 1
   TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1
    INDEX UNIQUE SCAN CX_METER_INFO_P1 1
  TABLE ACCESS BY INDEX ROWID S_CONTACT 1
   INDEX UNIQUE SCAN S_CONTACT_P1 1

, который начинает возвращать строки почти мгновенно.

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

Поскольку я относительно новичок в PL / SQL, я не могу понять причины столь существенных различий в планах выполнения.

Я также пытаюсь выполнить запрос как SAS, но для проверки скорости выполнения я использую SQL * Plus.

РЕДАКТИРОВАТЬ:

Для пояснения я уже пытался использовать переменные связывания следующим образом:

var start_date varchar2(8);
exec :start_date := to_char(add_months(trunc(sysdate,'MM'), -1),'yyyymmdd')

со следующим предложением where:

and d.created between to_date(:start_date,'yyyymmdd') and sysdate

, которое возвращает стоимость выполнения 1179377.

Я также хотел бы по возможности избегать связывания переменных, так как не верю, что могу ссылаться на них из сквозного запроса SAS (хотя могу ошибаться).

Ответы [ 4 ]

8 голосов
/ 15 февраля 2011

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

Хороший план выполнения начинается с сканирования диапазона на S_DOC_QUOTE_IDX1. Учитывая характер изменения запроса, я предполагаю, что это индекс для столбца CREATED. Часто оптимизатор не выбирает использование индекса для столбца даты, если условие фильтра основано на SYSDATE. Поскольку он не оценивается до времени выполнения, после того как план выполнения был определен, синтаксический анализатор не может правильно оценить избирательность условия фильтра даты. Когда вместо этого вы используете жестко запрограммированную дату начала, анализатор может использовать эту информацию для определения селективности и делает лучший выбор в отношении использования индекса.

Я бы также предложил связать переменные, но я думаю, что из-за того, что вы используете 8i, оптимизатор не может взглянуть на значения связывания, так что он остается таким же темным, как и прежде. В более поздней версии Oracle я ожидаю, что решение для связывания будет эффективным.

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

Поэтому я предлагаю написать некоторый код для определения статического значения для даты начала и объединить его непосредственно в строку запроса перед анализом и выполнением.

6 голосов
/ 15 февраля 2011

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

Вы получаете совершенно другое время выполнения, потому что, как вы заметили, Oracle выбирает другой путь доступа. Выбор одного пути доступа по другому может привести к разнице во времени выполнения на порядки величин. Поэтому реальный вопрос не в том, «почему add_months требует времени?». но:

Почему Oracle выбирает этот конкретный неэффективный путь, в то время как есть более эффективный?

Чтобы ответить на этот вопрос, нужно понять, как работает оптимизатор. Оптимизатор выбирает конкретный путь доступа, оценивая стоимость нескольких путей доступа (все из них, если имеется только несколько таблиц) и выбирая план выполнения, который, как ожидается, будет наиболее эффективным. Алгоритм для определения стоимости плана выполнения имеет правила, и он делает свою оценку на основе статистики, собранной из ваших данных.

Как и все алгоритмы оценки, он делает предположения о ваших данных, такие как общее распределение, основанное на минимальном / максимальном значении столбцов, количество элементов и физическое распределение значений в сегменте (коэффициент кластеризации).

Как это относится к вашему конкретному запросу

В вашем случае оптимизатор должен оценить селективность различных предложений фильтра. В первом запросе фильтр находится между двумя переменными (add_months(trunc(sysdate,'MM'), -1) and sysdate), в то время как в другом случае фильтр находится между константой и переменной.

Они выглядят одинаково для вас, потому что вы заменили переменную на ее значение, но для оптимизатора случаи очень разные: оптимизатор (по крайней мере, в 8i) вычисляет план выполнения только для определенного запроса. Как только путь доступа будет определен, все последующее выполнение получит тот же план выполнения. Поэтому он не может заменить переменную на ее значение, потому что это значение может измениться в будущем, и план доступа должен работать для всех возможных значений.

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

Что вы можете сделать, если оптимизатор не выберет правильный план

Как упоминалось выше, оптимизатор иногда делает неверные предположения, что приводит к неоптимальному пути доступа. Даже если это случается редко, это может иметь катастрофические последствия (часы, а не секунды). Вот некоторые действия, которые вы можете попробовать:

  • Убедитесь, что ваша статистика актуальна . Столбцы last_analyzed на ALL_TABLES и ALL_INDEXES сообщат вам, когда в последний раз собирали статистику по этим объектам. Хорошие достоверные статистические данные приводят к более точным догадкам, что (надеюсь) приводит к улучшению плана выполнения.
  • Узнайте о различных вариантах сбора статистики (пакет dbms_stats)
  • Перепишите ваш запрос, чтобы использовать константы, когда это имеет смысл, чтобы оптимизатор сделал более надежные предположения.
  • Иногда два логически идентичных запроса приводят к разным планам выполнения, поскольку оптимизатор не будет вычислять одни и те же пути доступа (из всех возможных путей).
  • Есть несколько приемов, которые вы можете использовать, чтобы заставить оптимизатор выполнить некоторые объединения раньше других, например:
    • Используйте rownum для материализации подзапроса (это может занять больше временного пространства, но позволит вам выполнить оптимизатор через определенный шаг).
    • Используйте подсказки , хотя большую часть времени я обращаюсь к подсказкам только тогда, когда ничего не помогает.В частности, я иногда использую подсказку LEADING , чтобы заставить оптимизатор запускаться с определенной таблицы (или пары таблиц).
  • И наконец, вы будетевероятно, обнаружится, что более поздние выпуски имеют в целом более надежный оптимизатор.8i исполнилось 12 лет, и может быть настало время для обновления:)

Это действительно интересная тема.Оптимизатор оракула постоянно меняется (между выпусками), он улучшается с течением времени, даже если новые причуды иногда появляются по мере исправления дефектов.Если вы хотите узнать больше, я бы посоветовал Джонатану Льюису Oracle на основе стоимости: основы

1 голос
/ 15 февраля 2011

Это потому, что функция запускается для каждого сравнения.

иногда это быстрее сделать выбор из двойного:

and d.created 
    between (select add_months(trunc(sysdate,'MM'), -1) from dual) 
    and sysdate

В противном случае вы также можете присоединиться к дате:

select
    -- stuff --
from
    s_doc_quote d,
    s_quote_item i,
    s_contact c,
    s_addr_per a,
    cx_meter_info m,
    (select add_months(trunc(sysdate,'MM'), -1) as startdate from dual) sd
where
    d.row_id = i.sd_id
    and d.con_per_id = c.row_id
    and i.ship_per_addr_id = a.row_id(+)
    and i.x_meter_info_id = m.row_id(+)
    and d.x_move_type in ('Move In','Move Out','Move Out / Move In')
    and i.prod_id in ('1-QH6','1-QH8')
    and d.created between sd.startdate and sysdate

Последний вариант и фактически лучший шанс повышения производительности: добавьте параметр даты в запрос следующим образом:

and d.created between :startdate and sysdate

[править] Извините, я вижу, вы уже пробовали варианты, подобные этим. Все еще странно. Если константное значение работает, параметр bind также должен работать, если вы оставляете функцию add_months вне запроса.

0 голосов
/ 15 февраля 2011

Это SQL.Возможно, вы захотите использовать PL / SQL и сохранить вычисление add_months (trunc (sysdate, 'MM'), -1) сначала в переменную, а затем связать это.

Кроме того, я видел, как SAS вычисляетдолгое время из-за перетаскивания данных по сети и выполнения дополнительной работы в каждой строке, которую они обрабатывают.В зависимости от вашей среды вы можете рассмотреть вопрос о создании временной таблицы, чтобы сначала сохранить результаты этих объединений, а затем нажать на временную таблицу (попробуйте CTAS).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...