Как стать автором
Обновить

Комментарии 87

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

Это не существенно. Если вы знаете, что в вашем конкретном случае они появляются не по порядку, вы можете использовать поле date. Всё абсолютно точно так же, date < ?last_seen, order by date desc

НЛО прилетело и опубликовало эту надпись здесь
date здесь как пример любого поля по которому отсортирована выборка, т.е. не зависимо от порядка добавления вывод будет верным.
НЛО прилетело и опубликовало эту надпись здесь
И как же изоляция может привести к такому чудесному результату?
Спойлер
Да никак.
НЛО прилетело и опубликовало эту надпись здесь
И? Тут чистая видимость транзакции — и, внезапно, никакого нарушения порядка вставки!
Смотрите, ваш пример
-- табличка, создаем сразу
use tempdb;
drop table if exists books;
create table books (id int not null identity primary key, val int not null, dt datetime default sysdatetime());


-- сессия 1
-- вот от этой штуки зависит результат
set transaction isolation level read committed;
begin tran;
insert	into books(val) values (1);
waitfor delay '00:00:30';
commit;


-- сессия 2
-- уровень изоляции тот же, что в скрипте 1 для однозначности, например
set transaction isolation level read committed;
begin tran;
insert	into books(val) values (2);
commit;


-- сессия 3
-- уровень изоляции тот же, что в скрипте 1 для однозначности, например
set transaction isolation level read committed;
select * from books;


В зависимости от уровней изоляции законно получаем разные эффекты, но всегда, абсолютно всегда сервером гарантируется, что более ранняя вставка имеет меньший identity. Если какие-то записи чтение/вставку не блокируют (например, при read uncommitted/RCSI), то никакого нарушения нет, а есть только в чистом виде эффекты изоляции. С тем же успехом можно было, например, заблокировать запись и делать широкие выводы вида «таракану оторвали ноги — таракан не слышит».
НЛО прилетело и опубликовало эту надпись здесь
Для внешнего наблюдателя (txn3) из моего примера сначала появится запись из txn2
Во-первых, абсолютно необязательно. Про уровни изоляции я не просто так обращал внимание. Обычный уровень RC и любой выше, как в моем примере, покажет в примере сразу две записи, и только когда завершится вторая сессия. А Read Uncommitted aka грязное чтение — и вовсе по мере появления незакоммиченных записей. А вот с RCSI действительно будет сначала результат второй транзакции, затем — обеих. С верным порядком ключей — сначала более ранняя вставка и никак иначе, ни при каком варианте!
Во-вторых, опять же, это ровно никакого отношения к порядку ключей относительно порядка вставки не имеет, могу лишь повторить повторить:
Тут чистая видимость транзакции — и, внезапно, никакого нарушения порядка вставки!
Вещь эта, конечно, базовая, но базовая вообще безотносительно автоинкремента, уберите автоинкремент — и абсолютно ничего в примере не изменится.

А вот выше вы утверждали:
Вы же в курсе, да, что автоинкрементальные ID не обязаны появляться в БД в строгом порядке? И запись под номером 9 может появиться после записи под номером 11?
Это, простите, просто дичь.
НЛО прилетело и опубликовало эту надпись здесь
Очень интересно. И как же результат txn1, недостигшей еще COMMIT, будет виден другой транзакции с уровнем READ COMMITTED?
У меня вообще-то написано ровно обратное, причем вы еще и окончание предложения снесли. Наверно, случайно так получилось:
Обычный уровень RC и любой выше, как в моем примере, покажет в примере сразу две записи, и только когда завершится вторая сессия
Еще раз: при read committed результат будет виден извне только сразу для двоих записей. Сразу для двоих, т.е. одной записью из первой сессии выхлопа вообще не будет, понимаете? А случится это только тогда, когда обе транзакции из вашего примера зафиксируются.
PS. READ UNCOMMITTED это вообще хак, и далеко не везде он поддерживается
Не знаю, что такое хак в лично вашем определении, но то, что грязное чтение поддерживается не везде, опять же ничего не меняет.

