Los ejemplos en la pregunta no producen los mismos resultados (el OFFSETejemplo tiene un error off-by-one). Los formularios actualizados a continuación solucionan ese problema, eliminan la clasificación adicional para el ROW_NUMBERcaso y usan variables para hacer que la solución sea más general:
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;
El ROW_NUMBERplan tiene un costo estimado de 0.0197935 :

El OFFSETplan tiene un costo estimado de 0.0196955 :

Eso es un ahorro de 0.000098 unidades de costo estimado (aunque el OFFSETplan requeriría operadores adicionales si desea devolver un número de fila para cada fila). El OFFSETplan seguirá siendo un poco más barato, en general, pero recuerde que los costos estimados son exactamente eso: aún se requieren pruebas reales. La mayor parte del costo en ambos planes es el costo del tipo completo del conjunto de entrada, por lo que los índices útiles beneficiarían a ambas soluciones.
Cuando se utilizan valores literales constantes (p. Ej., OFFSET 30En el ejemplo original), el optimizador puede usar una clasificación TopN en lugar de una clasificación completa seguida de una clasificación Top. Cuando las filas necesarias del TopN Sort son un literal constante y <= 100 (la suma de OFFSETy FETCH) el motor de ejecución puede usar un algoritmo de ordenación diferente que puede funcionar más rápido que el TopN generalizado. Los tres casos tienen características de rendimiento diferentes en general.
En cuanto a por qué el optimizador no transforma automáticamente el ROW_NUMBERpatrón de sintaxis para usar OFFSET, hay una serie de razones:
- Es casi imposible escribir una transformación que coincida con todos los usos existentes
- Tener algunas consultas de paginación se transforman automáticamente y otras no pueden ser confusas.
- No
OFFSETse garantiza que el plan sea mejor en todos los casos.
Un ejemplo para el tercer punto anterior ocurre cuando el conjunto de paginación es bastante amplio. Puede ser mucho más eficiente buscar las claves necesarias utilizando un índice no agrupado y buscar manualmente en el índice agrupado en comparación con escanear el índice con OFFSETo ROW_NUMBER. Hay problemas adicionales a considerar si la aplicación de paginación necesita saber cuántas filas o páginas hay en total. Hay otra buena discusión sobre los méritos relativos de la tecla 'buscar' y 'compensar' métodos aquí .
En general, probablemente sea mejor que las personas tomen una decisión informada de cambiar sus consultas de paginación para usar OFFSET, si corresponde, después de una prueba exhaustiva.