Um grande
OFFSET
sempre vai ser lento. O Postgres tem que ordenar todas as linhas e contar o visível aqueles até o seu deslocamento. Para pular todas as linhas anteriores diretamente você pode adicionar um row_number
indexado para a tabela (ou crie uma MATERIALIZED VIEW
incluindo o dito row_number
) e trabalhe com WHERE row_number > x
em vez de OFFSET x
. No entanto, essa abordagem só é sensata para dados somente leitura (ou principalmente). Implementando o mesmo para dados de tabela que podem mudar simultaneamente é mais desafiador. Você precisa começar definindo o comportamento desejado exatamente .
Sugiro uma abordagem diferente para paginação :
SELECT *
FROM big_table
WHERE (vote, id) > (vote_x, id_x) -- ROW values
ORDER BY vote, id -- needs to be deterministic
LIMIT n;
Onde
vote_x
e id_x
são do último linha da página anterior (para ambos DESC
e ASC
). Ou desde o primeiro se estiver navegando para trás . A comparação de valores de linha é suportada pelo índice que você já possui - um recurso que está em conformidade com o padrão ISO SQL, mas nem todos os RDBMS o suportam.
CREATE INDEX vote_order_asc ON big_table (vote, id);
Ou para ordem decrescente:
SELECT *
FROM big_table
WHERE (vote, id) < (vote_x, id_x) -- ROW values
ORDER BY vote DESC, id DESC
LIMIT n;
Pode usar o mesmo índice.
Sugiro que você declare suas colunas
NOT NULL
ou familiarize-se com o NULLS FIRST|LAST
construir:- PostgreSQL classifica por datetime asc, null primeiro?
Observe duas coisas em particular:
-
AROW
valores emWHERE
cláusula não pode ser substituída por campos de membro separados.WHERE (vote, id) > (vote_x, id_x)
não pode ser substituído por:
WHERE vote >= vote_x AND id > id_x
Isso excluiria todos linhas comid <= id_x
, enquanto queremos fazê-lo apenas para a mesma votação e não para a próxima. A tradução correta seria:
WHERE (vote = vote_x AND id > id_x) OR vote > vote_x
... que não funciona tão bem com os índices e fica cada vez mais complicado para mais colunas.
Seria simples para um único coluna, obviamente. Esse é o caso especial que mencionei no início.
-
A técnica não funciona para direções mistas emORDER BY
Como:
ORDER BY vote ASC, id DESC
Pelo menos não consigo pensar em um genérico maneira de implementar isso com a mesma eficiência. Se pelo menos uma das duas colunas for um tipo numérico, você pode usar um índice funcional com um valor invertido em(vote, (id * -1))
- e use a mesma expressão emORDER BY
:
ORDER BY vote ASC, (id * -1) ASC
Relacionado:
- Termo de sintaxe SQL para 'WHERE (col1, col2) <(val1, val2)'
- Melhore o desempenho para ordenar com colunas de muitas tabelas
Observe em particular a apresentação de Markus Win e I vinculada a:
- "Paginação feita à maneira do PostgreSQL"