Re[13]: определить позицию строки в запросе
От: Овощ http://www.google.com
Дата: 02.09.09 06:40
Оценка:
Здравствуйте, KRA, Вы писали:

KRA>Запрос получается вида (пишу с ораклиным синтаксисом)


KRA>
KRA>select rn from  (
KRA>select e.*, rownum rn
KRA>from table e
KRA>order by
KRA>  e.field1, e.field2
KRA>) 
KRA>where id = :id
KRA>


KRA>при наличии индексов по полям сортировки даже при 100к может работать с приемлимой скоростью на соответствующем железе (по крайней мере оракл с rownum-ами неплохо работает).


Правильно уже сказали — тут фактическая ошибка. Oracle высчитывает rownum до выполнения order by. Т.е. в общем случае надо делать так (выносить rownum на уровень выше чем order by):

select t.*, rownum
from
(
    select * from table1 order by field1
) t


KRA>После этого, зная номер записи (и расчитав на какой странице она находится), мы можем подчитать только нужную нам страницу запросом вида


KRA>
KRA>select e.* from (
KRA>select e.*, rownum rn
KRA>from table e
KRA>order by
KRA>  e.field1, e.field2
KRA>)
KRA>where rn between :start_index and :end_index
KRA>


Опять же не совсем правильно. О том как работает rownum и как правильно делать серверный пейджинг в оракле есть хорошая статья Ask Tom: On ROWNUM and Limiting Results.

KRA>Опять же в оракле такой запрос не приводит к тому, что читаются все данные целиком. Оракл понимает, что такое rownum и как тут можно оптимизировать.


Также здесь я не уверен, что такое использование значения rownum в конструкции between может быть эффективно оптимизировано. Oracle может построить эффективный план для простых предикатов, подобных ... where rownum < :end_index (и использовать в этом месте такую операцию в плане как COUNT STOPKEY). Впрочем, вероятно и это вполне может хорошо работать.
Re[14]: определить позицию строки в запросе
От: Овощ http://www.google.com
Дата: 02.09.09 07:52
Оценка: 98 (2)
Здравствуйте, MasterZiv, Вы писали:

MZ>Ээээ.. доказательства будут ?


MZ> Оракл понимает, что такое rownum и как тут можно

>> оптимизировать.

MZ>Как можно что-то оптимизировать по rownum, если он генерируется

MZ>в момент создания набора данных ?
MZ>Объясните, я не очень понимаю.

Попробую объяснить как все это происходит в оракле.
Рассмотрим два случая: с наличием индекса для сортировки, и без оного.

Для первного случая создаем простую таблицу:
SQL> create table t1 (i integer primary key);

Для первичного ключа будет создан нужный нам индекс.
Заполняем эту таблицу числами от 1 до 10000000.
SQL> insert into t1 select rownum from dual connect by level <= 10000000;
10000000 rows created.

Собираем статистику по таблице — для оптимизатора:
exec dbms_stats.gather_table_stats(ownname=>user, cascade=>true, tabname=>'t1');

Теперь собственно сам запрос. Для начала рассмотрим только запрос аналогичный конструкции TOP N, т.е. получение первых N записей в порядке сортировки.
Выглядить он будет так (N=100):
select t.i
from
(
    select * from t1 order by i
) t
where rownum <= 100

с планом:
----------------------------------------------------------------------------------
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |              |   100 |  1300 |     3   (0)| 00:00:01 |
|*  1 |  COUNT STOPKEY    |              |       |       |            |          |
|   2 |   VIEW            |              |   100 |  1300 |     3   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN| SYS_C0088663 |  9875K|    47M|     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=100)


