Как я могу ускорить row_number в Oracle?

У меня есть запрос SQL, который выглядит примерно так:

SELECT * FROM(
    SELECT
        ...,
        row_number() OVER(ORDER BY ID) rn
    FROM
        ...
) WHERE rn between :start and :end

По сути, это часть ORDER BY, которая замедляет работу. Если бы я удалил его, стоимость EXPLAIN уменьшилась бы на порядок (более чем в 1000 раз). Я пробовал это:

SELECT 
    ...
FROM
    ...
WHERE
    rownum between :start and :end

Но это не дает правильных результатов. Есть ли простой способ ускорить это? Или мне придется потратить еще немного времени на инструмент EXPLAIN?


person Jason Baker    schedule 05.05.2009    source источник


Ответы (5)


ROW_NUMBER совершенно неэффективен в Oracle.

Подробнее о производительности см. в статье в моем блоге:

Для вашего конкретного запроса я бы рекомендовал вам заменить его на ROWNUM и убедиться, что используется индекс:

SELECT  *
FROM    (
        SELECT  /*+ INDEX_ASC(t index_on_column) NOPARALLEL_INDEX(t index_on_column) */
                t.*, ROWNUM AS rn
        FROM    table t
        ORDER BY
                column
        )
WHERE rn >= :start
      AND rownum <= :end - :start + 1

Этот запрос будет использовать COUNT STOPKEY

Также либо убедитесь, что column не допускает значение NULL, либо добавьте условие WHERE column IS NOT NULL.

В противном случае индекс нельзя будет использовать для извлечения всех значений.

Обратите внимание, что вы не можете использовать ROWNUM BETWEEN :start and :end без подзапроса.

ROWNUM всегда назначается последним и проверяется последним, поэтому ROWNUM всегда идут по порядку без пропусков.

Если вы используете ROWNUM BETWEEN 10 and 20, первая строка, удовлетворяющая всем остальным условиям, станет кандидатом на возврат, временно получит ROWNUM = 1 и не пройдет тест ROWNUM BETWEEN 10 AND 20.

Тогда следующая строка будет кандидатом, назначенным с ROWNUM = 1 и неудачным, и т. д., поэтому, наконец, строки не будут возвращены вообще.

Это можно обойти, поместив ROWNUM в подзапрос.

person Quassnoi    schedule 06.05.2009
comment
Работает как шарм. Однако подсказки оптимизатора, похоже, не имели заметного значения. - person Jason Baker; 06.05.2009
comment
Это означает, что CBO был достаточно умен, чтобы подобрать индексы. На самом деле здесь имело значение ROWNUM, а не ROW_NUMBER. - person Quassnoi; 06.05.2009
comment
Но я бы все же оставил подсказки или создал СХЕМУ, на случай, если СВО передумает :) - person Quassnoi; 06.05.2009
comment
К вашему сведению, я еще не пробовал это с обоими, но если я попробую этот запрос с FIRST_ROWS вместо INDEX_ASC и NOPARALLEL_INDEX, стоимость объяснения увеличится с ~ 25 000 до 8 и будет выполняться довольно близко к постоянному времени (я могу получить все записи так же быстро, как один из них), так что мне, возможно, даже не нужно больше листать страницы. Я, вероятно, все еще буду использовать пейджинг в случае внезапных всплесков количества записей. - person Jason Baker; 07.05.2009
comment
+1, но у меня возникли некоторые трудности с его работой. Мне пришлось использовать последний пример (с двумя подзапросами) из упомянутого сообщения в блоге explainextended.com/2009/05/06/oracle-row_number-vs-rownum - person Frosty Z; 12.03.2012
comment
Это неправильно, ROWNUM нельзя использовать вместе с ORDER BY, последовательность ROWNUM генерируется перед ORDER BY. - person Rafael Piccolo; 19.07.2019
comment
@RafaelPiccolo: из документов: Если вы встроите предложение ORDER BY в подзапрос и поместите условие ROWNUM в запрос верхнего уровня, вы можете принудительно применить условие ROWNUM после упорядочения строк. Это именно то, что я делаю здесь. - person Quassnoi; 19.07.2019
comment
@Quassnoi не совсем, внешний выбор просто фильтрует результаты. Внутренний выбор имеет как Rownum , так и Order By. Чтобы это работало, требуется 3 уровня выбора: один для применения порядка, внешний выбор для применения rownum (как указано в документах) и еще один внешний выбор для фильтрации rownum. - person Rafael Piccolo; 21.07.2019

