データが歪んでいること、クエリヒントを使用してオプティマイザーに何をするかを強制したくないこと、およびのすべての可能な入力値に対して良好なパフォーマンスを得る必要があると仮定します@Id
。次のインデックスペア(またはそれらに相当するもの)を作成する場合は、可能な入力値に対してわずかな数の論理読み取りのみを必要とするクエリプランを取得できます。
CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;
以下は私のテストデータです。テーブルに13 M行を配置し、その半分に列の値を設定し'3A35EA17-CE7E-4637-8319-4C517B6E48CA'
ましたId
。
DROP TABLE IF EXISTS dbo.MyTable;
CREATE TABLE dbo.MyTable (
Id uniqueidentifier,
SomeTimestamp DATETIME2,
SomeInt INT,
SomeBit BIT,
FILLER VARCHAR(100)
);
INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT NEWID(), CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT '3A35EA17-CE7E-4637-8319-4C517B6E48CA', CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
このクエリは、最初は少し奇妙に見えるかもしれません。
DECLARE @Id UNIQUEIDENTIFIER = '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
SELECT
@Id,
st.SomeTimestamp,
si.SomeInt
FROM (
SELECT TOP (1) SomeInt, Id
FROM dbo.MyTable
WHERE Id = @Id
AND SomeBit = 1
ORDER BY SomeInt DESC
) si
CROSS JOIN (
SELECT TOP (1) SomeTimestamp, Id
FROM dbo.MyTable
WHERE Id = @Id
AND SomeBit = 1
ORDER BY SomeTimestamp ASC
) st;
インデックスの順序を利用して、いくつかの論理読み取りで最小値または最大値を見つけるように設計されています。CROSS JOIN
以下のため、一致する行が存在しない場合に正しい結果を得るためにそこにある@Id
値が。テーブルで最も人気のある値(650万行に一致)でフィルタリングしても、8つの論理読み取りしか得られません。
テーブル「MyTable」。スキャンカウント2、論理読み取り8
クエリプランは次のとおりです。
両方のインデックスシークは、0行または1行を見つけます。非常に効率的ですが、2つのインデックスを作成することは、シナリオにとってはやり過ぎかもしれません。代わりに、次のインデックスを検討できます。
CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;
これで、元のクエリのクエリプラン(MAXDOP 1
ヒントはオプション)は少し異なります。
キー検索は不要になりました。すべての入力に対して適切に機能するより良いアクセスパスを使用すると、密度ベクトルのためにオプティマイザーが誤ったクエリプランを選択することを心配する必要はありません。ただし、一般的な@Id
値を検索する場合、このクエリとインデックスは他のクエリとインデックスほど効率的ではありません。
テーブル「MyTable」。スキャンカウント1、論理読み取り33757