План означает следующее. Сортировка была заменена на упорядоченное чтение индекса (INDEX FULL SCAN). Более того строки, последовательно читаемые (в порядке сортировки) из индекса передаются на вход операции COUNT STOPKEY (filter(ROWNUM<=100). Эта операция фактически считает кол-во строк, которые прошли через нее и когда достигается заранее заданное значение (в нашем случае 100), то дальнейшее выполнение запроса останавливается.
Т.е. весь запрос хоть и обращается к таблице с 10000000 записей, но читает лишь первые несколько блоков из индекса и останавливается, когда прочитает ровно то кол-во строк, которые нужно. Но отсюда сразу виден недостаток этого подхода — в нашем случае у нас есть преимущество в том, что мы задали для чтения лишь 100 строк. Если бы нам потребовался скажем миллион строк, то прочитать нужно намного больше, но опять же не всю таблицу/индекс. Но и в реальной жизни это тоже имеет некоторый смысл — в большинстве случаев любой большой список для пользователя будет показываться в виде небольшой части расположенной с начала этого списка, и лишь некоторые из пользователей будут долго и упорно переходить по страницам, чтобы увидеть конец списка.

Превращение вышеприведенного запроса в полноценный запрос с пейджингом ничего интересного не привнесет (выберем скажем страницы по 10 записей и отобразим 9-ую страницу — записи с 91-ой по 100-ую):

select t.* from
(
    select t.i, rownum as rn
    from
    (
        select * from t1 order by i
    ) t
    where rownum <= 100
) t
where t.rn > 90

-----------------------------------------------------------------------------------
| Id  | Operation          | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |              |   100 |  2600 |     3   (0)| 00:00:01 |
|*  1 |  VIEW              |              |   100 |  2600 |     3   (0)| 00:00:01 |
|*  2 |   COUNT STOPKEY    |              |       |       |            |          |
|   3 |    VIEW            |              |   100 |  1300 |     3   (0)| 00:00:01 |
|   4 |     INDEX FULL SCAN| SYS_C0088663 |  9875K|    47M|     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("T"."RN">90)
   2 - filter(ROWNUM<=100)


Фактически запрос выполняется абсолютно точно также как и первый — берется первые 100 записей по индексу, но затем эти 100 записей передаются на вход операции VIEW (filter("T"."RN">90)), которая выполняет самую простую фильтрацию по предикату "RN">90 — для того чтобы не возвращать не нужные записи с номерами меньше 90. Никаких преимуществ (например по чтению с диска) по сравнению с первым запросом здесь нет, разве что кроме того, что на клиент отправляется меньшее количество записей (10 вместо 100). Недостатки такие же как и в первом случае.

Теперь можно рассмотреть случай без использования индекса.
SQL> create table t2 (i integer);
Table created.
SQL> insert into t2 select rownum from dual connect by level <= 10000000;
10000000 rows created.


Запрос с первыми 100 записей (TOP 100).

select t.i
from
(
    select * from t2 order by i
) t
where rownum <= 100

----------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |   100 |  1300 |       | 53658   (5)| 00:10:44 |
|*  1 |  COUNT STOPKEY          |      |       |       |       |            |          |
|   2 |   VIEW                  |      |    10M|   126M|       | 53658   (5)| 00:10:44 |
|*  3 |    SORT ORDER BY STOPKEY|      |    10M|   126M|   390M| 53658   (5)| 00:10:44 |
|   4 |     TABLE ACCESS FULL   | T2   |    10M|   126M|       |  3607   (8)| 00:00:44 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=100)
   3 - filter(ROWNUM<=100)


Здесь уже индекса нет, поэтому приходится читать всю таблицу — TABLE ACCESS FULL T2.
Однако полной сортировки не происходит, ввиду того что Oracle опять распознал использование rownum и понял что ему нужно лишь определенное фиксированное количество записей (100). Происходит это в операции SORT ORDER BY STOPKEY (filter(ROWNUM<=100)). Эта операция выделяет лишь небольшой буфер в памяти (у нас — на 100 элементов) и организует в нем что-то типа очереди с приоритетами. По мере того как будут читаться записи из таблицы, каждая запись будет сравниваться с теми записями, которые уже находятся в буфере, и оцениваться на то, попадает ли она в этот буфер (возможно вытесняя из него другие записи, помещенные в него раньше) или же просто отбрасывает новые строки. В любом случае на выходе этой операции будет ровно 100 нужных нам элементов отсортированных в нужном нам порядке. Это видно по трассировке:

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  COUNT STOPKEY (cr=15208 pr=0 pw=0 time=1992552 us)
    100   VIEW  (cr=15208 pr=0 pw=0 time=1992547 us)
    100    SORT ORDER BY STOPKEY (cr=15208 pr=0 pw=0 time=1992541 us)
