2009-12-22 2 views
7

Я написал заявление, на которое уходит почти час, поэтому я прошу помощи, чтобы я мог сделать это быстрее. Так вот мы идем:Sql: Оптимизация предложения BETWEEN

творю внутреннее объединение двух таблиц:

У меня есть много временных интервалов, представленных интервалы, и я хочу, чтобы получить меру ДАННЫХ от мер только в пределах этих интервалов.

intervals: имеет два столбца, один начальное время, другой время окончания интервала (число строк = 1295)

measures: имеет две колонки, одна с мерой, другой с (количество строк = один миллион)

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

Вот мой код:

select measures.measure as measure, measures.time as time, intervals.entry_time as entry_time, intervals.exit_time as exit_time 
    from 
    intervals 
    inner join 
    measures 
    on intervals.entry_time<=measures.time and measures.time <=intervals.exit_time 
    order by time asc 

Благодаря

+0

Являются ли столбцы, на которые вы ссылаетесь, индексируются? вы также должны запустить свой запрос в анализаторе запросов для своей системы баз данных и опубликовать вывод здесь. – nos

+0

hi marc_s: oracle 10.2.0.3 nos: да, это индексировано, я попытаюсь сделать это, тогда omg ponies: я собираюсь взглянуть на него – user235693

+0

Проходят ли интервалы? – Quassnoi

ответ

2

Первое, что я делаю, это ваш инструмент базы данных генерировать план выполнения, который вы можете просматривать (это «Control-L» в MSSQL, но я m не уверен, как это сделать в Oracle) - это попытается указать медленные части, и, в зависимости от вашего сервера/редактора, он может даже рекомендовать некоторые базовые индексы. После того, как у вас есть план выполнения, вы можете искать любые таблицы сканирования внутренних соединений цикла, оба из которых очень медленные - индексы могут помочь при сканировании таблиц, и вы можете добавить дополнительные предикаты соединения, чтобы облегчить объединение циклов.

Мое предположение: MEASURES нуждается в индексе в столбце TIME, и вы также можете включить столбец MEASURE для ускорения поиска. Попробуйте это:

CREATE INDEX idxMeasures_Time ON Measures ([Time]) INCLUDES (Measure) 

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

ON measures.time BETWEEN intervals.entry_time AND intervals.exit_time 

Это просто сочетает в себе ваши два < = и> = в один оператор.

+0

BETWEEN - это просто синтаксический сахар - на самом деле он не будет быстрее и не медленнее. –

+0

Это правда, но rwmnau действительно говорит: «это не изменит ваш план выполнения или ускорит ваш запрос». Я согласен с тем, что читать с предложением BETWEEN проще, чем +1. –

2

Вы не можете оптимизировать свое утверждение - это довольно просто, как есть.

Что вы, , может делать, если вам помогут некоторые индексы.

Вы выбираете на intervals.entry_time, intervals.exit_time, measures.time - указаны ли эти столбцы?

+0

Нет, я думал, что они были, но они нет, завтра я должен изменить это – user235693

1

Ваш SQL эквивалентно:

select m.measure. m.time, 
    i.entry_time, i.exit_time 
from intervals i 
    join measures m 
     on m.time Between i.entry_time And i.exit_time 
order by time asc 

Единственное, что я мог бы предложить удостоверяется есть индекс на m.Time. Тогда, если это не улучшит производительность, попробуйте добавить индексы на i.Start_Time и i.End_Time, а также

+0

Да, это то, что я предложил большинству ответов, я попробую это завтра thanx charles – user235693

19

Это довольно распространенная проблема.

Plain B-Tree индексы не являются хорошими для запросов, как это:

SELECT measures.measure as measure, 
     measures.time as time, 
     intervals.entry_time as entry_time, 
     intervals.exit_time as exit_time 
FROM intervals 
JOIN measures 
ON  measures.time BETWEEN intervals.entry_time AND intervals.exit_time 
ORDER BY 
     time ASC 

Индекс хорош для поиска значений в заданных пределах, например:

, но не для поиска границ, содержащих заданное значение, например:

В этой статье в своем блоге объясняет проблему более подробно:

