Скорость выгружаемых запросов в Oracle

Это бесконечная тема для меня, и мне интересно, могу ли я что-то игнорировать. По сути, я использую два типа операторов SQL в приложении:

  • Регулярные запросы с "резервным" лимитом
  • Отсортированные и запрограммированные запросы

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

Случай 1 действительно прост. Мы добавим дополнительный фильтр ROWNUM:

WHERE ...
  AND ROWNUM < ?

Это довольно быстро, так как Oracle CBO учтет этот фильтр для своего плана выполнения и, вероятно, применит операцию FIRST_ROWS (похожую на ту, которая применяется в подсказке /*+FIRST_ROWS*/.

Случай 2, однако это немного сложнее с Oracle, так как нет предложения LIMIT ... OFFSET, как в других СУБД. Поэтому мы вставляем наш "бизнес-запрос" в техническую оболочку как таковую:

SELECT outer.* FROM (
  SELECT * FROM (
    SELECT inner.*, ROWNUM as RNUM, MAX(ROWNUM) OVER(PARTITION BY 1) as TOTAL_ROWS
    FROM (
      [... USER SORTED business query ...]
    ) inner
  ) 
  WHERE ROWNUM < ?
) outer
WHERE outer.RNUM > ?

Обратите внимание, что поле TOTAL_ROWS рассчитано так, чтобы знать, сколько страниц у нас будет, даже не получая все данные. Теперь этот запрос подкачки обычно вполне удовлетворяет. Но время от времени (как я уже говорил, при запросе записей 5M +, возможно, включая неиндексированные поисковые запросы) это выполняется в течение 2-3 минут.

РЕДАКТИРОВАТЬ. Обратите внимание, что потенциальное узкое место не так просто обойти, потому что сортировка должна быть применена до подкачки!

Мне интересно, это современное моделирование LIMIT ... OFFSET, включая TOTAL_ROWS в Oracle, или есть лучшее решение, которое будет быстрее по дизайну, например. используя ROW_NUMBER() функцию окна вместо псевдо-столбца ROWNUM?

Ответ 1

Основная проблема с случаем 2 заключается в том, что во многих случаях весь набор результатов запроса должен быть получен, а затем отсортирован до, первые N строк могут быть возвращены - если только индексы ORDER BY не индексируются и Oracle может использовать индекс, чтобы избежать сортировки. Для сложного запроса и большого набора данных это может занять некоторое время. Однако могут быть некоторые вещи, которые вы можете сделать для повышения скорости:

  • Попробуйте убедиться, что во внутреннем SQL не вызывается никаких функций - они могут получить 5 миллионов раз, чтобы вернуть первые 20 строк. Если вы можете переместить эти вызовы функций во внешний запрос, их будет вызывать меньше.
  • Используйте подсказку FIRST_ROWS_n, чтобы подтолкнуть Oracle к оптимизации за то, что вы никогда не вернете все данные.

EDIT:

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

Ответ 2

Возможно, вы захотите отследить запрос, требующий много времени, и посмотрите на его план объяснения. Скорее всего, узкое место производительности исходит из расчета TOTAL_ROWS. Oracle должен считывать все данные, даже если вы выбираете только одну строку, это общая проблема, с которой сталкиваются все СУРБД с этим типом запроса. Никакая реализация TOTAL_ROWS не обойдется.

Радикальным способом ускорения этого типа запроса является отказ от расчета TOTAL_ROWS. Просто покажите, что есть дополнительные страницы. Нужно ли вашим пользователям действительно знать, что они могут переходить через страницы 52486? Оценка может быть достаточной. Это другое решение, реализованное при поиске по Google, например: оценка количества страниц вместо фактического подсчета их.

Разработка точного и эффективного алгоритма оценки может быть не тривиальным.

Ответ 3

"LIMIT... OFFSET" - это в значительной степени синтаксический сахар. Это может сделать запрос более красивым, но если вам все равно нужно прочитать весь набор данных и отсортировать его и получить строки "50-60", то это будет выполняться.

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

Ответ 4

Он может лучше выполнять два запроса вместо того, чтобы пытаться считать() и возвращать результаты в том же запросе. Oracle может отвечать на count() без какой-либо сортировки или присоединения ко всем таблицам (исключение объединения таблиц на основе объявленных ограничений внешнего ключа). Это то, что мы обычно делаем в нашем приложении. Для важных показателей производительности мы пишем отдельный запрос, который, как мы знаем, вернет правильный счет, поскольку иногда мы можем лучше, чем Oracle.

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