Os exemplos na pergunta não produzem os mesmos resultados (o OFFSET
exemplo tem um erro de um por um). Os formulários atualizados abaixo corrigem esse problema, removem a classificação extra para o ROW_NUMBER
caso e usam variáveis para tornar a solução mais geral:
DECLARE
@PageSize bigint = 10,
@PageNumber integer = 3;
WITH Numbered AS
(
SELECT TOP ((@PageNumber + 1) * @PageSize)
o.*,
rn = ROW_NUMBER() OVER (
ORDER BY o.[object_id])
FROM #objects AS o
ORDER BY
o.[object_id]
)
SELECT
x.name,
x.[object_id],
x.principal_id,
x.[schema_id],
x.parent_object_id,
x.[type],
x.type_desc,
x.create_date,
x.modify_date,
x.is_ms_shipped,
x.is_published,
x.is_schema_published
FROM Numbered AS x
WHERE
x.rn >= @PageNumber * @PageSize
AND x.rn < ((@PageNumber + 1) * @PageSize)
ORDER BY
x.[object_id];
SELECT
o.name,
o.[object_id],
o.principal_id,
o.[schema_id],
o.parent_object_id,
o.[type],
o.type_desc,
o.create_date,
o.modify_date,
o.is_ms_shipped,
o.is_published,
o.is_schema_published
FROM #objects AS o
ORDER BY
o.[object_id]
OFFSET @PageNumber * @PageSize - 1 ROWS
FETCH NEXT @PageSize ROWS ONLY;
O ROW_NUMBER
plano tem um custo estimado de 0,0197935 :
O OFFSET
plano tem um custo estimado de 0,0196955 :
Isso representa uma economia de 0,000098 unidades de custo estimado (embora o OFFSET
plano exija operadores adicionais se você desejar retornar um número de linha para cada linha). O OFFSET
plano ainda será um pouco mais barato, de um modo geral, mas lembre-se de que os custos estimados são exatamente isso - testes reais ainda são necessários. A maior parte do custo em ambos os planos é o custo de todo o tipo de conjunto de entradas; portanto, índices úteis beneficiariam ambas as soluções.
Onde valores literais constantes são usados (por exemplo, OFFSET 30
no exemplo original), o otimizador pode usar uma Classificação TopN em vez de uma classificação completa seguida por um Top. Quando as linhas necessárias da Classificação TopN são um literal constante e <= 100 (a soma de OFFSET
e FETCH
) o mecanismo de execução pode usar um algoritmo de classificação diferente, que pode executar mais rapidamente que a classificação TopN generalizada. Todos os três casos têm características de desempenho diferentes em geral.
Quanto ao motivo pelo qual o otimizador não transforma automaticamente o ROW_NUMBER
padrão de sintaxe para uso OFFSET
, há vários motivos:
- É quase impossível escrever uma transformação que corresponda a todos os usos existentes
- Ter algumas consultas de paginação transformadas automaticamente e outras não, pode ser confuso
- Não
OFFSET
é garantido que o plano seja melhor em todos os casos
Um exemplo para o terceiro ponto acima ocorre onde o conjunto de paginação é bastante amplo. Pode ser muito mais eficiente procurar as chaves necessárias usando um índice não clusterizado e procurar manualmente o índice clusterizado em comparação com a varredura do índice com OFFSET
ou ROW_NUMBER
. Existem problemas adicionais a serem considerados se o aplicativo de paginação precisar saber quantas linhas ou páginas existem no total. Há outra boa discussão sobre os méritos relativos dos métodos 'busca por chave' e 'compensação' aqui .
No geral, é provavelmente melhor que as pessoas tomem uma decisão informada de alterar suas consultas de paginação para usar OFFSET
, se apropriado, após testes completos.