(вложенные множества моделей сделок с подобным типом предиката).

Вы можете сделать индекс на time, таким образом intervals будет ведущим в соединении, время в диапазоне будет использовано внутри вложенных циклов. Это потребует сортировки по time.

Вы можете создать пространственный индекс intervals (доступный в MySQL с помощью MyISAM хранения), который будет включать в себя start и end в одной колонке геометрии. Таким образом, measures может привести к соединению, и сортировка не потребуется.

Пространственные индексы, однако, медленнее, поэтому это будет эффективно только в том случае, если у вас мало мер, но есть много интервалов.

Поскольку у вас есть несколько интервалов, но многие меры, просто убедитесь, что у вас есть индекс по measures.time:

CREATE INDEX ix_measures_time ON measures (time) 

Update:

Вот пример сценария для теста:

BEGIN 
     DBMS_RANDOM.seed(20091223); 
END; 
/

CREATE TABLE intervals (
     entry_time NOT NULL, 
     exit_time NOT NULL 
) 
AS 
SELECT TO_DATE('23.12.2009', 'dd.mm.yyyy') - level, 
     TO_DATE('23.12.2009', 'dd.mm.yyyy') - level + DBMS_RANDOM.value 
FROM dual 
CONNECT BY 
     level <= 1500 
/

CREATE UNIQUE INDEX ux_intervals_entry ON intervals (entry_time) 
/

CREATE TABLE measures (
     time NOT NULL, 
     measure NOT NULL 
) 
AS 
SELECT TO_DATE('23.12.2009', 'dd.mm.yyyy') - level/720, 
     CAST(DBMS_RANDOM.value * 10000 AS NUMBER(18, 2)) 
FROM dual 
CONNECT BY 
     level <= 1080000 
/

ALTER TABLE measures ADD CONSTRAINT pk_measures_time PRIMARY KEY (time) 
/

CREATE INDEX ix_measures_time_measure ON measures (time, measure) 
/

Этот запрос:

SELECT SUM(measure), AVG(time - TO_DATE('23.12.2009', 'dd.mm.yyyy')) 
FROM (
     SELECT * 
     FROM (
       SELECT /*+ ORDERED USE_NL(intervals measures) */ 
         * 
       FROM intervals 
       JOIN measures 
       ON  measures.time BETWEEN intervals.entry_time AND intervals.exit_time 
       ORDER BY 
         time 
       ) 
     WHERE rownum <= 500000 
     ) 

использует NESTED LOOPS и возвращается в 1.7 секунд.

Этот запрос:

SELECT SUM(measure), AVG(time - TO_DATE('23.12.2009', 'dd.mm.yyyy')) 
FROM (
     SELECT * 
     FROM (
       SELECT /*+ ORDERED USE_MERGE(intervals measures) */ 
         * 
       FROM intervals 
       JOIN measures 
       ON  measures.time BETWEEN intervals.entry_time AND intervals.exit_time 
       ORDER BY 
         time 
       ) 
     WHERE rownum <= 500000 
     ) 

MERGE JOIN использует, и я должен был остановить его после 5 минут.

Update 2:

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

SELECT /*+ LEADING (intervals) USE_NL(intervals, measures) */ 
     measures.measure as measure, 
     measures.time as time, 
     intervals.entry_time as entry_time, 
     intervals.exit_time as exit_time 
FROM intervals 
JOIN measures 
ON  measures.time BETWEEN intervals.entry_time AND intervals.exit_time 
ORDER BY 
     time ASC 

Оптимизатор Oracle «s является недостаточно умны, чтобы увидеть, что интервалы не пересекаются. Вот почему он, скорее всего, будет использовать measures в качестве ведущей таблицы (это было бы мудрое решение, если бы интервалы пересекались).

Обновление 3:

