Skip to content

Paginar com keyset (seek) em vez de OFFSET snippet

A paginação por OFFSET atravessa N linhas a cada página — a página 1000 relê 999 páginas de dados, e qualquer inserção entre pedidos desloca linhas e faz itens repetirem-se ou desaparecer.

A paginação por OFFSET atravessa N linhas a cada página — a página 1000 relê 999 páginas de dados, e qualquer inserção entre pedidos desloca linhas e faz itens repetirem-se ou desaparecer. A paginação keyset (seek) em vez disso pergunta: dê-me as linhas estritamente a seguir à última que vi. A chave de ordenação tem de ser única e estável — um carimbo temporal sozinho não é, linhas empatadas reordenam-se imprevisivelmente — pelo que o id acompanha como desempatador, e o índice composto (created_at, id) serve a varredura. A sintaxe de comparação de linhas (a, b) > (x, y) é a grafia concisa do Postgres.

Receita executável · 1 linguagens
Files & Datasqlpaginationkeysetseekperformance

Every language

1 linguagens, copy-ready. One at a time with syntax highlighting, or all inline.

SQLSQLrunnable
-- page 1
SELECT id, created_at, title
FROM posts
ORDER BY created_at DESC, id DESC
LIMIT 20;

-- next page: feed the LAST row's keys back in
SELECT id, created_at, title
FROM posts
WHERE (created_at, id) < ('2026-08-24 18:02:00+00', 9014)
ORDER BY created_at DESC, id DESC
LIMIT 20;

The tuple comparison (created_at, id) < (last_seen_at, last_seen_id) is lexicographic — exactly the ORDER BY in reverse. Needs the composite index CREATE INDEX ON posts (created_at DESC, id DESC) to stay an index scan at depth. Run both queries in the playground.

Run in the SQL playground →

Keep going

Read the sql-indexing cheatsheet →