SQL Serverインデックスをテストしていたところ、非常に奇妙な動作が見つかりました。これが私のコードです:
DROP TABLE IF EXISTS dbo._Test
DROP TABLE IF EXISTS dbo._Newtest
GO
CREATE TABLE _Test(
ID INT NOT NULL,
UserSystemID INT NOT NULL,
Age INT
)
GO
INSERT INTO dbo._Test
( ID, UserSystemID, Age )
SELECT TOP 10000000 ABS(CHECKSUM(NEWID())) % 5000000, ABS(CHECKSUM(NEWID())) % 2, ABS(CHECKSUM(NEWID())) % 100
FROM sys.all_columns
CROSS JOIN sys.all_objects a
CROSS JOIN sys.all_objects b
CROSS JOIN sys.all_objects c
; WITH cte AS (
SELECT ID, UserSystemID, age, ROW_NUMBER() OVER(PARTITION BY ID, UserSystemID ORDER BY GETDATE()) rn
FROM dbo._Test
)
SELECT cte.ID ,
cte.UserSystemID ,
cte.Age
INTO _newTest
FROM cte
WHERE cte.rn = 1
CREATE UNIQUE NONCLUSTERED INDEX IX_test ON dbo._NewTest(ID, UserSystemID) INCLUDE(age)
GO
ALTER TABLE dbo._NewTest ADD CONSTRAINT PK_NewTest PRIMARY KEY CLUSTERED(UserSystemID, ID)
GO
この時点で、同じテーブルと同じ列に2つのインデックスがあります。 1つ目は非クラスター化で、2つ目はクラスター化されています。 Id
列はより選択的であり(約5000000の一意の値)、UserSystemID
は選択的ではありません(2つの一意の値)。
次に、次のクエリを実行して、使用されているインデックスをテストします。
SELECT id, UserSystemID, age
FROM _NewTest
WHERE id = 1502945
AND UserSystemID = 1
クラスタ化インデックスを探します。あなたは計画を見ることができます ここ 。
問題は、SQL Serverが一意の非クラスター化インデックスではなくクラスター化インデックスを優先する理由です。
クラスタ化インデックスの最初の列は、他の一意の非クラスタ化インデックスよりも選択性がはるかに低くなっています。したがって、クラスター化インデックスを使用するとパフォーマンスが低下するはずですが、実際にはそうではありません。
一意のインデックスを指定すると、クエリは最大で1行を選択します。
オプティマイザは、インデックスBツリーを1回だけ下降する必要があることを認識しており、そのポイントから前方または後方にスキャンしてさらに一致を見つける必要はありません。これは、シングルトンシーク(一意のインデックスの同等性テスト)として知られています。
現在のインデックスマッチングの実装では、シングルトンシークを使用できる場合は常にクラスター化インデックスを選択します。
ここでのクラスター化インデックスと非クラスター化インデックスの選択は、一般的にそれほど重要ではありません。 bツリーの上位レベルがナビゲートされるとtiny追加コストが発生する可能性があります(バイナリ検索または線形補間を使用)が、これを測定することも困難です。非リーフインデックスページには、ID
およびUserSystemID
キーコンポーネントのみが存在することに注意してください。
より広いクラスター化インデックスリーフページは、平均してメモリに存在する可能性が低いと主張できます。他にもいくつかの エッジの場合 の影響がありますが、この動作がすぐに変更されることはありません。
しかし、クラスター化インデックスの最初の列は、他の一意の非クラスター化インデックスよりも選択性がはるかに低くなっています。したがって、クラスター化インデックスを使用するとパフォーマンスが低下するはずですが、実際にはそうではありません。
選択性は、複合Bツリーインデックスの等価シークでは重要ではありません。
一意のクラスター化複合インデックスにはキー(UserSystemID、id)があります。
(UserSystemID = 1およびid = 1502945)の行を検索するために、SQL ServerはUserSystemID = 1のすべての行を検索するのではなく、id = 1502945の行を検索します。これは非常に非効率的です。
SET STATISTICS IO ON
を使用すると、テストクエリがタッチするページ数を確認できます。この例では、2つの非リーフレベルのクラスター化インデックスを作成します。全体として、目的の行を見つけるということは、インデックスの各レベルに1つずつ、合計3ページをタッチすることを意味します。
行は、インデックス内でUserSystemIDとIDの順に並べられます。デモテーブルの私のコピーは、クラスター化インデックスのルート(トップレベル)ページに次のレイアウトを持っています。
このページでバイナリ検索を実行するのは簡単です:
そのロジックに従って、検索されたキーが存在する場合に検索される子(次の下位レベル)インデックスページをすばやく見つけます。そのページでバイナリ検索を繰り返し、必要に応じて、探している行が含まれている必要がある単一のリーフレベルページに到達するまで繰り返します。