WITH splits AS 
     (
     SELECT /*+ MATERIALIZE */ 
       entry_range, exit_range, 
       exit_range - entry_range + 1 AS range_span, 
       entry_time, exit_time 
     FROM (
       SELECT TRUNC((entry_time - TO_DATE(1, 'J')) * 2) AS entry_range, 
         TRUNC((exit_time - TO_DATE(1, 'J')) * 2) AS exit_range, 
         entry_time, 
         exit_time 
       FROM intervals 
       ) 
     ), 
     upper AS 
     (
     SELECT /*+ MATERIALIZE */ 
       MAX(range_span) AS max_range 
     FROM splits 
     ), 
     ranges AS 
     (
     SELECT /*+ MATERIALIZE */ 
       level AS chunk 
     FROM upper 
     CONNECT BY 
       level <= max_range 
     ), 
     tiles AS 
     (
     SELECT /*+ MATERIALIZE USE_MERGE (r s) */ 
       entry_range + chunk - 1 AS tile, 
       entry_time, 
       exit_time 
     FROM ranges r 
     JOIN splits s 
     ON  chunk <= range_span 
     ) 
SELECT /*+ LEADING(t) USE_HASH(m t) */ 
     SUM(LENGTH(stuffing)) 
FROM tiles t 
JOIN measures m 
ON  TRUNC((m.time - TO_DATE(1, 'J')) * 2) = tile 
     AND m.time BETWEEN t.entry_time AND t.exit_time 

Этот запрос разбивает ось времени в диапазонах и использует HASH JOIN, чтобы присоединиться меры и временные метки на значения дальности, с тонкой фильтрации позже.

Смотрите эту статью в своем блоге для более подробных объяснений о том, как это работает:

+0

+1 Слишком плохо, что я отвечаю в качестве фаворитов. Наверное, мне просто нужно найти страницу блога. :) –

+0

Hi Quassnoi, я новичок в этом, я читаю ваш блог, но это не так просто для меня, я думаю, что я бы понял с практическим примером. В любом случае, я попробую то, что вы предложили, с индексированием на measure.time и посмотреть, могу ли я запустить его. Поскольку я не могу модифицировать себя в таблицах, а затем создать индекс, я просто смогу сделать это завтра, я дам вам обратную связь, спасибо! – user235693

+0

Мне действительно непонятно, как индексирование b-дерева получило бы его быстрее ... – user235693

0

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

Для каждой записи в таблице измерений вы можете иметь несколько записей в таблице интервалов (intervals.entry_time<=measures.time), и для каждой записи в таблице интервалов вы можете иметь несколько записей по мере (measures.time <=intervals.exit_time). возникающие в результате отношения «один-ко-многим» и «многие-к-одному» по соединению означают многократное сканирование таблицы для каждой записи. Я сомневаюсь, что Cartesian Product - правильный термин, но он довольно близок.

Индексирование определенно поможет, но это поможет еще больше, если вы сможете найти лучший ключ для объединения двух таблиц. наличие отношений «один ко многим», идущих в одном направлении, только ускорит обработку, поскольку ему не придется сканировать каждую таблицу/индекс дважды для каждой записи.

+0

«Индексация определенно поможет» Twaddle. В строке SQL КАЖДЫЙ в меру и интервалы необходимо обработать. Имея только 1000 (ish) строк в Intervals, это, вероятно, всего лишь несколько блоков и не будет полезно индексировать, так как есть хороший шанс, что весь индекс нужно будет отсканировать, если критерии находятся в двух столбцах. Попытка прочитать всю таблицу мер с помощью нескольких сканирований индекса, вероятно, замедлит ее. –

+0

Я согласен с тобой в Джереми в том, что я должен попытаться ограничить его отношениями «один ко многим», но я попытался, и с моим отсутствием опыта я не нашел пути. Я определенно пробовал метод индекса thanx – user235693

+0

'@ Gary': каждая строка в' measure' и 'interval' должна, конечно же, обрабатываться. Но используя индексы, каждая строка должна обрабатываться только один раз *. Без индексов каждая строка должна обрабатываться много раз, как в 'CARTESIAN JOIN'. Так что да, индексирование определенно поможет. – Quassnoi

2

попробовать параллельный запрос

 
    alter session enable parallel query; 

    select /*+ parallel */ ... same as before; 

Вы также можете создать материализованное представление, возможно, с параллельным намеком выше. Это может занять много времени, чтобы создать MV, но после его создания можно запросить многократно.

