Примеры, приведенные в вопросе, не дают одинаковых результатов (в 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
, если это уместно, после тщательного тестирования.