Для меня это похоже на запрос на разбивку на страницы.

Из этой статьи ASKTOM (около 90% вниз по странице):

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

Кроме того, ваши запросы далеко не одинаковы, поэтому я не уверен, в чем преимущество сравнения затрат одного с другим.

person David    schedule 05.05.2009
comment
На самом деле, эта статья помогла мне написать запрос. Однако я не заметил часть заказа по уникальным идентификаторам. Также есть подсказка оптимизатора запросов, которую я пропустил. Завтра на работе попробую! - person Jason Baker; 06.05.2009
comment
;) показалось знакомым. first_rows может быть удивительным с запросами на разбивку на страницы. - person David; 06.05.2009
comment
Благодаря этому и совету Quassnoi мой запрос сократился почти до постоянного времени! Я хотел бы выбрать два ответа. :-( - person Jason Baker; 07.05.2009

Проиндексирован ли ваш столбец ORDER BY? Если нет, то это хорошее место для начала.

person Gandalf    schedule 05.05.2009
comment
На самом деле это не так. Но изменение его на строку, проиндексированную IS, не помогает. Но спасибо за очевидное предложение. :-) - person Jason Baker; 06.05.2009
comment
Индекс поможет улучшить ORDER BY только в том случае, если путь доступа может использовать этот индекс (т. е. вы просматриваете диапазон идентификаторов). - person Dave Costa; 06.05.2009

Часть проблемы заключается в том, насколько велик промежуток между «началом» и «концом» и где они «живут». Допустим, у вас есть миллион строк в таблице, и вам нужны строки с 567 890 по 567 900, тогда вам придется смириться с тем фактом, что вам нужно будет просмотреть всю таблицу, почти все это отсортировать по идентификатору, и определите, какие строки попадают в этот диапазон.

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

Это также не то, с чем может помочь индекс. Индекс задает порядок, но в лучшем случае это дает вам возможность с чего-то начать, а затем вы продолжаете чтение, пока не дойдете до 567 900-й записи.

Если вы показываете конечному пользователю по 10 элементов за раз, возможно, стоит взять первые 100 из БД, а затем приложение разбивает эти 100 на десять фрагментов.

person Gary Myers    schedule 05.05.2009
comment
Это звучит уместно. Я действительно извлекаю около 15 000 записей из ~ 2 миллионов записей. Мы ограничены во времени, которое может занять запрос, а одновременное извлечение всех 15 000 записей вызывало тайм-ауты. Таким образом, я думал, что просмотр результатов предотвратит это. Я полагаю, это просто означает, что мне придется пройти через бюрократический кошмар с просьбой о более длительном тайм-ауте. - person Jason Baker; 06.05.2009
comment
Надеюсь, вы не отправляете пользователю 15 000 строк! - person John Saunders; 06.05.2009

Проводите больше времени с инструментом EXPLAIN PLAN. Если вы видите TABLE SCAN, вам нужно изменить свой запрос.

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

person duffymo    schedule 05.05.2009
comment
Это нумерация страниц. И это, по сути, то, что запрос делает, по крайней мере, с пейджингом. Я только что удалил остальную часть запроса (главным образом потому, что он нетривиален). Все многоточия там, где я вырезал материал для краткости. - person Jason Baker; 06.05.2009