+0

Привет, redcayuga, не могли бы вы объяснить мне более подробно, как работают параллельные запросы и будут ли применяться в этом случае? – user235693

+0

Включение параллельного запроса говорит Oracle разбить большой запрос на части и запускать эти части параллельно (многопоточные или многозадачные или что-то еще). Даже если у вас есть только один процессор, вы можете воспользоваться PQ; в то время как некоторые потоки заблокированы, попробуйте прочитать диск, который поток мог бы использовать CPU для обработки того, что находится в памяти. Чтобы использовать его, выполните команду «изменить сеанс» один раз и положите/* + parallel */hint сразу после выбора. Между знаками/* и + нет. Oracle должен автоматически использовать PQ. Проанализированы ли эти таблицы? – redcayuga

0

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

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

Я предполагаю, что ваш процесс вызова хочет ВСЕ строки. Поскольку предикат соединения не равен равенству, я бы сказал, что MERGE JOIN может быть лучшим подходом для достижения цели. Объединение слияния требует, чтобы его источники данных сортировались, поэтому, если мы можем избежать сортировки, запрос должен выполняться так быстро, как это возможно (запрет более интересных подходов, таких как специализированные индексы или материализованные представления).

Чтобы избежать операции СНП на intervals и measures, можно добавить индексы (measures.time, measures.measure) и (intervals.entry_time, intervals.exit_time). База данных может использовать индекс, чтобы избежать сортировки, и он будет быстрее, потому что ему не нужно посещать какие-либо табличные блоки.

В качестве альтернативы, если у вас есть только индекс на measures.time, запрос может все еще работать нормально, не добавляя еще один большой индекс - он будет работать медленнее, хотя, вероятно, придется прочитать много табличных блоков, чтобы получить measures.measure для SELECT.

+0

+1: Да, мне нравится сортировка для этого. Трудно сказать, кроме планов выполнения. –

3

Подводя итог: ваш запрос работает с полным набором МЕР. Он соответствует времени каждой записи MEASURES для записи INTERVALS. Если время, время, затраченное на INTERVALS, примерно похоже на окно, натянутое на MEASURES, тогда ваш запрос также работает против полного набора INTERVALS, в противном случае он работает с подмножеством.

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

Возможные стратегии не являются:

  • нет индексов на всех
  • индекса МЕР (TIME, МЕРА)
  • индекса МЕР (TIME)
  • не индекс МЕР
  • индекс на INTERVALS (ENTRY_TIME, EXIT_TIME)
  • индекс на INTERVALS (ENTRY_TIME)
  • нет индекса на отрезках
  • параллельный запрос

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

Вот данные испытаний. Как видите, я использую несколько большие наборы данных. Окно INTERVALS больше, чем окна MEASURES, но не намного. Интервалы составляют 10000 секунд в ширину, и меры принимаются каждые 15 секунд.

SQL> select min(entry_time), max(exit_time), count(*) from intervals; 

MIN(ENTRY MAX(EXIT_ COUNT(*) 
--------- --------- ---------- 
01-JAN-09 20-AUG-09  2001 

SQL> select min(ts), max(ts), count(*) from measures; 

MIN(TS) MAX(TS)  COUNT(*) 
--------- --------- ---------- 
02-JAN-09 17-JUN-09 1200001 

SQL> 

NB В моих тестовых данных я предположил, что ИНТЕРВАЛ запись не перекрывается. Это имеет важное значение: запись MEASURES объединяется только с одним INTERVAL.

Benchmark

Вот тест без каких-либо индексов.

SQL> exec dbms_stats.gather_table_stats(user, 'MEASURES', cascade=>true) 

PL/SQL procedure successfully completed. 

SQL> exec dbms_stats.gather_table_stats(user, 'INTERVALS', cascade=>true) 

PL/SQL procedure successfully completed. 

SQL> set timing on 
SQL> 
SQL> select m.measure 
    2   , m.ts as "TIME" 
    3   , i.entry_time 
    4   , i.exit_time 
    5 from 
    6  intervals i 
    7 inner join 
    8  measures m 
    9  on (m.ts between i.entry_time and i.exit_time) 
10 order by m.ts asc 
11/

1200001 rows selected. 

Elapsed: 00:05:37.03 

SQL> 

МЕРЫ тестирует

Теперь давайте создадим уникальный индекс на отрезках (ENTRY_TIME, EXIT_TIME) и опробовать различные стратегии индексирования для МЕР. Сначала введите только индекс MEASURES TIME.

SQL> create index meas_idx on measures (ts) 
    2/

Index created. 

SQL> exec dbms_stats.gather_table_stats(user, 'MEASURES', cascade=>true) 

PL/SQL procedure successfully completed. 

SQL> 
SQL> set autotrace traceonly exp 
SQL> 
SQL> set timing on 
SQL> 
SQL> select m.measure 
    2   , m.ts as "TIME" 
    3   , i.entry_time 
    4   , i.exit_time 
    5 from 
    6  intervals i 
    7 inner join 
    8  measures m 
    9  on (m.ts between i.entry_time and i.exit_time) 
10 order by m.ts asc 
11/

1200001 rows selected. 

Elapsed: 00:05:20.21 

SQL> 

Теперь давайте индекс MEASURES.TIME и МЕРА Колонны

SQL> drop index meas_idx 
    2/

Index dropped. 

SQL> create index meas_idx on measures (ts, measure) 
    2/

Index created. 

SQL> exec dbms_stats.gather_table_stats(user, 'MEASURES', cascade=>true) 

PL/SQL procedure successfully completed. 


SQL> select m.measure 
    2   , m.ts as "TIME" 
    3   , i.entry_time 
    4   , i.exit_time 
    5 from 
    6  intervals i 
    7 inner join 
    8  measures m 
    9  on (m.ts between i.entry_time and i.exit_time) 
10 order by m.ts asc 
11/

1200001 rows selected. 

Elapsed: 00:05:28.54 

SQL> 

Теперь без индекса на МЕР (но по-прежнему показатель на отрезках)

SQL> drop index meas_idx 
    2/

Index dropped. 

SQL> exec dbms_stats.gather_table_stats(user, 'MEASURES', cascade=>true) 

PL/SQL procedure successfully completed. 

SQL> select m.measure 
    2   , m.ts as "TIME" 
    3   , i.entry_time 
    4   , i.exit_time 
    5 from 
    6  intervals i 
    7 inner join 
    8  measures m 
    9  on (m.ts between i.entry_time and i.exit_time) 
10 order by m.ts asc 
11/

1200001 rows selected. 

Elapsed: 00:05:24.81 

SQL> 

Так какая разница сделать параллельный запрос?

SQL> select /*+ parallel (4) */ 
    2   m.measure 
    3   , m.ts as "TIME" 
    4   , i.entry_time 
    5   , i.exit_time 
    6 from 
    7  intervals i 
    8 inner join 
    9  measures m 