Вы вообще чрезвычайно легко даете определения, категорически противоречащие общепринятым, чего стоит одно это:
Под автоинкрементом обычно подразумевается, что новой транзакции будет выдан номер больший, чем последний, использованный до этого
Автоинкремент — это номер транзакции? Да, а номер транзакции — это что такое, вы забыли определить? И выводите из этого дальше сапоги всмятку. На самом деле автоикремент — это значение столбца, автозаполняемого монотонно данными с заданным приращением в порядке вставки. Вот вам, например, по-русски, ознакомьтесь: docs.microsoft.com/ru-ru/sql/t-sql/statements/create-table-transact-sql-identity-property?view=sql-server-ver15
НЛО прилетело и опубликовало эту надпись здесь
А почему вы всегда настолько перевираете мои слова? Опять само так получилось? Я ведь опять ничего подобного не писал. А писал я про ваш пример, который вы срисовали откуда-то, не поняв, что он значит. А значит он, что пока есть хоть одна блокировка, несовместимая по чтению с выбранной на выбранном уровне изоляции (а читать третья сессия хочет всю таблицу, а не диапазон ключей) — читающая на обычном уровне RC будет ждать, именно потому что не закоммичена запись, которую требуется прочитать. Она даже называется Read Committed именно поэтому. Я же вам готовый скрипты дал, просто попробуйте их выполнить. И да, в боевых условиях при этих же вводных будет ровно тот же эффект.
Ну прочтите вы хоть что-то по теме, по которой собрались спорить, чтобы понять хоть этот абзац. Ну хотя бы про RC, что ли. Вы же даже логического смысла блокировок не понимаете, зато смело несете в массы свои определения типа
Это просто определение того, что значит «появиться раньше/позже» с точки зрения наблюдателя системы

А если там несколько записей с одним date, и страница закончилась в середине последовательности?

Под автоинкрементом обычно подразумевается, что значения будут монотонно возрастать. Другое дело, что можно самому установить конкретное значение и этот атрибут уже не будет возрастать монотонно (или вообще использовать рандомный uuid), но для этого мы и должны указывать явную сортировку, а наш ключ, покрыть индексом с такой же сортировкой как и в order by. Поправьте, если неправильно понял ваш вопрос
НЛО прилетело и опубликовало эту надпись здесь
Нет никаких транзакций с номер 10. Есть автоинкрементный ключ вставляемой записи, и он получает значение строго на момент вставки, и при этом строго возрастает. Т.е. ранее вставленная строка всегда имеет значение меньшее, чем вставленная позднее. Это все, что требуется знать для понимания ситуации. Никакие другие факторы, включая время начала транзакции, не меняют вообще ничего. Даже откаченная транзакция просто приведет к пропуску значения, что ничего не меняет в сказанном выше. Так что
Вы же в курсе, да, что автоинкрементальные ID не обязаны появляться в БД в строгом порядке? И запись под номером 9 может появиться после записи под номером 11?
Оба утверждения ложны: автоинкрементные столбцы появляются всегда в строго монотонном порядке (если исключить зацикливание в некоторых СУБД) и запись со значением 9 не может появиться после записи 11 (в случае положительного приращения и с тем же исключением, что и выше). Ну, или вы имели в виду написать совсем не то, что написали, дав свое определение понятия упорядочивания по времени (что, видимо, следует из комментов).
НЛО прилетело и опубликовало эту надпись здесь
Значит B появился позже A
Не значит.
Читайте про изоляцию транзакций — именно там написано, что и почему вышеприведенное значит.
НЛО прилетело и опубликовало эту надпись здесь
а почему вы просто не используете оставляете открытыми scrollable курсоры раз уж у вас Java? и закрывать их по таймауту. можно же даже двунаправленные.

Курсоры не явы драйвер их создает и "держит" на БД.
Кроме то-го что это дорого в плане ресурсов само БД это еще головная боль с закрытием этих курсоров в вашем коде- это то что вы не хотите на самом деле иметь в вашем сервисе.

А в чём вы тут видите проблему? Если у вас в ТЗ указано условие, что данные надо сортировать по ID или по другому полю (которое укажет пользователь), то какая разница в каком реально порядке эти записи попали в базу? Значение имеет только то, что увидит пользователь. А при использовании сортировки он всегда увидит данные в строгом порядке.
НЛО прилетело и опубликовало эту надпись здесь

Да, разновидность этой проблемы (добавление данных после начала "листания") в статье уже указана. Решать её надо другими методами, которые зависят от критичности этой проблемы. Например можно с каждой страницей возвращать некий ETag, который сможет показать клиенту, что данные, с момента начала листинга, изменились.