10000000     TABLE ACCESS FULL T2 (cr=15208 pr=0 pw=0 time=29 us)

Операция COUNT STOPKEY в данном случае не особо то и нужна, поскольку лишь дублирует функции SORT ORDER BY STOPKEY.
При относительно небольших N такая операция будет значительно менее затратной по ресурсам чем полная сортировка таблицы.

Пейджинг же реализуется здесь точно так же как и в первом случае, со всеми вытекающими недостатками.

select t.* from
(
    select t.i, rownum as rn
    from
    (
        select * from t2 order by i
    ) t
    where rownum <= 100
) t
where t.rn > 90

-----------------------------------------------------------------------------------------
| Id  | Operation                | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |      |   100 |  2600 |       | 53658   (5)| 00:10:44 |
|*  1 |  VIEW                    |      |   100 |  2600 |       | 53658   (5)| 00:10:44 |
|*  2 |   COUNT STOPKEY          |      |       |       |       |            |          |
|   3 |    VIEW                  |      |    10M|   126M|       | 53658   (5)| 00:10:44 |
|*  4 |     SORT ORDER BY STOPKEY|      |    10M|   126M|   390M| 53658   (5)| 00:10:44 |
|   5 |      TABLE ACCESS FULL   | T2   |    10M|   126M|       |  3607   (8)| 00:00:44 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("T"."RN">90)
   2 - filter(ROWNUM<=100)
   4 - filter(ROWNUM<=100)


Некоторые выводы:
1) Для оптимизации необходимы индексы. Поскольку наибольшая скорость будет именно с использованием индексов вместо сортировок.
2) Если нам нужно получить записи с номерами от :start до :end, то критичным является именно номер последней строки (:end), поскольку запрос в общем случае "выполняется" для всех записей с номерами от 1 до :end. Т.е. другими словами, это будет эффективно лишь если мы показываем страницы записей с небольшими номерами. Чем дальше отображаемая страница находится к концу списка страниц тем медленне будет запрос.

Вот как то так...
Re[15]: определить позицию строки в запросе
От: MasterZiv СССР  
Дата: 02.09.09 08:57
Оценка:
Овощ пишет:

> Однако полной сортировки не происходит, ввиду того что Oracle опять

> распознал использование rownum и понял что ему нужно лишь определенное
> фиксированное количество записей (100). Происходит это в операции SORT
> ORDER BY STOPKEY (filter(ROWNUM<=100)). Эта операция выделяет лишь
> небольшой буфер в памяти (у нас — на 100 элементов) и организует в нем
> что-то типа очереди с приоритетами. По мере того как будут читаться
> записи из таблицы, каждая запись будет сравниваться с теми записями,
> которые уже находятся в буфере, и оцениваться на то, попадает ли она в
> этот буфер (возможно вытесняя из него другие записи, помещенные в него
> раньше) или же просто отбрасывает новые строки. В любом случае на выходе
> этой операции будет ровно 100 нужных нам элементов отсортированных в
> нужном нам порядке. Это видно по трассировке:

"Однако полной сортировки не происходит". Ну да, читается вся таблица,
а "полной сортировки не происходит".
Это -- просто специальный вид алгоритма сортировки, с урезанным
результатом, она дешевле полной сортировки, да, но всё равно это --
обработка всей таблицы/набора. Вы же сами написали:

"каждая запись будет сравниваться с теми записями,
которые уже находятся в буфере"

> При относительно небольших N такая операция будет значительно менее

> затратной по ресурсам чем полная сортировка таблицы.

Основные затраты сервера при формировании наборов -- чтение данных,
чтение строк. Да, сортировка особенно больших наборов накладна,
но это -- не то, что нужно минимизировать при работе с БД.
Нужно минимизировать кол-во чтений записей. Если записей
мало, сортировки не оказывают уже какого-то влияния на производительность.


> Некоторые выводы:

> 1) Для оптимизации необходимы индексы. Поскольку наибольшая скорость
> будет именно с использованием индексов вместо сортировок.

Подчеркну мысль: индексы для ORDER BY.

Теперь покритикую: если есть критерии поиска, индексы должны
использоваться для поиска, а не для ORDER BY. Тогда ваша мысль не
работает.
Posted via RSDN NNTP Server 2.1 beta
Re[14]: определить позицию строки в запросе
От: MasterZiv СССР  
Дата: 02.09.09 09:01
Оценка:
Овощ пишет:

> Также здесь я не уверен, что такое использование значения rownum в

> конструкции between может быть эффективно оптимизировано. Oracle может
> построить эффективный план для простых предикатов, подобных ... where
> rownum < :end_index (и использовать в этом месте такую операцию в плане
> как COUNT STOPKEY). Впрочем, вероятно и это вполне может хорошо работать.

Дело не в том, какая конструкция, between или нет. Я уверен, что between
уж как-нибудь оракл умеет обрабатывать правильно (он представляется
как начало и конец диапазона, field >= :beg and field <= :end),
а вот дело-то в том, что rownum не известен ДО выполнения запроса,
поэтому на его основе нельзя что-то оптимизировать в принципе.

Вообще rownum -- дебильная штука. Хотя и прикольная иногда.
Posted via RSDN NNTP Server 2.1 beta
Re[15]: определить позицию строки в запросе
От: Овощ http://www.google.com
Дата: 02.09.09 10:01
Оценка: +1
Здравствуйте, MasterZiv, Вы писали:

Только я не понял, что именно вы предлагаете взамен?
Увидел только это:

MZ>Да, например. Можно по любому запросу выводить, скажем, только первые 100

MZ>записей, и если есть ещё, писать, что часть данных были отброшены.

MZ>Больше 100-1000 записей никто читать не будет. Если это не выгрузка

MZ>данных куда-то для экспорта, или не отчёт какой-то для складирования, бесполезно
MZ>выводить более 100-1000 записей. Конкретные 100-1000 конечно варьируются в
MZ>зависимости от специфики приложения, но в общем идея такая.

MZ>>Обычно пользователи хотят задавать любые критерии и

MZ>>иметь пейджинг.

MZ>Нахрена пейджинг нужен, если ты получаешь 10-100 нужных тебе записей ?


Если у нас есть "первые"/"последние" то видимо есть какой-то порядок (который наверняка задается через order by).
Если нам нужны только 100 записей, то видимо у нас есть ограничение на выборку в виде TOP/rownum.

Получается, что для этого нам надо использовать точно такую же конструкцию из rownum + order by (или top + order by), которая вам не понравилась.
И в конечно счете все получается абсолютно точно также, как и в случаях, которые я расписал выше.
Разницы между пейджинг/непейджинг здесь нет никакой, разве что в микродеталях реализации (зададим жестко, что номер последней строки для выборки не должен быть больше 100 и все — нет пейджинга, а есть предложенный вами вариант).
Не вижу разницы. Она здесь есть?

Путь для "оптимизации" я вижу только один — выбирать не первые 100, а случайные 100. Тогда можно отказаться от сортировки.
Но обычно сортировка результатов поиска просто обязательна в любом приложении (да и само поле сортировки может меняться).

Варинт же с отображением пользователю вместо результатов выборки сообщения вида "Найдено 100000 записей удовлетворяющих критериям поиска. Это слишком много для отображения. Уточните критерии поиска." вообще мне кажется неприемлемым.
Re[14]: определить позицию строки в запросе
От: KRA Украина  
Дата: 02.09.09 10:06
Оценка: +1
Здравствуйте, Овощ, Вы писали:

О>Правильно уже сказали — тут фактическая ошибка. Oracle высчитывает rownum до выполнения order by. Т.е. в общем случае надо делать так (выносить rownum на уровень выше чем order by):


Спасибо за уточнения. Я привёл приблизительный вид запросов, т.к. это не главная мысль. Главная мысль такая, что оптимизатор oracle отлично понимает семантику rownum.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.