问题中的示例所产生的结果并不完全相同(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排序,而不是紧随其后的完全排序。当从TOPN所需要的行进行排序是一个常量文字和<= 100(的总和OFFSET
和FETCH
)执行引擎可以使用不同的排序算法可以比排序广义TOPN执行得更快。这三种情况总体上具有不同的性能特征。
关于优化器为何不自动将ROW_NUMBER
语法模式转换为use OFFSET
的原因,有很多原因:
- 编写与所有现有用途匹配的转换几乎是不可能的
- 使某些分页查询自动转换,而其他的则不会造成混淆
OFFSET
不能保证该计划在所有情况下都更好
上面第三点的一个示例发生在寻呼集很宽的地方。与使用或扫描索引相比,使用非聚簇索引查找所需的键并针对聚簇索引手动查找可能会更加高效。有需要考虑其他问题,如果寻呼应用程序需要知道有多少行或页总共有。有“抵消”方法的相对优劣的另一个很好的讨论“键寻找”和这里。OFFSET
ROW_NUMBER
总体而言,最好是OFFSET
在经过全面测试后,让人们做出明智的决定,以更改其分页查询以使用。