НЛО прилетело и опубликовало эту надпись здесь

Само по себе значение ETag не так важно. Важен факт его изменения. А это несколько проще реализовать. Например хранить в отдельной таблице (или даже в чём-то вроде Redis) некий uuid или просто автоинкремент, генерируемый при каждом изменении данных.

НЛО прилетело и опубликовало эту надпись здесь
А что делать предлагается в случае, если etag поменялся? В активной системе новые записи могут постоянно потоком добавляться.

Нуу, тут можно книгу написать по этой теме. Всё зависит от требований к системе и имеющихся ресурсов. Варианты навскидку:


  • ничего не делаем, продолжаем листать;
  • перечитываем всё с начала в бесконечном цикле, пока не успеем всё прочитать в промежуток между изменениями;
  • продолжаем листать дальше, а потом запрашиваем с сервера историю изменений за время пока листали, и с её помощью фиксим полученный список;
  • и т.д. и т.п
В посте не хватает примеров, как быть, если надо выбирать данные с разной сортировкой. К примеру довольно часто надо выбирать от новых к старым записям и по рейтингу. Если первое не вызывает затруднений, тут опираемся на автокриментируемый индекс, то с рейтингом возникают проблемы — рейтинг не уникален по всей таблице.
Если я вас правильно понял, то составной индекс должен покрыть этот кейс. Статья, как я упомянул, больше обзорная (манифест), описание подхода, его сильных и слабых сторон, без фанатизма. Вообще говоря, автор примерно в 3х статьях описывает общий подход как построить такие запросы, начиная от pipelined order by и взаимодействии с ним запросов top-n, до конкретных реализаций с некоторыми замерами производительности. Если этот перевод многим зайдет, то постараемся выложить переводы оставшихся статей с пояснениями и некоторыми дополнительными замерами

Сосздание индексов на все случаи жизни — так себе решение, тк увеличивает нагрузку на сервер БД при вставке/модификации данных. Палка о 2-х концах, так сказать. К тому же это не сработает а случае join-ов.
Как правильно заметил delimer, метод не будет работать в случае сортировки по неуникальному полю, но идея хороша, хоть и для частных случаев.

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

Мое лично отношение к тем, кто выдвигает такие идеи — чтоб у вас, умников, книжки были исключительно с инфинити-скролом. Чтобы для того, чтобы продолжить читать с 300-й страницы, нужно было пролистать предыдущие 299.
Веб страница или экран приложения со списком записей, это не книга и ее не читают от корки до корки. Аналогия неуместна.
А элеткронная книга — это не «экран приложения со списком записей»?
А что в электронной книге будет элементом списка записей?
Страница текста (например в PDF).
Сразу возражу, то что они выводятся по одной за раз никак не влияет на механику постраничной навигации.
Веб-страницы, выводящие списки с традиционной постраничной навигацией по номерам страниц (например, тут), в подавляющем большинстве случаев не имеют возможности мгновленно перехода к произвольной странице, так как содержат лишь малую часть ссылок: 1, 2, 3 [пробел, многоточние] N. Все ссылки просто на страницу не поместятся. Так что ее работа мало чем отличается от последовательного скроллинга, а проблем с производительностью такой способ создает много.
К тому же содержимое книги постоянно, а списки из БД могут меняться. их элемены могут добавляться/удаляться, поэтому что на какой странице находится не так уж и важно по большому счету. Никто такие списки не просматривает, начиная со страницы M. Аналогия с книгами неуместна, потому что списки на веб-страницах в общем случае не являются книгами, а не наоборот. К ним другие требования и возможности у них другие.

Ссылки могут быть вида 1, 2, 10, 11, 100, 400, 509, 510. И это реально удобно в использовании. Но это уже явно не последовательный скроллинг.
Могут то могут, но что-то не видел я такого. Хорошо, допустим вам нужна страница 200, как до нее добраться по вашему примеру? А главное зачем? Что такого конкретного может быть на 200 странице, чтобы была необходимость прямого перехода туда?
Через сотую, где будет уже 100, 101, 102, 200.

