Duże OFFSET zawsze będzie powolny. Postgres musi uporządkować wszystkie rzędy i policzyć widoczne te do twojego offsetu. Aby pominąć wszystkie poprzednie wiersze bezpośrednio możesz dodać zindeksowany row_number do tabeli (lub utwórz MATERIALIZED VIEW w tym wspomniany row_number ) i pracuj z WHERE row_number > x zamiast OFFSET x .
Jednak to podejście jest sensowne tylko w przypadku danych tylko do odczytu (lub w większości). Implementacja tego samego dla danych tabeli, które mogą się zmieniać równocześnie jest trudniejsze. Musisz zacząć od zdefiniowania pożądanego zachowania dokładnie .
Proponuję inne podejście do paginacji :
SELECT *
FROM big_table
WHERE (vote, id) > (vote_x, id_x) -- ROW values
ORDER BY vote, id -- needs to be deterministic
LIMIT n;
Gdzie vote_x i id_x pochodzą od ostatnich wiersz poprzedniej strony (dla obu DESC i ASC ). Lub od pierwszego jeśli nawigujesz wstecz .
Porównywanie wartości wierszy jest obsługiwane przez indeks, który już posiadasz — funkcja, która jest zgodna ze standardem ISO SQL, ale nie każdy RDBMS ją obsługuje.
CREATE INDEX vote_order_asc ON big_table (vote, id);
Lub w porządku malejącym:
SELECT *
FROM big_table
WHERE (vote, id) < (vote_x, id_x) -- ROW values
ORDER BY vote DESC, id DESC
LIMIT n;
Może używać tego samego indeksu.
Proponuję zadeklarować kolumny NOT NULL lub zapoznaj się z NULLS FIRST|LAST konstrukcja:
- PostgreSQL sortuje według daty i godziny asc, najpierw null?
Zwróć uwagę na dwie rzeczy w szczególności:
-
ROWwartości wWHEREklauzuli nie można zastąpić oddzielnymi polami składowymi.WHERE (vote, id) > (vote_x, id_x)nie można być zastąpione przez:WHERE vote >= vote_x AND id > id_xTo wykluczyłoby wszystkie wiersze z
id <= id_x, podczas gdy chcemy to zrobić tylko dla tego samego głosowania, a nie dla następnego. Prawidłowe tłumaczenie to:WHERE (vote = vote_x AND id > id_x) OR vote > vote_x... co nie działa tak dobrze z indeksami i staje się coraz bardziej skomplikowane dla większej liczby kolumn.
Byłoby proste dla singla kolumna, oczywiście. To szczególny przypadek, o którym wspomniałem na początku.
-
Technika nie działa dla mieszanych kierunków w
ORDER BYjak:ORDER BY vote ASC, id DESCPrzynajmniej nie przychodzi mi do głowy ogólny sposób, aby wdrożyć to równie skutecznie. Jeśli przynajmniej jedna z obu kolumn jest typu liczbowego, możesz użyć indeksu funkcjonalnego z odwróconą wartością
(vote, (id * -1))- i użyj tego samego wyrażenia wORDER BY:ORDER BY vote ASC, (id * -1) ASC
Powiązane:
- Termin składni SQL dla „GDZIE (col1, col2) <(val1, val2)”
- Popraw wydajność zamówień dzięki kolumnom z wielu tabel
Zwróć uwagę w szczególności na prezentację Markusa Winanda, do której podałem link:
- „Paginacja na sposób PostgreSQL”