SQL Server 2012で導入された新しいOFFSET ... FETCH
モデルは、シンプルで高速なページングを提供します。 2つの形式が意味的に同一であり、非常に一般的であることを考えると、なぜ違いがあるのですか?
オプティマイザが両方を認識し、それらを(自明に)最大限に最適化すると想定します。
これは、コストの見積もりによると、OFFSET ... FETCH
が〜2倍高速な非常に単純なケースです。
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
object_id
にCIを作成するか、フィルターを追加することにより、このテストケースを変更できますが、すべての計画の違いを削除することは不可能です。 OFFSET ... FETCH
は、実行時の処理が少ないため、常に高速です。
質問の例では、同じ結果が得られません(OFFSET
の例には、1つずれるエラーがあります)。以下の更新されたフォームはその問題を修正し、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
)、オプティマイザは完全な並べ替えの後にTopが続く代わりに、TopN Sortを使用できます。 TopNソートから必要な行が定数リテラルで<= 100(OFFSET
とFETCH
の合計)の場合、実行エンジンは 別のソートアルゴリズム を使用できます。これは、一般的なTopNソートよりも高速に実行できます。 3つのケースはすべて、全体的にパフォーマンス特性が異なります。
オプティマイザがROW_NUMBER
構文パターンを自動的に変換してOFFSET
を使用しない理由には、いくつかの理由があります。
OFFSET
プランがすべてのケースでより優れているとは限りません上記の3番目のポイントの一例は、ページングセットが非常に広い場合です。 必要なキーを探す 非クラスター化インデックスを使用し、OFFSET
またはROW_NUMBER
でインデックスをスキャンするよりも、クラスター化インデックスに対して手動でルックアップする方がはるかに効率的です。ページングアプリケーションが合計で行またはページの数を知る必要がある場合、 検討すべき追加の問題 があります。 「キーシーク」と「オフセット」メソッドの相対的なメリットについては、別のよい議論があります here 。
概して、十分なテストを行った後、ページングクエリを変更して、適切な場合はOFFSET
を使用するように、情報に基づいた決定を下す方がおそらく良いでしょう。
あなたのクエリを少しいじると、等しいコスト推定(50/50)と等しいIO stats:
; 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
これにより、object_id
ではなくr
でソートすることにより、バージョンに表示される追加のソートが回避されます。