Удобная вещь, когда надо просматривать большое количество данных, но выборочно и не целиком.
И я бы посмотрел как они предложат уместить ссылки на каждую из 100500 страниц. А вариант с многоточнием посередине чем принципиально отличается от последовательного просмотра с точки зрения UX?
Указание номера страницы для щелчка — в любом случае плохое решение при разработке UI (прим.: мнение автора статьи).

Можно чуть подробнее раскрыть этот момент, почему «плохо»? В итоге мы получаем стрелочки <- -> у которых «под капотом» скрывается id последнего отображенной сущности на странице?
НЛО прилетело и опубликовало эту надпись здесь
Эх! Я успел обновить свой первый вопрос. Повторюсь, почему, с точки зрения UI, решение с номерными страницами – плохо?
НЛО прилетело и опубликовало эту надпись здесь
Спасибо!
НЛО прилетело и опубликовало эту надпись здесь
  1. Для получения номера последней страницы нужно выбрать и посчитать все записи. Уже при миллионах записей это работает никак. Для современных СУБД миллиард записей — нормальная ситуация. Даже для того, чтобы убедиться что страниц "очень много" (и можно забить на нумерацию) нужен достаточно тяжёлый запрос.
  2. Содержимое N-й страницы зависит от всех предыдущих и потенциально разное при каждом селекте.
  3. Ну и как правильно указали в статье — для номерной страницы часто используют offset, который в лучшем случае линейно зависит от номера страницы.
AND id < ?last_seen_id
Это для всех типов ID, или строго Integer? А то там может быть и истинно рандомный GUID.
НЛО прилетело и опубликовало эту надпись здесь
на статических данных прокатит
Не, ну это не спортивно — на статических проблема внезапного появления записи даже и не возникнет.
Автор забыл (а может и специально умолчал) про важное ограничение для этого способа. А именно то, что поле (или поля), используемое для паджинации по ключу, должно быть «уникальным» и обязательно указываться на первом месте с списке полей используемых для сортировки.
Такие ограничения очень сильно уменьшает возможности использования этого метода.
НЛО прилетело и опубликовало эту надпись здесь
Часто «уникальное» поле и поле для сортировки — это разные поля. Например часто сортировать надо по дате создания/обновления записи. И что бы такое поле было ещё и уникальным — это надо заморочится в самом начале разработки (менять старые данные не всегда допустимо).
НЛО прилетело и опубликовало эту надпись здесь
Да, индекс по паре (sort_column, unique_column) и использование его для сортировки при паджинации — это подходящее решение. Но надо понимать, что оно увеличивает размер индексов (особенно если надо уметь сортировать по разным полям).
НЛО прилетело и опубликовало эту надпись здесь

В MS SQL часто по подходящему уникальному полю есть кластеризованный индекс, а значит его значения есть в каждом некластеризованном. В этом случае на самом деле индекс (sort_column) и (sort_column, unique_column) на уровне движка БД тупо одинаковый.
PS: Для Oracle и PostgeSQL это не так.

А зачем постраничная выборка?

Чем сложнее механизмы выборки данных, тем сложнее пользователю понять правила начитки и ориентироваться со списком.
В частности, как замечено в статье, в случае составных индексов, сортировок и фильтров, сложные механизмы механизмы начинают лажаться. Пользователь теряется.

При разработке под DOS постраничная выборка была нужна т.к. на экране список был со статической таблицей из пары десятков строк и я все еще помню приемы, которые использовал в ранних 90х.

При переходе под Винды было круто делать постраничную выборку но скролируемый список с фильтром был также в ходу — толстый клиент позволял закачивать большие объемы в приложение.
С добавлением ограничения на кол-во строк, фильтр с сортировкой стал превалировать и мои пользователи стали отходить от постраничной выборки.

С переходом на веб-приложения в последние 10 лет в контролах используем исключительно:
1. ограничение на размер возвращаемых записей (200-500)
2. фильтр по всем колонкам (в некоторых колонках multi-select из списка возможных) и сортировку.

