WHERE句が「含まれる」列の恩恵を受けるのはなぜですか?


12

この回答によれば、制限に使用される列にインデックスが構築されない限り、クエリはインデックスの恩恵を受けません。

私にはこの定義があります:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

このクエリ:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

実際のプランを確認しましたが、述語と同じようにインデックスシークが1つだけありWHEREます- LastAccessTimeインデックスの一部ではなく、インデックスに「含まれる」だけの追加の「ブックマークルックアップ」はありません。

この動作は、列が単に「含まれる」のではなく、インデックスの一部でなければならないという規則に矛盾するように思えます。

私が観察した行動は正しいものですか?WHERE含まれている列からの利益があるかどうか、または列をインデックスの一部にする必要があるかどうかを事前に知るにはどうすればよいですか?


ItemState値に基づいてシークすることはできますが、シークは、インデックスが次のように構成されている場合ほど効率的ではありません(ItemState, CreationTime, LastAccessTime)
マークシンキンソン

1
@MarkSinkinsonまたはちょうど(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ

@sharptoothあなたが持っているリンクされた答えはそれを言いません(「クエリを制限するために使用される列にインデックスが構築されない限り、インデックスの恩恵を受けない」)。上(a,b)のクエリはクエリでの使用には最適ではなく、SELECT a FROM t WHERE b=5;オンのインデックス(b) INCLUDE (a)ははるかに優れていると言われています。
ypercubeᵀᴹ

回答:


9

述語は、シーク述語とは異なります。

Seek Predicateは、インデックス内の順序付けられたデータを検索するために使用されます。この場合、3つのシークを実行します。1つは対象の各ItemStateに対応します。それ以上は、データはItemPriority順であるため、それ以上の「シーク」操作は実行できません。

ただし、データが返される前に、Predicateを使用してすべての行をチェックします。これをResidual Predicateと呼びます。シーク述語の結果に基づいて行われます。

含まれる列は順序付けられたデータの一部ではありませんが、余分なLookupを実行することなく、Residual Predicateを満たすために使用できます。

Sargabilityに関してこれについて書いた資料を見ることができます。特にhttp://bit.ly/Sargabilityの SQLBitsでセッションを確認してください

編集:残差の影響をよりよく示すために、文書化されていないを使用してクエリを実行しますOPTION (QUERYTRACEON 9130)。これにより、残差が個別のフィルター演算子に分離されます(実際には、残差がシーク演算子に移動する前のプランの以前のバージョンです)。フィルタに残っている行の数により、効果のないシークの影響を明確に示しています。

また、ItemStateのIN句のため、左に渡されるデータは、実際にはItemPriorityの順序ではなく、ItemStateの順序であることに注意してください。日付の1つが続くItemStateの複合インデックス(たとえば(ItemState、LastAccessTime))を使用して、2つのレベルに対してそれぞれ3つのシーク(1つのSeek演算子内で3つのシークを示していることに注意)まだItemStateの順序です(たとえば、ItemState = 3およびLastAccessTimeが何かより小さく、次にItemState = 9およびLastAccessTimeが何かより小さく、次にItemState = 10およびLastAccessTimeが何かよりも小さい)。

(ItemState、LastAccesTime、CreationTime)のインデックスは(ItemState、LastAccessTime)のインデックスよりも有用ではありません。CreationTimeレベルは、シークが範囲ではなく、特定のItemStateとLastAccessTimeの組み合わせに対してのみ有効であるためです。Fで始まる姓に興味がある場合、電話帳がFirstNameの順序になっていないように。

複合インデックスが必要であるが、以前の列を使用する方法のために、Seek Predicatesで後の列を使用できない場合は、それらを含める列としても使用できます。インデックス(上位レベルではなく、インデックスのリーフレベルにのみ格納されるため)が、ルックアップを回避し、残余述語で使用される可能性があります。

残余述語という用語によると、それはシークのこのプロパティに対する私自身の用語です。マージ結合は、それと同等の残差述語を明示的に呼び出し、ハッシュマッチは、それをプローブ残差と呼びます(ハッシュに一致する場合、TSAから取得する可能性があります)。しかし、シークでは、彼らはそれを単に述語と呼びます。


3

where句がオンになってItemState + LastAccessTime + CreationTimeいるため、GetItemToProcessIndexは完全にシークできません。インデックス付きの列とwhere句は完全には一致しません。

にカバリングインデックスを作成すると、ItemState + LastAccessTime + CreationTimeGetItemToProcessIndexから取得する各一致に対して、プライマリキー(ItemId)の値も取得します。2番目の日付が一致することを確認するだけです。

これで、ページの行の場所にジャンプして更新する必要があります。

現在のインデックスを使用すると、サーバーが必要なItemStateを持つ行を見つけるのに役立つ場合がありますが、LastAccessTime + CreationTimeで正しい一致を見つけるために、インデックスからすべての行を読み取る必要があります。日付の述語と一致するセットのサイズ、および除外する対象によっては、ItemStateと2番目の列(最初のインデックス付けされた日付)を検索する3列のみの完全にカバーするインデックスよりもはるかに多くのIOが発生する場合があります。ただし、インデックスの2番目の日付を含めることができます。4列目としても問題ありませんが、これら3つの間に余分な列をインデックス付けしないでください(追加の列についてはrobの回答を参照してください)。

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