LIMIT e OFFSET: paginação
1. Introdução à Paginação em SQL
Paginação é a técnica de dividir um grande conjunto de resultados em partes menores e gerenciáveis, chamadas de páginas. Em aplicações web, APIs e relatórios, raramente faz sentido retornar milhares ou milhões de registros de uma só vez — isso sobrecarrega o banco de dados, consome memória do servidor e degrada a experiência do usuário.
Sem paginação, uma consulta como SELECT * FROM vendas poderia trazer 5 milhões de linhas, causando timeouts e lentidão generalizada. Com paginação, recuperamos apenas 20 ou 50 registros por vez.
As cláusulas LIMIT e OFFSET são a abordagem mais simples e difundida para implementar paginação em SQL. LIMIT define quantas linhas retornar, enquanto OFFSET especifica quantas linhas pular antes de começar a retornar.
2. Sintaxe Básica do LIMIT
A sintaxe fundamental é:
SELECT colunas
FROM tabela
LIMIT n;
Isso retorna as primeiras n linhas da consulta. Exemplo prático:
SELECT nome, email
FROM clientes
LIMIT 5;
Resultado: os 5 primeiros clientes da tabela.
Em bancos como MySQL, PostgreSQL e SQLite, a sintaxe é idêntica. No SQL Server, a cláusula equivalente é SELECT TOP n, mas o padrão mais moderno (a partir do SQL Server 2012) usa OFFSET...FETCH, como veremos adiante.
3. Sintaxe Básica do OFFSET
OFFSET sozinho não faz sentido — ele precisa de LIMIT (ou FETCH) para funcionar:
SELECT colunas
FROM tabela
LIMIT n
OFFSET m;
Isso pula as primeiras m linhas e retorna as próximas n. Exemplo:
SELECT nome, email
FROM clientes
LIMIT 3
OFFSET 2;
Aqui, pulamos os 2 primeiros clientes e retornamos os 3 seguintes (clientes 3, 4 e 5, assumindo ordenação padrão).
4. Paginação com LIMIT e OFFSET
A fórmula clássica para calcular os parâmetros é:
LIMIT tamanho_da_pagina
OFFSET (numero_da_pagina - 1) * tamanho_da_pagina
Exemplo completo com página de 10 registros:
Página 1:
SELECT id, nome, data_cadastro
FROM clientes
ORDER BY id
LIMIT 10 OFFSET 0;
Página 2:
SELECT id, nome, data_cadastro
FROM clientes
ORDER BY id
LIMIT 10 OFFSET 10;
Página 3:
SELECT id, nome, data_cadastro
FROM clientes
ORDER BY id
LIMIT 10 OFFSET 20;
Atenção: ORDER BY é obrigatório! Sem ele, a ordem dos registros não é garantida, e a mesma página pode mostrar dados diferentes em execuções distintas. Sempre ordene por uma coluna (ou combinação) que seja única ou estável.
5. Performance e Limitações
Embora simples, OFFSET tem um problema grave de performance em tabelas grandes. O banco de dados precisa ler e descartar todas as linhas até o ponto de OFFSET. Para OFFSET 1000000 com LIMIT 20, o banco lê 1.000.020 linhas, descarta as primeiras 1.000.000 e retorna apenas 20.
Isso causa:
- Uso intensivo de I/O e CPU
- Aumento do tempo de resposta conforme a página avança
- Travamentos em consultas com milhões de registros
Alternativa recomendada: paginação baseada em cursor (keyset pagination). Em vez de pular linhas, usamos a última chave da página anterior:
SELECT id, nome, data_cadastro
FROM clientes
WHERE id > 100 -- último id da página anterior
ORDER BY id
LIMIT 10;
Essa abordagem é muito mais eficiente, pois usa índices e não precisa escanear linhas descartadas. Para tabelas com milhões de registros, prefira essa técnica.
6. Combinação com ORDER BY e Filtros
Podemos combinar LIMIT/OFFSET com WHERE e ORDER BY livremente:
SELECT id, nome, total_gasto
FROM clientes
WHERE ativo = true
ORDER BY total_gasto DESC, id ASC
LIMIT 10 OFFSET 20;
Isso retorna a terceira página (registros 21 a 30) dos clientes ativos, ordenados do maior gastador para o menor.
Ordenação instável: Se dois registros tiverem o mesmo valor na coluna ORDER BY, a ordem entre eles pode variar entre execuções. Para garantir estabilidade, inclua uma coluna única como desempate (ex.: ORDER BY total_gasto DESC, id ASC).
7. Variações entre SGBDs
Cada banco tem sua sintaxe:
MySQL, PostgreSQL, SQLite:
SELECT * FROM produtos
ORDER BY preco
LIMIT 5 OFFSET 10;
SQL Server (2012+):
SELECT *
FROM produtos
ORDER BY preco
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;
Oracle (antes da versão 12c):
SELECT * FROM (
SELECT p.*, ROWNUM rnum
FROM (
SELECT * FROM produtos ORDER BY preco
) p
WHERE ROWNUM <= 15
)
WHERE rnum > 10;
Oracle (12c+): Suporta OFFSET...FETCH similar ao SQL Server.
DB2 e Firebird: Também usam OFFSET...FETCH ou LIMIT...OFFSET dependendo da versão.
8. Boas Práticas e Casos de Uso
Quando usar OFFSET:
- Tabelas pequenas (até algumas centenas de milhares de registros)
- Prototipagem rápida
- Sistemas internos com baixo volume de dados
Quando evitar OFFSET:
- Tabelas com milhões de registros
- APIs públicas com requisitos de performance
- Relatórios com navegação profunda (página 100+)
Implementação de "carregar mais" (infinite scroll): Prefira paginação por cursor. A cada requisição, envie o último id ou created_at visto e use WHERE chave > valor com LIMIT.
Dicas para testes:
- Sempre valide que o número total de registros está correto (use COUNT(*) separado)
- Teste páginas extremas (primeira, última, além do fim)
- Verifique se a ordenação está estável com dados duplicados
Referências
- PostgreSQL Documentation: LIMIT and OFFSET — Documentação oficial do PostgreSQL sobre LIMIT e OFFSET, com exemplos e detalhes de comportamento.
- MySQL 8.0 Reference Manual: SELECT ... LIMIT — Seção oficial do MySQL explicando a cláusula LIMIT e suas variações.
- SQL Server: OFFSET and FETCH — Documentação da Microsoft sobre paginação com OFFSET...FETCH no SQL Server.
- Use the OFFSET and FETCH clauses to limit the rows returned — Tutorial prático da Microsoft sobre paginação eficiente.
- SQLite Query Language: SELECT — Documentação do SQLite sobre LIMIT e OFFSET, incluindo comportamento com ORDER BY.
- Oracle Database: Row Limiting Clause — Documentação Oracle sobre a cláusula de limitação de linhas (OFFSET...FETCH) a partir da versão 12c.
- Use keyset pagination to avoid OFFSET performance issues — Artigo técnico de Markus Winand explicando paginação baseada em cursor como alternativa ao OFFSET.
- SQL Performance Explained: Pagination — Guia detalhado sobre diferentes estratégias de paginação e seus impactos na performance.