10  on (m.ts between i.entry_time and i.exit_time) 
11 order by m.ts asc 
12/

1200001 rows selected. 

Elapsed: 00:02:33.82 


SQL> 

МЕРЫ Заключение

Не большая разница в истекшего времени для различных показателей. Я был немного удивлен тем, что построение индекса MEASURES (TS, MEASURE) привело к полному сканированию таблицы и несколько более медленному времени выполнения. С другой стороны, неудивительно, что работа в параллельном запросе намного быстрее. Поэтому, если у вас есть Enterprise Edition, и у вас есть запасные части для процессоров, использование PQ, безусловно, сократит время, затраченное на то, что оно не изменит стоимость ресурсов (и фактически делает более лот).

INTERVALS тесты

Так что разница может различные индексы на отрезках сделать? В следующих тестах мы сохраним индекс для MEASURES (TS). Прежде всего, мы поместим первичный ключ на оба столбца INTERVALS и заменим его только ограничением на INTERVALS (ENTRY_TIME).

SQL> alter table intervals drop constraint ivl_pk drop index 
    2/

Table altered. 

SQL> alter table intervals add constraint ivl_pk primary key (entry_time) using index 
    2/

Table altered. 

SQL> exec dbms_stats.gather_table_stats(user, 'INTERVALS', cascade=>true) 

