Примеры, приведенные в вопросе, не дают одинаковых результатов (в OFFSETпримере есть ошибка «один за другим»). Обновленные формы ниже исправляют эту проблему, удаляют дополнительную сортировку для ROW_NUMBERслучая и используют переменные, чтобы сделать решение более общим:
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;
ROW_NUMBERПлан имеет ориентировочную стоимость 0.0197935 :

OFFSETПлан имеет ориентировочную стоимость 0.0196955 :

Это экономит 0,000098 единиц расчетной стоимости (хотя OFFSETплан потребует дополнительных операторов, если вы хотите вернуть номер строки для каждой строки). OFFSETПлан еще будет немного дешевле, вообще говоря, но помните , что сметные расходы именно это - реальное тестирование еще требуется. Большая часть затрат в обоих планах - это стоимость полного набора входных данных, поэтому полезные индексы будут полезны для обоих решений.
Если используются постоянные литеральные значения (например, OFFSET 30в исходном примере), оптимизатор может использовать сортировку TopN вместо полной сортировки, за которой следует Top. Когда строки, необходимые из сортировки TopN, являются константным литералом и <= 100 (сумма OFFSETи FETCH), механизм выполнения может использовать другой алгоритм сортировки, который может работать быстрее, чем обобщенная сортировка TopN. Все три случая имеют разные характеристики производительности в целом.
Относительно того, почему оптимизатор не преобразует автоматически используемый ROW_NUMBERсинтаксический шаблон OFFSET, существует ряд причин:
- Почти невозможно написать преобразование, которое бы соответствовало всем существующим применениям.
- Автоматическое преобразование некоторых пейджинговых запросов, а другие - не сбивает с толку.
OFFSETПлан не гарантированно будет лучше во всех случаях
Один пример для третьего пункта выше встречается, когда набор страниц достаточно широк. Гораздо эффективнее искать ключи, необходимые с использованием некластеризованного индекса и поиска вручную по кластерному индексу, по сравнению со сканированием индекса с помощью OFFSETили ROW_NUMBER. Существуют и другие проблемы, которые необходимо учитывать, если приложению подкачки нужно знать, сколько всего строк или страниц. Существует еще одна хорошая дискуссия относительных достоинств «ключ искать» и «смещение» методы здесь .
В целом, вероятно, лучше, чтобы люди приняли информированное решение изменить свои пейджинговые запросы для использования OFFSET, если это уместно, после тщательного тестирования.