次のシナリオでインデックス付きビューを設定して、2つのクラスター化インデックススキャンなしで次のクエリが実行されるようにしています。このクエリのインデックスビューを作成して使用するときはいつでも、それに付けたインデックスはすべて無視されるようです。
-- +++ THE QUERY THAT I WANT TO IMPROVE PERFORMANCE-WISE +++
SELECT TOP 1 *
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
ORDER BY t1.somethingelse1
,t2.somethingelse2;
GO
テーブルの設定は次のとおりです。
(以下のスクリプトには、問題の再現に役立つ場合に備えて、テストデータを生成するための行もあります)
-- +++ TABLE SETUP +++
CREATE TABLE [dbo].[TB_test1]
(
[PK_ID1] [INT] IDENTITY(1, 1) NOT NULL
,[something1] VARCHAR(40) NOT NULL
,[somethingelse1] BIGINT NOT NULL
CONSTRAINT [PK_TB_test1] PRIMARY KEY CLUSTERED ( [PK_ID1] ASC )
);
GO
create TABLE [dbo].[TB_test2]
(
[PK_ID2] [INT] IDENTITY(1, 1) NOT NULL
,[FK_ID1] [INT] NOT NULL
,[something2] VARCHAR(40) NOT NULL
,[somethingelse2] BIGINT NOT NULL
CONSTRAINT [PK_TB_test2] PRIMARY KEY CLUSTERED ( [PK_ID2] ASC )
);
GO
ALTER TABLE [dbo].[TB_test2] WITH CHECK ADD CONSTRAINT [FK_TB_Test1] FOREIGN KEY([FK_ID1])
REFERENCES [dbo].[TB_test1] ([PK_ID1])
GO
ALTER TABLE [dbo].[TB_test2] CHECK CONSTRAINT [FK_TB_Test1]
GO
-- +++ TABLE DATA GENERATION +++
-- this might not be the quickest way, but it's only to set up test data
INSERT INTO dbo.TB_test1
( something1, somethingelse1 )
VALUES ( CONVERT(VARCHAR(40), NEWID()) -- something1 - varchar(40)
,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1) -- somethingelse1 - bigint
)
GO 100000
RAISERROR( 'Finished setting up dbo.TB_test1', 0, 1) WITH NOWAIT
GO
INSERT INTO dbo.TB_test2
( FK_ID1, something2, somethingelse2 )
VALUES ( ISNULL(ABS(CHECKSUM(NewId())) % ((SELECT MAX(PK_ID1) FROM dbo.TB_test1) - 1), 0) + 1 -- FK_ID1 - int
,CONVERT(VARCHAR(40), NEWID()) -- something2 - varchar(40)
,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1) -- somethingelse2 - bigint
)
GO 100000
RAISERROR( 'Finished setting up dbo.TB_test2', 0, 1) WITH NOWAIT
GO
インデックス付きビューはおそらく次のように定義する必要があり、結果のTOP 1クエリは以下のとおりです。しかし、このクエリがインデックス付きビューがない場合よりもパフォーマンスを向上させるには、どのインデックスが必要ですか?
CREATE VIEW VI_test
WITH SCHEMABINDING
AS
SELECT t1.PK_ID1
,t1.something1
,t1.somethingelse1
,t2.PK_ID2
,t2.FK_ID1
,t2.something2
,t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
GO
SELECT TOP 1 * FROM dbo.VI_test ORDER BY somethingelse1,somethingelse2
GO
それが私がそれに置いたインデックスを無視しているようです
SQL Server Enterprise Edition(または同等のTrial and Developer)を使用している場合を除き、ビュー参照でWITH (NOEXPAND)
を使用して使用する必要があります。実際、Enterpriseを使用している場合でも、 そのヒントを使用する十分な理由 があります。
ヒントがないと、(Enterprise Editionの)クエリオプティマイザーは、マテリアライズドビューを使用するか、ベーステーブルにアクセスするかをコストベースで選択できます。ビューがベーステーブルと同じ大きさの場合、この計算ではベーステーブルが優先されます。
もう1つの興味深い点は、NOEXPAND
ヒントがないと、ビュー参照は常に最適化が始まる前に基本クエリに展開されるということです。最適化の進行に伴い、オプティマイザーは、以前の最適化アクティビティに応じて、拡張された定義をマテリアライズドビューに戻すことができる場合とできない場合があります。これはほとんどの場合、単純なクエリには当てはまりませんが、完全を期すために言及します。
したがって、NOEXPAND
テーブルヒントを使用することが主なオプションですが、ビューでの順序付けに必要なベーステーブルキーと列を具体化することも考えられます。結合されたキー列に一意のクラスター化インデックスを作成してから、順序付け列に個別の非クラスター化インデックスを作成します。
これにより、マテリアライズドビューのサイズが小さくなり、ビューとベーステーブルの同期を維持するために必要な自動更新の数が制限されます。次に、クエリを作成して、ビューから上位1つのキーを必要な順序で(理想的にはNOEXPAND
を使用して)フェッチし、ベーステーブルに結合して、ビューのキーを使用して残りの列をフェッチします。
もう1つのバリエーションは、順序付けされた列とテーブルキーでビューをクラスター化し、キーを使用してベーステーブルから非ビュー列を手動でフェッチするクエリを記述します。最適なオプションは、幅広いコンテキストによって異なります。決定する良い方法は、実際のデータとワークロードでテストすることです。
CREATE VIEW VI_test
WITH SCHEMABINDING
AS
SELECT
t1.PK_ID1,
t1.something1,
t1.somethingelse1,
t2.PK_ID2,
t2.FK_ID1,
t2.something2,
t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2
ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Brute force unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(somethingelse1, somethingelse2, PK_ID1, PK_ID2);
GO
SELECT TOP (1) *
FROM dbo.VI_test WITH (NOEXPAND)
ORDER BY somethingelse1,somethingelse2;
実行計画:
-- Minimal unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(PK_ID1, PK_ID2)
WITH (DROP_EXISTING = ON);
GO
-- Nonclustered index for ordering
CREATE NONCLUSTERED INDEX ix
ON dbo.VI_test (somethingelse1, somethingelse2);
実行計画:
このプランにはルックアップがありますが、これは単一の行をフェッチするためにのみ使用されます。
ALTER VIEW VI_test
WITH SCHEMABINDING
AS
SELECT
t1.PK_ID1,
t2.PK_ID2,
t1.somethingelse1,
t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2
ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(somethingelse1, somethingelse2, PK_ID1, PK_ID2);
クエリ:
SELECT TOP (1)
V.PK_ID1,
TT1.something1,
V.somethingelse1,
V.PK_ID2,
TT2.FK_ID1,
TT2.something2,
V.somethingelse2
FROM dbo.VI_test AS V WITH (NOEXPAND)
JOIN dbo.TB_test1 AS TT1 ON TT1.PK_ID1 = V.PK_ID1
JOIN dbo.TB_test2 AS TT2 ON TT2.PK_ID2 = V.PK_ID2
ORDER BY somethingelse1,somethingelse2;
実行計画:
これは、テーブルキーが取得され(ビューのクラスター化インデックスから順に単一行がフェッチされる)、続いてベーステーブルで単一行を2回検索して残りの列をフェッチすることを示しています。