PL/SQL procedure successfully completed. 


SQL> select m.measure 
    2   , m.ts as "TIME" 
    3   , i.entry_time 
    4   , i.exit_time 
    5 from 
    6  intervals i 
    7 inner join 
    8  measures m 
    9  on (m.ts between i.entry_time and i.exit_time) 
10 order by m.ts asc 
11/

1200001 rows selected. 

Elapsed: 00:05:38.39 

SQL> 

Наконец без индекса на отрезках при всех

SQL> alter table intervals drop constraint ivl_pk drop index 
    2/

Table altered. 

SQL> exec dbms_stats.gather_table_stats(user, 'INTERVALS', cascade=>true) 

PL/SQL procedure successfully completed. 

SQL> select m.measure 
    2   , m.ts as "TIME" 
    3   , i.entry_time 
    4   , i.exit_time 
    5 from 
    6  intervals i 
    7 inner join 
    8  measures m 
    9  on (m.ts between i.entry_time and i.exit_time) 
10 order by m.ts asc 
11/

1200001 rows selected. 

Elapsed: 00:05:29.15 

SQL> 

ИНТЕРВАЛОВ заключение

Индекс на отрезках делает небольшую разницу. То есть индексирование (ENTRY_TIME, EXIT_TIME) приводит к более быстрому выполнению. Это связано с тем, что он обеспечивает быстрое сканирование полного индекса, а не полное сканирование таблицы. Это было бы более значительным, если бы временное окно, обозначенное INTERVALS, было значительно шире, чем временное окно MEASURES.

Общие выводы

Поскольку мы делаем полную таблицу запросов ни один из индексов существенно не изменилось время выполнения. Поэтому, если у вас есть Enterprise Edition и несколько процессоров, Parallel Query даст вам наилучшие результаты. В противном случае самыми лучшими индексами будут INTERVALS (ENTRY_TIME, EXIT_TIME) и MEASURES (TS). Решение вложенных циклов определенно быстрее, чем параллельный запрос - см. Редактировать 4 ниже.

Если вы работаете против подмножества МЕР (скажем, ценность недели), то наличие индексов будет иметь большее влияние, это, вероятно, что два я рекомендовал в предыдущем пункте, остаются наиболее эффективными,

Последнее наблюдение: я использовал это на стандартном двухъядерном ноутбуке для болота с SGA всего 512 МБ. Но все мои запросы заняли менее шести минут. Если ваш запрос действительно занимает час, то в вашей базе данных есть серьезные проблемы. Хотя это долгое время может быть артефактом перекрывающихся INTERVALS, что может привести к декартовому продукту.

** Редактировать **

Первоначально я включил выход из

SQL> set autotrace traceonly stat exp 

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

Edit 4 (предыдущий редактировать вынимается по причинам пространства)

На третьей попытке я был в состоянии воспроизвести дэ улучшение производительности для решения Quassnoi в.

SQL> set autotrace traceonly stat exp 
SQL> 
SQL> set timing on 
SQL> 
SQL> select 
    2   /*+ LEADING (i) USE_NL(i, m) */ 
    3    m.measure 
    4    , m.ts as "TIME" 
    5    , i.entry_time 
    6    , i.exit_time 
    7 from 
    8  intervals i 
    9 inner join 
10  measures m 
11  on (m.ts between i.entry_time and i.exit_time) 
12 order by m.ts asc 
13/

1200001 rows selected. 

Elapsed: 00:00:18.39 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 974071908 

--------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| Time  | 
--------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |   | 6003K| 257M|  | 973K (1)| 03:14:46 | 
| 1 | SORT ORDER BY    |   | 6003K| 257M| 646M| 973K (1)| 03:14:46 | 
| 2 | NESTED LOOPS    |   |  |  |  |   |   | 
| 3 | NESTED LOOPS    |   | 6003K| 257M|  | 905K (1)| 03:01:06 | 
| 4 |  TABLE ACCESS FULL   | INTERVALS | 2001 | 32016 |  | 2739 (1)| 00:00:33 | 
|* 5 |  INDEX RANGE SCAN   | MEAS_IDX | 60000 |  |  | 161 (1)| 00:00:02 | 
| 6 | TABLE ACCESS BY INDEX ROWID| MEASURES | 3000 | 87000 |  | 451 (1)| 00:00:06 | 
--------------------------------------------------------------------------------------------------- 

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

    5 - access("M"."TS">="I"."ENTRY_TIME" AND "M"."TS"<="I"."EXIT_TIME") 