Значительно упрощает обращение к базе, понимание пользователем настроек выборки, скорость просмотра записей (грид без постраничной выборки).
А как без нее? Всю базу сразу вываливать?) У бесконечного скроллинга под капотом тоже постраничная выборка
А как без нее? Всю базу сразу вываливать?
Ограничивать выборку и делать удобную фильтртацию по клонокам.
Как я писал выше «SELECT TOP 500 ...»
Если программист вываливает пользователю больше 1000 записей одновременно, то нужно пересмотреть дизайн грида.
А, то есть весь список никогда не показывать, а показывать всегда, допустим, MAX 500 элементов и выводить общее количество, чтоб пользователь понимал, что это не все и искал нужные ему элементы списка поиском?
P.S. так то почему нет, достойный способ, но не подойдет для краулеров, котороым нужно проиндексировать все данные.
Классно! Спасибо, возьму на заметку.
С точки зрения юзабилити например мне нужно посмотреть какую-нибудь прошлую запись, и по моим прикидкам она должна быть где-нибудь на 15-й странице. Скроллом или переходом по одной странице искать замучаюсь, фильтра может и не быть, а если он и есть, то порой проще тыкнуть на страницу 15, если не угадал, то на страницу 14 или 16, чем заморачиваться с уточняющими условиями.

Интересно, что 1С с списками в экранных формах работает именно так — не через offset, а через top N where "записи ниже последней". Это и по причинам прозводительности и по причинам того, что 1С работает с 5 разными СУБД (часто используется общий функциональный минимум).
При этом, кстати, корректно обрабатывается сортировка по нескольким полям при том что первые поля (и комбинации левого "подкортежа" сортировки) не являются уникальными. Там, правда, нюанс — "корректно" не всегда значит "эффективно" — в некоторых пограничных случаях идёт жёсткий пролёт мимо удачного плана, к счастью, это достаточно редкие случаи, которых можно/нужно избегать (а в SQL Server 2005 c SP до четвёртого приводил к падению SQL Server, но это именно бага SQL Server).

Это достаточно простой функционал на самом деле.

Кстати, чтобы не пролетать мимо планов есть куда эффективнее способ сначала читать ключи, потом читать колонки. Чуть подробнее в недавней статье.

Ну и еще круче на самом деле отбрасывать сложные фильтры без индексов, находить более широкое «окно» на простых фильтрах для которых есть индексы, а потом добавлять это «окно» в фильтр, чтобы это окно протолкнулось в эти сложные фильтры (чтобы они расчитывались не для всей базы). Но пока так даже lsFusion не умеет, хотя вся инфраструктура для этого есть, возможно в будущих версиях и появится.

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


Решения пагинанции по ключам просто не существует в природе. Т.к пагинация предполагает произвольный выбор страниц: 3-я, 124-я, 45-я и как в этом случае помогут ключи? Имеется в виду наверное пагинация в стиле next/previous. Но это же как бы совсем другой вид навигации.


Что касается этого вида навигации то всегда нужно иметь в виду два момента
1) В запросах всегда всенепременно необходимо задавать сортировку
2) Т.к. в общем поля сортировки не обязаны является первичным ключом, неободимо в качестве дополнительного поля сортировки задавать первичный ключ.


К тому факту что за момент просмотра страницы пройдут где-то вставки/удаления — некоторые системы это отслеживают в реальном времени. но это же дико грузит и сеть и сервер и приложения. Поэтому уже почти как аксиома кажется что набор является "отсоединенным" от базы данных и иного не моет быть. А еще лет 20 назад все так и было. данные в приложениях менялись мгновенно после изменения их на сервере.

Развивая данную идею (OFFSET не нужен, «пляшем» от ключа), можно построить не только пагинацию, но и непрерывный скроллинг — что гораздо интереснее, чем пагинация. Более того, reverse engineering некоторых вещей показывает, что люди давно так его и делают.

Поддержка подобных запросов была бы хороша не только на уровне фреймворков, но и на уровне СУБД.

Уже несколько лет как я пишу по этой проблеме и делал доклад на JPoint, наиболее удачное изложение, на мой взгляд, здесь: dzone.com/articles/hidden-complexity-of-a-routine-task-presenting-tab
Да, в статье небольшая заметка есть про это, что бесконечный скроллинг как раз отлично ложится на такой подход.
В целом в статье не только про фреймворки говорится, а что нет комплексных решений для такого подхода. Хотелось бы иметь сквозную технологию, которая упрощала бы все это на всех уровнях, от баз данных до исполнения на фронте
Бесконечный скроллинг — это лишь один из возможных подходов, довольно тривиальный. Я же рассказывал про то, как мы сделали честный скроллинг на датасете в несколько млн записей, развивая идею про «пляшем от ключа».

