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:
-
ROW
wartości wWHERE
klauzuli 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 BY
jak:ORDER BY vote ASC, id DESC
Przynajmniej 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”