Les exemples de la question ne produisent pas tout à fait les mêmes résultats (l' OFFSET
exemple a une erreur de coup par coup). Les formulaires mis à jour ci-dessous corrigent ce problème, suppriment le tri supplémentaire pour le ROW_NUMBER
cas et utilisent des variables pour rendre la solution plus générale:
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;
Le ROW_NUMBER
plan a un coût estimé à 0,0197935 :
Le OFFSET
plan a un coût estimé à 0,0196955 :
Cela représente une économie de 0,000098 unité de coût estimée (bien que le OFFSET
plan nécessite des opérateurs supplémentaires si vous souhaitez renvoyer un numéro de ligne pour chaque ligne). Le OFFSET
plan sera toujours légèrement moins cher, de manière générale, mais n'oubliez pas que les coûts estimés sont exactement cela - de vrais tests sont toujours nécessaires. La majeure partie du coût dans les deux plans est le coût du type complet de l'ensemble d'entrée, donc des index utiles bénéficieraient aux deux solutions.
Lorsque des valeurs littérales constantes sont utilisées (par exemple OFFSET 30
dans l'exemple d'origine), l'optimiseur peut utiliser un tri TopN au lieu d'un tri complet suivi d'un Top. Lorsque les lignes nécessaires du tri TopN sont un littéral constant et <= 100 (la somme de OFFSET
et FETCH
), le moteur d'exécution peut utiliser un algorithme de tri différent qui peut fonctionner plus rapidement que le tri TopN généralisé. Les trois cas ont globalement des caractéristiques de performance différentes.
Quant à savoir pourquoi l'optimiseur ne transforme pas automatiquement le ROW_NUMBER
modèle de syntaxe à utiliser OFFSET
, il existe un certain nombre de raisons:
- Il est presque impossible d'écrire une transformation qui correspondrait à toutes les utilisations existantes
- La transformation automatique de certaines requêtes de pagination et pas d'autres pourrait être source de confusion
- Le
OFFSET
plan n'est pas garanti d'être meilleur dans tous les cas
Un exemple pour le troisième point ci-dessus se produit lorsque l'ensemble de pagination est assez large. Il peut être beaucoup plus efficace de rechercher les clés nécessaires à l' aide d'un index non cluster et de rechercher manuellement l'index cluster par rapport à l'analyse de l'index avec OFFSET
ou ROW_NUMBER
. Il y a d' autres problèmes à considérer si l'application de pagination a besoin de savoir combien de lignes ou de pages il y a au total. Il y a une autre bonne discussion des mérites relatifs des méthodes de «recherche de clé» et de «compensation» ici .
Dans l'ensemble, il est probablement préférable que les utilisateurs prennent une décision éclairée de modifier leurs requêtes de pagination à utiliser OFFSET
, le cas échéant, après des tests approfondis.