Оптимизирующий запрос Oracle, включающий вычисление даты - PullRequest
1 голос
/ 02 июня 2009

База данных

Table1
 Id
 Table2Id

...

Table2
  Id
  StartTime
  Duration  //in hours

Запрос

select * from Table1 join Table2 on Table2Id = Table2.Id 
where starttime < :starttime and starttime + Duration/24 > :endtime

Этот запрос в настоящее время занимает около 2 секунд, что слишком долго. В столбцах идентификаторов есть индекс, а в Start_time + duration / 24 - индекс функции. В Sql Developer план запросов не показывает используемых индексов. Запрос возвращает 475 строк для моего времени начала и окончания теста. Таблица2 имеет ~ 800 тыс. Строк Таблица1 содержит ~ 200 тыс. Строк

Если расчет длительности / 24 удаляется из запроса и заменяется статическим значением, время запроса уменьшается вдвое. Это не приводит к получению точно таких же данных, но наводит меня на мысль, что разделение является дорогостоящим.

Я также протестировал добавление столбца конечного времени в Таблицу 2, которая заполняется (время начала + продолжительность / 24). Столбец был предварительно заполнен с помощью одного обновления, если он будет использоваться в рабочей среде, я заполнил бы его с помощью триггера обновления.

select * from Table1 join Table2 on Table2Id = Table2.Id 
where starttime < :starttime and endtime > :endtime

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

Есть ли способы сделать этот запрос быстрее?

Ответы [ 3 ]

3 голосов
/ 02 июня 2009

Создать индекс функции как во время запуска, так и в выражении starttime + Duration/24:

create index myindex on table2(starttime, starttime + Duration / 24);

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

Также убедитесь, что вы не выполняете неявное преобразование из varchar в дату, гарантируя, что вы передаете DATE в свои переменные связывания.

Попробуйте понизить системный параметр optimizer_index_cost_adj. Я считаю, что по умолчанию установлено значение 100. Попробуйте установить значение 10 и посмотрите, выбран ли ваш индекс.

Рассмотрите возможность разделения таблицы по времени начала.

1 голос
/ 03 июня 2009

У вас есть два критерия с предикатами диапазона (больше / меньше чем). Сканирование диапазона индекса может начинаться в одной точке индекса и заканчиваться в другой.

Для составного индекса времени начала и «Время начала + длительность / 24», поскольку передний столбец - время начала, а предикат - «меньше значения привязки», он начнется с самого левого края индекса (самое раннее время начала) и диапазон сканирования всех строк до точки, где время начала достигает предела. Для каждого из этих совпадений он может оценить вычисленное значение для «Starttime + duration / 24» в индексе по отношению к значению привязки и пропустить или отклонить строку. Я подозреваю, что большинство данных в таблице старые, поэтому у большинства записей старое время старта, и вы в конечном итоге сканируете большую часть индекса.

Для составного индекса «Время начала + длительность / 24» и время начала, поскольку передний столбец является функцией, а предикат «больше значения привязки», он будет начинаться частично через индекс и работать до конца. Для каждого из этих совпадений он может сравнить начальное время индекса со значением привязки и пропустить или отклонить строку. Если переданная конечная дата является недавней, я подозреваю, что это на самом деле связано с гораздо меньшим количеством сканируемого индекса.

Даже без времени начала в качестве второго столбца в индексе существующий индекс на основе функций «Время начала + длительность / 24» все равно должен быть полезен и использоваться. Проверьте план объяснения, чтобы убедиться, что значение привязки является либо датой, либо преобразованной в дату. Если он преобразован, убедитесь, что используется соответствующая маска формата (например, введенное значение '1 / Jun / 09' может быть преобразовано в год 0009, поэтому Oracle увидит условие как очень смягченное и будет склонно не использовать индекс - плюс результат может быть неправильным).

"В Sql Developer план запроса показывает, что индексы не используются." Если индекс не использовался для поиска строк таблицы2, я подозреваю, что оптимизатор думал, что большая часть / вся таблица2 будет возвращена [что очевидно не является ' т, по вашим номерам. Я предполагаю, что это хотя большая часть таблицы 1 будет возвращена, и, следовательно, ни один из ваших предикатов не сделал большую фильтрацию. Как я уже говорил выше, я думаю, что предикат «меньше чем» не является селективным, но «больше чем» должно быть. Посмотрите на план объяснения, особенно значение ROWS, чтобы увидеть, что Oracle думает

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

1 голос
/ 02 июня 2009

Oracle не будет использовать индексы, если селективность предложения where не очень хорошая. Индекс будет использоваться, если количество возвращаемых строк будет составлять некоторый процент от общего числа строк в таблице (процент варьируется, поскольку oracle будет считать стоимость чтения индекса, а также чтения таблиц).

Кроме того, при изменении столбцов индекса в предложении where индекс будет отключен. Например, UPPERCASE (some_index_column) отключит использование индекса для some_index_column. Вот почему время начала + Продолжительность / 24>: время окончания не использует индекс.

Можете ли вы попробовать это

select * from Table1 join Table2 on Table1.Id = Table2.Table1Id 
where starttime < :starttime and starttime  > :endtime - Duration/24

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

...