Statistics 
---------------------------------------------------------- 
     66 recursive calls 
      2 db block gets 
     21743 consistent gets 
     18175 physical reads 
      0 redo size 
    52171689 bytes sent via SQL*Net to client 
    880416 bytes received via SQL*Net from client 
     80002 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      1 sorts (disk) 
    1200001 rows processed 

SQL> 

Так что вложенные петли - это, безусловно, путь.

Полезные уроки упражнения

  1. Запуск диагностических тестов является гораздо более ценны, чем гадать и теоретизирования
  2. Понимание данных имеет решающее значение
  3. Даже с 11g мы еще soemtimes необходимость использовать подсказки для создания оптимизатора в некоторых случаях
+0

Да, моя мысль об этом заключалась в том, что ответом будет сортировка-слияние, и трюк будет заключаться в том, чтобы избежать сортировки таблицы мер, разливающейся на диск. Однако индекс мер (времени, меры) может быть полностью отсканирован, чтобы дать правильный порядок как для сортировки, так и для требуемого порядка, поэтому я удивлен, что это не улучшилось - как был выполнен план выполнения для этого? (Я бы запускал его сам, но я слишком ленив, чтобы создавать исходные наборы данных ... я сказал «ленивый»? Я имел в виду «занят»). –

+0

@ Давид. В плане выполнения был показан ПОЛНЫЙ ТАБЛИЦЫ СКАНИРОВАНИЯ ПО МЕРЫ, когда я использовал этот индекс, который я действительно не понимаю. Если бы я был не так, э-э, * занят * сам, я бы запустил след 10053, чтобы понять, почему. Если я получу возможность, я мог бы еще сделать это, но завтраки помастят, а затем послеобеденную работу. – APC

+1

Да, с тонкими таблицами измерений я не был бы слишком удивлен, если бы оптимизатор применил сканирование индекса и пошел на FTS, что с индексом, в котором есть rowid's ..., что, вероятно, делает индекс дважды размер таблицы и оптимизатор взвешивает сканирование и сортирует таблицу (многоблочный) и считывает индекс (одноблочные чтения в два раза больше размера сегмента). Победа для FTS даже с одним проходом сортировки по данным. –

1

Возможно, существует очень эффективный способ написания этого запроса, если интервалы являются детерминированными, поскольку запрос может быть преобразован в equi-join, что будет подкрепляться более эффективным объединением хэшей.

Например, если интервалы все ежечасно:

ENTRY_TIME   EXIT_TIME 
2000-01-15 09:00:00 2000-01-15 09:59:59 
2000-01-15 10:00:00 2000-01-15 10:59:59 
2000-01-15 11:00:00 2000-01-15 11:59:59 
2000-01-15 12:00:00 2000-01-15 12:59:59 
.... 

Затем соединение может быть записано как:

intervals.entry_time=trunc(measures.time,'HH') 

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

Однако, поскольку у вас есть операция ORDER BY, я думаю, что сортировка-слияние может по-прежнему превзойти ее, поскольку запрос написан прямо сейчас, потому что оптимизатор сортирует меньший набор данных для сортировки-слияния, чем он для хэш-соединения (потому что в последнем случае ему придется сортировать больше столбцов данных). Вы можете обойти это путем структурирования запроса как:

select 
    measures.measure  as measure, 
    measures.time  as time, 
    intervals.entry_time as entry_time, 
    intervals.exit_time as exit_time 
from 
    intervals inner join 
    (select time, measure from measures order by time) measures 
    on intervals.entry_time=trunc(measures.time,'HH') 
/

Это дает оценку стоимости ниже, чем сортировки сливать на моем 10.2.0.4 тест, например, но я бы рассматривать его как немного рискованно.

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

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