実行計画でキー検索(クラスター化)演算子を削除するにはどうすればよいですか?
テーブルtblQuotes
にはすでにクラスタードインデックス(QuoteID
上)と27の非クラスター化インデックスがあるため、これ以上作成しないようにしています。
私はクエリにクラスター化インデックス列QuoteID
を入れて、それが役立つことを願っていますが、残念ながらまだ同じです。
ここでの実行計画 。
またはそれを見る:
これは、キールックアップオペレーターによるものです。
クエリ:
declare
@EffDateFrom datetime ='2017-02-01',
@EffDateTo datetime ='2017-08-28'
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
IF OBJECT_ID('tempdb..#Data') IS NOT NULL
DROP TABLE #Data
CREATE TABLE #Data
(
QuoteID int NOT NULL, --clustered index
[EffectiveDate] [datetime] NULL, --not indexed
[Submitted] [int] NULL,
[Quoted] [int] NULL,
[Bound] [int] NULL,
[Exonerated] [int] NULL,
[ProducerLocationId] [int] NULL,
[ProducerName] [varchar](300) NULL,
[BusinessType] [varchar](50) NULL,
[DisplayStatus] [varchar](50) NULL,
[Agent] [varchar] (50) NULL,
[ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
SELECT
tblQuotes.QuoteID,
tblQuotes.EffectiveDate,
CASE WHEN lstQuoteStatus.QuoteStatusID >= 1 THEN 1 ELSE 0 END AS Submitted,
CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
tblQuotes.ProducerLocationID,
P.Name + ' / '+ P.City as [ProducerName],
CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business'
WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
END AS BusinessType,
tblQuotes.DisplayStatus,
tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
tblProducerContacts.ProducerContactGUID
FROM tblQuotes
INNER JOIN lstQuoteStatus
on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
INNER JOIN tblProducerLocations P
On P.ProducerLocationID=tblQuotes.ProducerLocationID
INNER JOIN tblProducerContacts
ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID
WHERE DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
AND tblQuotes.OriginalQuoteGUID is null
select * from #Data
実行計画:
クエリが結果を返すために必要な行を見つけるために使用されるインデックスに格納されていない列からクエリプロセッサが値を取得する必要がある場合、さまざまなフレーバーのキールックアップが発生します。
次のコードを例にとります。ここでは、単一のインデックスを持つテーブルを作成しています。
USE tempdb;
IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO
CREATE TABLE dbo.Table1
(
Table1ID int NOT NULL IDENTITY(1,1)
, Table1Data nvarchar(30) NOT NULL
);
CREATE INDEX IX_Table1
ON dbo.Table1 (Table1ID);
GO
1,000,000行をテーブルに挿入して、処理するデータをいくつか用意します。
INSERT INTO dbo.Table1 (Table1Data)
SELECT TOP(1000000) LEFT(c.name, 30)
FROM sys.columns c
CROSS JOIN sys.columns c1
CROSS JOIN sys.columns c2;
GO
次に、「実際の」実行プランを表示するオプションを使用してデータをクエリします。
SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;
クエリプランは以下を示します:
クエリは、IX_Table1
インデックスを調べて、Table1ID = 5000000
を含む行を見つけます。これは、そのインデックスを調べる方が、テーブル全体をスキャンしてその値を探すよりもはるかに高速だからです。ただし、クエリ結果を満たすために、クエリプロセッサはテーブル内の他の列の値も見つける必要があります。これが「RIDルックアップ」の出番です。これは、Table1ID
値500000を含む行に関連付けられた行ID(RIDルックアップのRID)をテーブルで検索し、Table1Data
列から値を取得します。プランの「RID Lookup」ノードの上にマウスを置くと、次のように表示されます。
「出力リスト」には、RIDルックアップによって返される列が含まれています。
クラスタ化インデックスと非クラスタ化インデックスを持つテーブルは、興味深い例になります。以下の表には3つの列があります。クラスタリングキーであるID、Dat
、非クラスタ化インデックスIX_Table
、および3番目の列Oth
によってインデックスが作成されます。
USE tempdb;
IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO
CREATE TABLE dbo.Table1
(
ID int NOT NULL IDENTITY(1,1)
PRIMARY KEY CLUSTERED
, Dat nvarchar(30) NOT NULL
, Oth nvarchar(3) NOT NULL
);
CREATE INDEX IX_Table1
ON dbo.Table1 (Dat);
GO
INSERT INTO dbo.Table1 (Dat, Oth)
SELECT TOP(1000000) CRYPT_GEN_RANDOM(30), CRYPT_GEN_RANDOM(3)
FROM sys.columns c
CROSS JOIN sys.columns c1
CROSS JOIN sys.columns c2;
GO
次のクエリ例を見てください。
SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';
SQL Serverに、Dat
列にWord Test
が含まれているテーブルからすべての列を返すように要求しています。ここにはいくつかの選択肢があります。テーブル(つまり、クラスター化インデックス)を見ることができます。ただし、テーブルはID
列で並べられているため、Test
列にDat
が含まれている行については何も示されていないため、全体をスキャンする必要があります。もう1つのオプション(およびSQL Serverによって選択されるオプション)は、IX_Table1
非クラスター化インデックスをシークしてDat = 'Test'
の行を見つけることですが、Oth
列も必要なので、SQL Serverはクラスター化インデックスへのルックアップを実行する必要があります「キー検索」操作。これはそのための計画です:
非クラスター化インデックスを変更してincludesOth
列を含める場合:
DROP INDEX IX_Table1
ON dbo.Table1;
GO
CREATE INDEX IX_Table1
ON dbo.Table1 (Dat)
INCLUDE (Oth); <---- This is the only change
GO
次に、クエリを再実行します。
SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';
SQL ServerはDat = 'Test'
インデックスのIX_Table1
の行を検索する必要があるため、単一の非クラスター化インデックスシークが表示されます。これには、Oth
の値とID
列(主キー)の値が含まれ、自動的に表示されます。すべての非クラスター化インデックス。計画:
キールックアップは、フェッチしようとしているすべての列を含まないインデックスをエンジンが使用することを選択したために発生します。そのため、インデックスはselectおよびwhereステートメントの列をカバーしていません。
キールックアップを排除するには、欠落している列(キールックアップの出力リストの列)= ProducerContactGuid、QuoteStatusID、PolicyTypeID、およびProducerLocationIDを含める必要があります。または、代わりにクエリでクラスター化インデックスを使用するように強制することもできます。
テーブルの27の非クラスター化インデックスは、パフォーマンスが低下する可能性があることに注意してください。更新、挿入、または削除を実行する場合、SQL Serverはすべてのインデックスを更新する必要があります。この余分な作業は、パフォーマンスに悪影響を及ぼす可能性があります。
このクエリに含まれるデータ量について言及するのを忘れていました。また、なぜ一時テーブルに挿入するのですか?表示する必要があるだけの場合は、挿入ステートメントを実行しないでください。
このクエリでは、tblQuotes
に27の非クラスター化インデックスは必要ありません。 1つのクラスター化インデックスと5つの非クラスター化インデックス、またはおそらく6つの非クラスター化インデックスが必要です。
このクエリでは、次の列にインデックスが必要です。
QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID
次のコードにも気付きました。
DATEDIFF(D, @EffDateFrom, tblQuotes.EffectiveDate) >= 0 AND
DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <= 0
はNON Sargable
です。つまり、インデックスを利用できません。
このコードをSARgable
にするには、次のように変更します。
tblQuotes.EffectiveDate >= @EffDateFrom
AND tblQuotes.EffectiveDate <= @EffDateFrom
あなたの主な質問に答えるには、「なぜキールックアップを取得しているのですか」:
クエリで言及されている列の一部がカバリングインデックスに存在しないため、KEY Look up
を取得しています。
グーグルしてCovering Index
またはInclude index
について学ぶことができます。
私の例では、tblQuotes.QuoteStatusIDが非クラスター化インデックスであるとすると、DisplayStatusもカバーできます。結果セットにDisplayStatusが必要なため。インデックスに存在せず、結果セットに存在する列は、KEY Look Up or Bookmark lookup
を回避するためにカバーできます。これはインデックスをカバーする例です:
create nonclustered index tblQuotes_QuoteStatusID
on tblQuotes(QuoteStatusID)
include(DisplayStatus);
**免責事項:**上記は私の例にすぎないことを思い出してください。DisplayStatusは分析後に他の非CIでカバーされる場合があります。
同様に、クエリに関連する他のテーブルにインデックスとカバーインデックスを作成する必要があります。
プランでもIndex SCAN
を取得しています。
これは、テーブルにインデックスがないため、または大量のデータがある場合に、オプティマイザがインデックスシークを実行するのではなくスキャンすることを決定するために発生する可能性があります。
これは、High cardinality
が原因で発生することもあります。不完全な結合により、必要以上の行数を取得しています。これも修正できます。