Не совсем понимаю какие проблемы с реализацией непрерывного скрола в тех же гридах с данными в смысле если отбросить проблемы с интерфейсом. Есть на самом деле интересные задачи. Например на популярном сайте который хостинг которое циклические видео нужно показывать список в порядке убывания рейтинга. Но рейтинг постоянно меняется и уже просмотренное видео может показаться в другом месте и его нужно исключить из показа. Или же в магазине с большой номенклатурой нужно показывать клиентам рандомный список товаров чтобы они равномерно продавались а не только те что в верхней части алфавита или в топе продаж.
Отдельная тема обновление данных естественно так чтобы не положить сервер. Тут уже были бы в помощь базы данных с подпиской на обновление. Такой функционал есть например у rethinktdb разработка которой к сожалению остановлена вендоргм и перешла в оперативную разработку и наверное уже практически не продвигается.

Подписка на обновления есть у оракла

На самом деле, идеальный вариант это использование одного и того же курсора, которые, кстати, могут быть и двунаправленными, те можно читать и предыдущие строки. Переоткрытие курсора для пагинации в любом случае гораздо более тяжёлая операция, а особенно при отсутствии подходящих ключей. Есть, конечно, некоторая сложность в сохранении открытого курсора при трехзвенке, но вполне решаемая(как будет время, покажу мой вариант). А для двухзвенки, так вообще единственно правильный вариант.

Но курсоры это же не у всех без данных. Это же oracle и pervasive и может быть mssql не помню точно

Oracle, PostgreSql, mssql, db2 точно поддерживают. Вообще поддерживают все, чьи драйвера полноценно поддерживают JDBC

Я уже лет 15 не работал с java. На тот момент середины нулевых ни один драйвер из мной проверенных не поддерживал динамических курсоров которые менялись бы в случае изменения данных на сервере. Oracle я не проверял ок у них тоже только начался процесс с выходом бесплатной ограниченной версии.

Курсоры можно эмулировать с помощью SQL и логики на сервере приложений.

В статье указан тег PostgreSQL, соответственно про долгоиграющие транзакции с открыми курсорами можно забыть. Это связано с особенностями реализации MVCC и возможным распуханием таблиц и индексов. С Ораклом вроде бы должно прокатить, потому что там MVCC работает совсем по другому, через Undo сегменты. Но сейчас мало кто делает монолиты, уж точно не Тинькофф, значит каждый запрос к back-end (дай следующую порцию данных из курсора) направляется балансировщиком к разным нодам/репликам. Получается, никаких долгоживущих курсоров. Но в теории да, курсоры были бы тут очень кстати, особенно вкупе с уровнем изоляции Repeatable Read, чтоб исключить большинство аномалий чтения.

Описанное Маркусом наивное решение подходит лишь для самых простых случаев, когда сортировки прибиты гвоздями в коде. Мне наверное так повезло, но все системы, в разработке которых я участвовал последние лет 20 (финансовая аналитика, страхование, банки и проч.), поддерживали множественные сортировки и фильтры по всем полям в гридах, во всех экранных формах. Юзер может кликать в заголовки колонок в гриде и выбирать одно или сразу несколько полей для сортировки, и менять её направление.
Это всегда динамический SQL, и значит индексы для чтения с диска "сразу в правильном порядке" тут не работают. Но при грамотном дизайне хранилища эта проблема решается. Идея в том, что индексы должны отработать раньше, еще до сортировок и лимитов, и это обычно какие-то особые хитрые индексы по всяким служебным полям в таблицах/mat. views.
В первую очередь большую БД хорошо бы распилить на мелкие кусочки и разнести их физически. Это сокращает размеры сегментов таблиц и высоту индексов, повышает степень параллелизма. Сначала оптимизатор выбирает ноду/шард/партицию, потом по комбинации индексов (partial/functional индексы в сочетании с бинарными рулят) получается уже небольшой кусок данных (ну например десятки тысяч строк), и потом только выполняются сортировки этого ограниченного набора — строго в оперативной памяти, это быстро. Иногда можно помочь оптимизатору избежать дисковой сортировки, подкрутив размер рабочей памяти у конкретных запросов. При скроллинге грида следующие порции выдаются повторными запросами быстрее, т.к. данные кешируются в буферах СУБД и ОС.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий