ROW_NUMBER()OVER(PARTITION BY B、A ORDER BY C)は、(A、B、C)のインデックスを使用しません


12

次の2つの機能を検討してください。

ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)

ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)

私の知る限り、まったく同じ結果が得られます。つまり、PARTITION BY句内の列をリストする順序は重要ではありません。

インデックスがある場合(A,B,C)、オプティマイザーが両方のバリアントでこのインデックスを使用することを期待しました。

しかし、驚くべきことに、オプティマイザーは2番目のバリアントで追加の明示的な並べ替えを行うことにしました。

SQL Server 2008 StandardおよびSQL Server 2014 Expressで見ました。

以下は、私がそれを再現するために使用した完全なスクリプトです。

Microsoft SQL Server 2014で試しました-12.0.2000.8(X64)2014年2月20日20:04:26 Copyright(c)Microsoft Corporation Express Edition(64-bit)on Windows NT 6.1(Build 7601:Service Pack 1)

およびMicrosoft SQL Server 2014(SP1-CU7)(KB3162659)-12.0.4459.0(X64)2016年5月27日15:33:17著作権(c)Windows NT 6.1(ビルド7601:サービス上のMicrosoft Corporation Express Edition(64ビット)パック1)

およびを使用して、新旧両方のカーディナリティ推定器を使用OPTION (QUERYTRACEON 9481)OPTION (QUERYTRACEON 2312)ます。

テーブル、インデックス、サンプルデータを設定する

CREATE TABLE [dbo].[T](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [A] [int] NOT NULL,
    [B] [int] NOT NULL,
    [C] [int] NOT NULL,
    CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
    [A] ASC,
    [B] ASC,
    [C] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, 
ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON)
GO

INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);

問い合わせ

SELECT -- AB
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- BA
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

実行計画

A、Bによるパーティション

AB

B、Aによる区分

BA

どちらも

どちらも

ご覧のとおり、2番目のプランには追加のソートがあります。B、A、Cで注文します。オプティマイザーは、明らかに、それPARTITION BY B,APARTITION BY A,Bデータと同じであり、データを再ソートすることに気付くほどスマートではありません。

興味深いことに、3番目のクエリには両方のバリエーションROW_NUMBERがあり、追加の並べ替えはありません!計画は最初のクエリと同じです。(シーケンスプロジェクトでは、出力リストに追加列の追加式がありますが、追加ソートはありません)。したがって、このより複雑なケースでは、オプティマイザーPARTITION BY B,Aは、それがと同じであることに気付くのに十分賢いように見えましたPARTITION BY A,B

最初のクエリと3番目のクエリでは、インデックススキャン演算子のプロパティはOrdered:Trueであり、2番目のクエリではFalseです。

さらに興味深いのは、次のように3番目のクエリを書き直した場合です(2列を入れ替えます)。

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

余分な並べ替えが再び表示されます!

誰かが光を当てることができますか?ここでオプティマイザーで何が起こっていますか?


回答:


2

あなたがその開発者であり、その内部を知らない限り、「オプティマイザーで何が起こっているのか」という質問に対する良い決定的な「答え」はないようです。

ここでコメントをまとめます。

全体として、クエリの最終結果が正しいため、バグと呼ぶには過酷すぎると思われます。場合によっては、実行計画が単に最適ではありません。ypercubeᵀᴹMartin Smith、およびAaron Bertrandは、それを「最適化の欠如」と呼んでいます。

  • 同じように見え、同一の計画GROUP BY a,bGROUP BY b,a生成しPARTITION BYますが、同じ変換を使用することはできません

  • 同じウィンドウ仕様を持つウィンドウ関数が、選択リスト内で異なる仕様を持つウィンドウ関数によって分離されている場合、追加のソート操作を行うことができる他の欠落した最適化もあります。

  • ええ、これは別の最適化を見逃したようです、そしてそれらはたくさんあります。オプティマイザーは人間によって書かれており、完璧ではありません


やや関連する記事Descending Indexesがあります。Itzik Ben-Ganによるインデックスの順序付け、並列処理、およびランキング計算。Itzikは、降順のインデックスについて説明し、インデックス定義の方向がパーティションのあるウィンドウ関数にどのように影響するかの例を示します。彼はROW_NUMBER、オプティマイザーが回避できた追加のソート演算子を持つクエリと生成されたプランの例を示します。


私にとって実際的な結果は、このオプティマイザーの特性を念頭に置くことです。PARTITION BYウィンドウ内の関数を使用する場合は、列をリストPARTITION BYする順序と列がインデックスにリストされている順序を常に一致させてください。それは問題ではありませんが。

この予防策のもう1つの側面は、インデックスを確認し、インデックス定義でいくつかの列を入れ替えることを決定するときです。影響を受けてはならないように思われる既存のクエリに誤って影響を与える可能性があることに注意してください。これが実際に私がオプティマイザーのこの特異性に気づいた方法です。

そうしないと、オプティマイザーはインデックスを最大限に活用できない可能性があります。オプティマイザーが最適なプランを選択したとしても、そのようなプランは、SELECTステートメント内の列の順序を変更するなど、クエリにわずかな無害な変更を加えるだけで最適性が低下する場合があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.