Подводя итог: ваш запрос выполняется с полным набором ИЗМЕРЕНИЙ. Он сопоставляет время каждой записи MEASURES с записью INTERVALS. Если окно времен, охватываемое INTERVALS, примерно аналогично окну, охватываемому MEASURES, тогда ваш запрос также выполняется с полным набором INTERVALS, в противном случае он работает с подмножеством.
Почему это так, потому что это уменьшает ваши возможности для настройки, так как полное сканирование таблицы, вероятно, будет самым быстрым способом получения всех строк. Поэтому, если в ваших реальных таблицах MEASURES или INTERVALS столбцов гораздо больше, чем вы нам предоставляете, маловероятно, что какие-либо индексы дадут много преимуществ.
Возможные стратегии:
- нет индексов вообще
- индекс по ИЗМЕРЕНИЯМ (ВРЕМЯ, ИЗМЕРЕНИЕ)
- индекс по ИЗМЕРЕНИЯМ (ВРЕМЯ)
- нет указателей по ИЗМЕРЕНИЯМ
- индекс по INTERVALS (ENTRY_TIME, EXIT_TIME)
- индекс ИНТЕРВАЛОВ (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 В моих тестовых данных я предположил, что записи INTERVAL не перекрываются. Это имеет важное следствие: запись 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>
МЕРЫ тесты
Теперь давайте создадим уникальный индекс для INTERVALS (ENTRY_TIME, EXIT_TIME) и опробуем различные стратегии индексации для MEASURES. Во-первых, только индекс 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 и MEASURE
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>
Теперь без индекса на MEASURES (но все еще индекс на INTERVALS)
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 определенно сократит затраченное время, хотя это не изменит затрат на ресурсы (и на самом деле lot больше сортировки).
ИНТЕРВАЛЫ тесты
Так, что могут изменить различные индексы на ИНТЕРВАЛАХ? В следующих тестах мы сохраним индекс на 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) приводит к более быстрому выполнению. Это связано с тем, что оно обеспечивает быстрое полное сканирование индекса, а не полное сканирование таблицы. Это было бы более значительным, если бы временной интервал, очерченный ИНТЕРВАЛОМ, был значительно шире, чем у МЕР.
Общие выводы
Поскольку мы выполняем запросы к полной таблице, ни один из индексов существенно не изменил время выполнения. Так что если у вас есть Enterprise Edition и несколько процессоров, Parallel Query даст вам лучшие результаты. В противном случае самыми лучшими индексами будут INTERVALS (ENTRY_TIME, EXIT_TIME) и MEASURES (TS). Решение Nested Loops определенно быстрее, чем Parallel Query - см. Edit 4 ниже.
Если вы работали с подмножеством ИЗМЕРЕНИЙ (скажем, за неделю), то наличие индексов имело бы большее влияние. Вероятно, что два, которые я рекомендовал в предыдущем абзаце, останутся наиболее эффективными,
Последнее наблюдение: я запустил его на стандартном двухъядерном ноутбуке с 5 Гбайт SGA. Все же все мои запросы заняли меньше шести минут. Если ваш запрос действительно занимает час, то у вашей базы данных есть серьезные проблемы. Хотя это длительное время работы может быть артефактом наложения ИНТЕРВАЛОВ, что может привести к декартовому произведению.
** Редактировать **
Изначально я включил вывод из
SQL> set autotrace traceonly stat exp
Но увы ТАК строго усек мой пост. Таким образом, я переписал это, но без выполнения или статистики. Те, кто хочет подтвердить мои выводы, должны будут выполнить запросы к ним.
Редактировать 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>
Так что Nested Loops - определенно лучший способ.
Полезные уроки из упражнения
- Выполнение диагностических тестов - это гораздо больше
ценнее, чем гадать и
теоретизирования
- Понимание данных имеет решающее значение
- Даже с 11g мы все еще время от времени
нужно использовать подсказки, чтобы подтолкнуть
оптимизатор в некоторых случаях