2012-02-03 2 views
7

Чтобы запросить top-n строк в Oracle, обычно используется ROWNUM. Так что следующий запрос кажется нормально (получает 5 последних платежей):Производительность Oracle ROWNUM

select a.paydate, a.amount 
from (
    select t.paydate, t.amount 
    from payments t 
    where t.some_id = id 
    order by t.paydate desc 
) a 
where rownum <= 5; 

Но для очень больших таблиц, это неэффективно - для меня это работать в течение ~ 10 минут. Так что я пробовал другие вопросы, и я закончил с этим, который работает меньше, чем вторая:

select * 
from (
    select a.*, rownum 
    from (select t.paydate, t.amount 
     from payments t 
     where t.some_id = id 
     order by t.paydate desc) a 
) 
where rownum <= 5; 

Чтобы выяснить, что происходит, я смотрел планы выполнения для каждого запроса. Для первого запроса:

SELECT STATEMENT, GOAL = ALL_ROWS 7 5 175 
COUNT STOPKEY   
VIEW 7 5 175 
TABLE ACCESS BY INDEX ROWID 7 316576866 6331537320 
INDEX FULL SCAN DESCENDING 4 6 

И для второй:

SELECT STATEMENT, GOAL = ALL_ROWS 86 5 175 
COUNT STOPKEY   
VIEW 86 81 2835 
COUNT   
VIEW 86 81 1782 
SORT ORDER BY 86 81 1620 
TABLE ACCESS BY INDEX ROWID 85 81 1620 
INDEX RANGE SCAN 4 81 

Очевидно, что это INDEX FULL SCAN DESCENDING, что делает первый запрос неэффективную для больших таблиц. Но я не могу отличить логику двух запросов, глядя на них. Может ли кто-нибудь объяснить мне логические различия между двумя запросами на человеческом языке?

Заранее благодарен!

+2

id - переменная связывания, нет (должно быть: id?), Если да, какое значение используется (такое же?) – tbone

+2

Я не думаю, что 'rownum', который вы используете для фильтра во второй версии, гарантированно будет таким же, как и в первом; подумайте, что вам нужно либо добавить свой второй запрос и ссылку, либо добавить в запрос запрос 'order by rownum' против' a'? Я сомневаюсь, что это влияет на скорость. –

ответ

3

Прежде всего, как упоминалось в комментарии Алекса, я не уверен, что ваша вторая версия гарантирована на 100%, чтобы дать вам правильные строки - поскольку «средний» блок запроса не имеет явного order by , Oracle не обязан передавать строки до внешнего блока запроса в любом конкретном порядке. Тем не менее, по-видимому, нет какой-либо конкретной причины, что он изменит порядок, в котором строки будут переданы из самого внутреннего блока, поэтому на практике это, вероятно, будет работать.

И поэтому Oracle выбирает другой план для второго запроса - он логически не может применить операцию STOPKEY к самому внутреннему блоку запросов.

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

Во втором случае я считаю, что он не может применить оптимизацию STOPKEY к самому внутреннему блоку из-за дополнительного слоя вложенности. В этом случае полное сканирование индекса станет намного менее привлекательным, так как всегда нужно сканировать весь индекс. Поэтому он выбирает индексный поиск по id (я предполагаю), за которым следует фактическая сортировка по дате. Если заданное значение id соответствует небольшому подмножеству строк, это, вероятно, будет более эффективным, но если вы дадите id, который имеет множество строк, расположенных по всей таблице, я ожидаю, что он станет медленнее, поскольку он будет иметь для доступа и сортировки многих строк.

Итак, я бы предположил, что ваши тесты использовали id (-ы), которые имеют относительно немного строк, которые не очень свежи.Если это будет типичный вариант использования, то второй запрос, вероятно, будет лучше для вас (опять же, с предостережением, что я не уверен, что технически гарантировано создать правильный набор результатов). Но если типичные значения будут иметь больше совпадающих строк и/или более вероятно иметь 5 очень последних строк, тогда первый запрос и план могут быть лучше.

+0

Отличное объяснение! Благодарю. @Alex: кажется, лучше добавить 'order by rownum', поскольку он добавляет« СОРТИРОВАТЬ ЗАКАЗ ПО STOPRKEY »в план exec, тогда как aliasing' rownum' удаляет «COUNT STOPKEY» в плане exec. Но, как вы заметили, я не видел изменений в скорости. – Bazi

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