PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Otimize a consulta com OFFSET em uma tabela grande


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:

  1. A ROW valores em WHERE 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 com id <= 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.

  2. A técnica não funciona para direções mistas em ORDER 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 em ORDER 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"