web-dev-qa-db-ja.com

SQL Serverは使用するインデックスをどのように選択するか

私は最近、DBのさまざまなビューのインデックス作成に取り組み始めました。多くの場合、これらのテーブルの一部は結合にのみ使用され、ステートメントのwhereまたはorderで使用されることはまれです。つまり、すべて主キーをキーとする複数のインデックス(または比較的効率の低い1つの大きなインデックス)を作成しています。このテーブルから何かを含めることによる場所や順序がなく、問題が発生しているビューにキールックアップがないことを明確にする必要があります。

SQLにすべてtbl.IDをキーとする複数のインデックスがある場合、1つのインデックスを他のインデックスよりも使用することをどのように選択しますか?インデックスから引き出されたデータが同じであっても、ビュー間で異なる可能性があることがわかりました。一般に、あるインデックスを他のインデックスよりも選択しても、効率に大きな利益/損失はありません。 「十分に良い」インデックスを選択して次に進むと私に信じさせます(同じ列から多くのキーオフがあるため)。

さらに、この理由により、同じキーで複数のインデックスを作成することは避けるべきですか?

私が現在扱っている特定の問題には、次のようなインデックスを選択するという見方があります。

CREATE NONCLUSTERED INDEX [index1]
ON a.tbl ([ID])
INCLUDE (number,name,year, ...)

インクルードに14列がある場合(残念ですが、現在のクエリ構造に基づいて必要です)

より効率的なインデックス:

CREATE NONCLUSTERED INDEX [index2]
ON a.tbl ([ID])
INCLUDE (number,name,year, ...)

インクルードに5列しかない場合。

どちらのインデックスにもビューに必要な情報がありますが、明らかに1つははるかに小さく、より効率的に使用できます。その中のすべての列(表の約半分)を呼び出す大規模なビューをカバーするには、より大きな索引が存在する必要があります。

上記のように1つの大規模なインデックスを作成することを選択できることはおそらく言及する価値があります。私のテストによると、これらの追加のインデックスを追加しても、テーブルの更新に大きなコストはかからないようです。パフォーマンスを少し向上させるために作成することにしました。一般に、これらは必要に応じて使用されますが、特定の場合、オプティマイザは明確な理由なしに効率の低いオプションを選択します。

7
Thomas D.

私の限られた実験では:

  • クエリがスキャンを実行する必要がある場合、SQL Serverは毎回最も狭いインデックスを検索する作業を行います。
  • クエリがシークを実行すると予想される場合、インデックスの幅は関係なく、作成された最後のカバーするインデックスを使用します。

これは理にかなっています。これらのいずれかの場合に完全なインデックスを検索するルールを追加する場合は、最も節約できるときに実行する必要があります。節約の観点からそれほど問題にならない場合に、より良いインデックスを見つけるために費やされる労力を求めてすべての行に対してとは対照的に行ごとにははるかに可能性が低くなります価値がある。 「最後に」はおそらく意図的なものであり、最後に作成したカバリングインデックスがおそらくあなたが作成した最高のものであるか、または任意で偶然である(欠落しているインデックス推奨の列の順序など)と想定しています。

証明(まあ、一種の証明)

私のテストのセットアップは非常に簡単でした:

CREATE TABLE dbo.t1
(
  id int IDENTITY(1,1) PRIMARY KEY,
  sn1 sysname, tn1 sysname, cn1 sysname, typ1 sysname,
  sn2 sysname, tn2 sysname, cn2 sysname, typ2 sysname,
  sn3 sysname, tn3 sysname, cn3 sysname, typ3 sysname,
  sn4 sysname, tn4 sysname, cn4 sysname, typ4 sysname
);
GO
SET NOCOUNT ON;
GO
INSERT dbo.t1
(
  sn1,tn1,cn1,typ1,sn2,tn2,cn2,typ2,
  sn3,tn3,cn3,typ3,sn4,tn4,cn4,typ4
)
SELECT s.name, t.name, c.name, typ.name,
       s.name, t.name, c.name, typ.name,
       s.name, t.name, c.name, typ.name,
       s.name, t.name, c.name, typ.name
FROM sys.schemas AS s
CROSS JOIN sys.objects AS t
INNER JOIN sys.all_columns AS c
ON t.[object_id] = c.[object_id]
INNER JOIN sys.types AS typ
ON c.user_type_id = typ.user_type_id;
GO

CREATE VIEW dbo.v1
AS
  SELECT id,sn1,tn1
  FROM dbo.t1;
GO

これにより、13,260行がテーブルに挿入されます(結果は異なります)。次に、同じ3つのインデックスを異なる順序で繰り返し作成しました。

-- widest first
CREATE INDEX ix_wide ON dbo.t1(sn1) 
  INCLUDE(tn1,cn1,typ1,sn2,tn2,cn2,typ2,sn3,tn3,cn3,typ3,sn4,tn4,cn4,typ4);
GO
CREATE INDEX ix_mid ON dbo.t1(sn1) INCLUDE(tn1,cn1,sn2,tn2,cn2,typ2,sn3,tn3);
GO
CREATE INDEX ix_small ON dbo.t1(sn1) INCLUDE(tn1,cn1);
GO

-- widest last = ix_small then ix_mid then ix_wide
-- middle1 = ix_mid then ix_wide then ix_small
-- middle2 = ix_small then ix_wide then ix_mid

次に、これらの4つのケースのそれぞれで、これら2つのクエリを実行して計画を調査しました。

DBCC FREEPROCCACHE;
GO
SELECT id,sn1,tn1 FROM dbo.v1; -- scan
GO
SELECT id,sn1,tn1 FROM dbo.v1 WHERE sn1 LIKE N'q%'; -- seek

結果:

         widest first  widest last  middle1   middle2
-------  ------------  -----------  --------  --------
   scan    ix_small      ix_small   ix_small  ix_small
   seek    ix_small      ix_wide    ix_small  ix_mid

スキャンは常に最も狭いインデックスを選択しました。シークは常に、最後に作成されたインデックスを選択しました(すべてのカバー以降)。この結果が変わるとは思わないので、テストに拡張して非カバーインデックスをミックスに含めることはしませんでした。

道徳(まあ、一種の道徳)

ここには2つの道徳があります:

  1. 最も狭いインデックスをシークで使用できるようにする場合は、最初に最も広いインデックスを作成します。
  2. これはおそらく、この最適化が価値のある狭い範囲のユースケースです。多くの行を返すシークなどand広いカバリングインデックスには、狭いインデックスにはない非常に大きな列があります。これらの場合、私がここで観察した動作に頼るのではなく、ヒント(および関連するすべての警告)を使用してインデックスを明示的に指定する価値があるかもしれません。

おそらく、Paulが明確にするためにここで見逃しているいくつかの内部とヒューリスティック。また、トレースフラグ302を有効にしてインデックス選択情報を出力しようとしましたが、これはSQL Serverの最新バージョンでは機能しないようです。

9
Aaron Bertrand