Как следует индексировать таблицы для оптимизации этого запроса Oracle SELECT? - PullRequest
0 голосов
/ 09 сентября 2011

Я получил следующий запрос в Oracle10g:

select * 
  from DATA_TABLE DT, 
       LOOKUP_TABLE_A LTA, 
       LOOKUP_TABLE_B LTB
 where DT.COL_A = LTA.COL_A (+) 
   and DT.COL_B = LTA.COL_B (+) 
   and LTA.COL_C = LTB.COL_C
   and LTA.COL_B = LTB.COL_B
   and ( DT.REF_TXT = :refTxt or DT.ALT_REF_TXT = :refTxt )
   and DT.CREATED_DATE between :startDate and :endDate

И мне было интересно, есть ли у вас какие-либо подсказки для оптимизации запроса.

В настоящее время у меня есть следующееиндексы:

IDX1 on DATA_TABLE (REF_TXT, CREATED_DATE)
IDX2 on DATA_TABLE (ALT_REF_TXT, CREATED_DATE)
LOOKUP_A_PK on LOOKUP_TABLE_A (COL_A, COL_B)
LOOKUP_A_IDX1 on LOOKUP_TABLE_A (COL_C, COL_B)
LOOKUP_B_PK on LOOKUP_TABLE_B (COL_C, COL_B)

Обратите внимание, таблицы LOOKUP очень малы (<200 строк). </p>

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

Объяснить план:

Query Plan
SELECT STATEMENT   Cost = 8
  FILTER
    NESTED LOOPS
      NESTED LOOPS
        TABLE ACCESS BY INDEX ROWID DATA_TABLE
          BITMAP CONVERSION TO ROWIDS
            BITMAP OR
              BITMAP CONVERSION FROM ROWIDS
                SORT ORDER BY
                  INDEX RANGE SCAN IDX1
              BITMAP CONVERSION FROM ROWIDS
                SORT ORDER BY
                  INDEX RANGE SCAN IDX2
        TABLE ACCESS BY INDEX ROWID LOOKUP_TABLE_A
          INDEX UNIQUE SCAN LOOKUP_A_PK
      TABLE ACCESS BY INDEX ROWID LOOKUP_TABLE_B
        INDEX UNIQUE SCAN LOOKUP_B_PK

EDIT2:

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

Будет 10000 различных REF_TXT, по 10-100 с CREATED_DT для каждого.ALT_REF_TXT будет в основном NULL, но будет 100 с-1000 с, которые будут отличаться от REF_TXT.

EDIT3: Исправлено то, что на самом деле содержит ALT_REF_TXT.

Ответы [ 5 ]

3 голосов
/ 09 сентября 2011

Ваши 2 сканирования диапазона индексов на IDX1 и IDX2 дадут не более 100 строк, поэтому ваш BITMAP CONVERSION TO ROWIDS даст не более 200 строк. И оттуда, это только индексированный доступ по rowids, что приводит к вероятному выполнению за секунду. Так вы действительно испытываете проблемы с производительностью? Если да, то сколько времени это займет?

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

С уважением,
Роб.

3 голосов
/ 09 сентября 2011

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

Как уже отмечали другие, у вас есть некоторые индикаторы внешнего соединения, но затем вы по существу предотвращаете внешнее соединение, требуя равенства для других столбцов в двух внешних таблицах. Как видно из плана выполнения, внешнего объединения не происходит. Если вам не нужно внешнее соединение, удалите операторы (+), они только запутывают проблему. Если вы хотите внешнее соединение, перепишите запрос, как показано @ Dems.

Если вы недовольны текущей производительностью, я бы предложил выполнить запрос с подсказкой gather_plan_statistics, а затем с помощью DBMS_XPLAN.DISPLAY_CURSOR(?,?,'ALLSTATS LAST') просмотреть фактическую статистику выполнения. Это покажет истекшее время, приписанное каждому шагу в плане выполнения.

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

2 голосов
/ 09 сентября 2011

Это может быть запрос, который вы хотите использовать с более современным синтаксисом.

(и без внутренних объединений, нарушающих внешние объединения)

select
  * 
from
  DATA_TABLE DT
left outer join
  (
    LOOKUP_TABLE_A LTA
  inner join
    LOOKUP_TABLE_B LTB
      on  LTA.COL_C = LTB.COL_C
      and LTA.COL_B = LTB.COL_B
  )
    on  DT.COL_A = LTA.COL_A
    and DT.COL_B = LTA.COL_B
where
   ( DT.REF_TXT = :refTxt or DT.ALT_REF_TXT = :refTxt )
   and DT.CREATED_DATE between :startDate and :endDate

У меня есть следующие индексы ...

LOOKUP_TABLE_A (COL_A, COL_B)
LOOKUP_TABLE_B (COL_B, COL_C)
DATA_TABLE (REF_TXT, CREATED_DATE)
DATA_TABLE (ALT_REF_TXT, CREATED_DATE)


Примечание. Первое условие в предложении WHERE содержит ИЛИ, которое, вероятно, нарушит использование INDEX.В этом случае я увидел преимущества в производительности при объединении двух запросов вместе ...

  <your query>
where
   DT.REF_TXT = :refTxt
   and DT.CREATED_DATE between :startDate and :endDate

UNION

  <your query>
where
   DT.ALT_REF_TXT = :refTxt
   and DT.CREATED_DATE between :startDate and :endDate
2 голосов
/ 09 сентября 2011

Это один из тех случаев, когда нет смысла пытаться оптимизировать производительность СУБД, не зная, что означают ваши данные.

Есть ли у вас много разных значений CREATED_DATE и несколько строк в вашей DT для каждой даты? Если это так, вам нужен индекс CREATED_DATE, так как СУБД будет основным способом отклонять столбцы, которые она не хочет обрабатывать.

С другой стороны, у вас есть только несколько дат и много разных значений REF_TXT или ALT_REF_TXT? В этом случае вы, вероятно, имеете правильный выбор составного индекса.

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

Если у вас есть десятки миллионов различных значений REF_TXT и ALT_REF_TXT, вы можете рассмотреть возможность денормализации этой схемы.


Изменить. Спасибо за дополнительную информацию. В вашем плане объяснения нет курящих пистолетов, которые я вижу. Несколько вещей, которые можно попробовать дальше, если вы еще не довольны производительностью.

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

Обменяйте SELECT * на имена столбцов, которые вам действительно нужны в наборе результатов запроса. В любом случае это хорошая практика программирования, и она МОЖЕТ позволить оптимизатору избежать какой-либо работы.

Если что-то все еще слишком медленно, попробуйте преобразовать это как СОЮЗ из двух запросов, а не с помощью ИЛИ. Это МОЖЕТ позволить части alt_ref_txt вашего запроса, которая немного усложняется всеми значениями NULL в этом столбце, оптимизироваться отдельно.

0 голосов
/ 09 сентября 2011

Обеспечить вывод этого запроса с помощью «set autot trace». Посмотрим, сколько блоков он тянет. Объясните, план выглядит хорошо, он должен быть очень быстрым. Если вам нужно больше, денормализуйте информацию таблицы поиска в DT. Нарушает третью нормальную форму, но это ускорит ваш запрос, исключив объединения. В ситуации, когда считаются миллисекунды, все находится в буферах, и вам нужно, чтобы этот запрос выполнялся 1000 раз в секунду, это может помочь путем уменьшения количества блоков, просматриваемых в строке. Это лучший способ повысить производительность чтения, но усложняет ваше приложение (и разрушает вашу прекрасную диаграмму ER).

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