Le nouveau OFFSET ... FETCH
Le modèle introduit avec SQL Server 2012 propose une pagination simple et plus rapide. Pourquoi y a-t-il des différences qui envisagent de penser que les deux formes sont sémantiquement identiques et très courantes?
On supposerait que l'optimiseur reconnaît les deux et les optimise (trivialement) au maximum.
Voici un cas très simple où OFFSET ... FETCH
est ~ 2 fois plus rapide selon l'estimation des coûts.
SELECT * INTO #objects FROM sys.objects
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY object_id) r
FROM #objects
) x
WHERE r >= 30 AND r < (30 + 10)
ORDER BY object_id
SELECT *
FROM #objects
ORDER BY object_id
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY
On peut varier ce boîtier de test en créant un CI sur object_id
ou ajouter des filtres mais il est impossible de supprimer toutes les différences de plan. OFFSET ... FETCH
est toujours plus rapide car il fonctionne moins à l'heure d'exécution.
Les exemples de la question ne produisent pas tout à fait les mêmes résultats (l'exemple OFFSET
a une erreur obsolète). Les formulaires mis à jour ci-dessous corrigent ce problème, supprimez le tri supplémentaire pour le ROW_NUMBER
Case et utilisez 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
Le plan a un coût estimé de 0.0197935 :
Le plan OFFSET
a un coût estimé de 0.0196955 :
C'est une économie de 0.000098 unités de coût estimées (bien que le plan OFFSET
nécessiterait des opérateurs supplémentaires si vous souhaitez retourner un numéro de ligne pour chaque rangée). Le plan OFFSET
sera toujours légèrement moins cher, d'une manière générale, mais rappelez-vous que les coûts estimés sont exactement que: les tests réels sont toujours nécessaires. L'essentiel du coût dans les deux plans est le coût de la totalité du type d'entrée, des index utiles bénéficieraient des solutions.
Où des valeurs littérales constantes sont utilisées (par exemple, OFFSET 30
Dans l'exemple original), l'optimiseur peut utiliser une sorte de topn au lieu d'une sorte complète suivie d'un sommet. Lorsque les lignes nécessaires à partir du type TOPN sont un littéral constant et <= 100 (la somme de OFFSET
et FETCH
) Le moteur d'exécution peut utiliser n algorithme de tri différent qui peut fonctionner plus rapidement que le tri général généralisé. Les trois cas ont des caractéristiques de performance différentes dans l'ensemble.
Pourquoi l'optimiseur ne transforme pas automatiquement le ROW_NUMBER
Motif de syntaxe à utiliser OFFSET
, il existe un certain nombre de raisons:
OFFSET
n'est pas garanti d'être meilleur dans tous les casUn exemple pour le troisième point ci-dessus se produit lorsque l'ensemble de radiomessagerie est assez large. Il peut être beaucoup plus efficace pour rechercher les clés nécessaires à l'aide d'un index non clusterisé et recherchez manuellement l'indice en cluster par rapport à la numérisation de l'index avec OFFSET
ou ROW_NUMBER
. Il y a questions supplémentaires à considérer Si l'application de pagination doit savoir combien de lignes ou de pages au total. Il y a une autre bonne discussion sur les mérites relatives des "méthodes de la recherche de la clé" et des "compensation" ici .
Dans l'ensemble, il est probablement préférable que les gens prennent une décision éclairée de modifier leurs requêtes de pagination pour utiliser OFFSET
, le cas échéant, après des tests approfondis.
Avec une légère violation de votre requête, je reçois un coût égal estimation (50/50) et égal IO Statistiques:
; WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY object_id) r
FROM #objects
)
SELECT *
FROM cte
WHERE r >= 30 AND r < 40
ORDER BY r
SELECT *
FROM #objects
ORDER BY object_id
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY
Cela évite le tri supplémentaire qui apparaît dans votre version en triant sur r
au lieu de object_id
.