テーブルCustPassMaster
に16列あり、そのうちの1つはCustNum varchar(8)
です。また、インデックス_IX_dbo_CustPassMaster_CustNum
_を作成しました。 SELECT
ステートメントを実行すると:
_SELECT * FROM dbo.CustPassMaster WHERE CustNum = '12345678'
_
インデックスを完全に無視します。別のテーブルCustDataMaster
にさらに列(55)があり、その1つがCustNum varchar(8)
であるため、これは私を混乱させます。このテーブルのこの列(_IX_dbo_CustDataMaster_CustNum
_)にインデックスを作成し、実際には同じクエリを使用します。
_SELECT * FROM dbo.CustDataMaster WHERE CustNum = '12345678'
_
そして、私が作成したインデックスを使用します。
これの背後にある特定の理由はありますか? CustDataMaster
からのインデックスを使用せず、CustPassMaster
からのインデックスを使用しないのはなぜですか?列数が少ないためですか?
最初のクエリは66行を返します。 2番目の場合、1行が返されます。
また、追記:CustPassMaster
には4991レコードがあり、CustDataMaster
には5376レコードがあります。これがインデックスを無視する理由になりますか? CustPassMaster
には、同じCustNum
値を持つ重複レコードもあります。これは別の要因ですか?
この主張は、両方のクエリの実際の実行プランの結果に基づいています。
CustPassMaster
(未使用のインデックスを持つもの)のDDLは次のとおりです。
_CREATE TABLE dbo.CustPassMaster(
[CustNum] [varchar](8) NOT NULL,
[Username] [char](15) NOT NULL,
[Password] [char](15) NOT NULL,
/* more columns here */
[VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_dbo_CustPassMaster_CustNum] ON dbo.CustPassMaster
(
[CustNum] ASC
) WITH (PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, SORT_IN_TEMPDB = OFF
, DROP_EXISTING = OFF
, ONLINE = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
_
そして、CustDataMaster
のDDL(私は多くの無関係なフィールドを省略しました):
_CREATE TABLE dbo.CustDataMaster(
[CustNum] [varchar](8) NOT NULL,
/* more columns here */
[VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_dbo_CustDataMaster_CustNum] ON dbo.CustDataMaster
(
[CustNum] ASC
)WITH (PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, SORT_IN_TEMPDB = OFF
, DROP_EXISTING = OFF
, ONLINE = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
_
これらのテーブルのいずれにもクラスター化インデックスはなく、1つの非クラスター化インデックスしかありません。
データ型が格納されているデータの型と完全に一致しないという事実は無視してください。これらのフィールドはIBM AS/400 DB2データベースからのバックアップであり、これらは互換性のあるデータ型です。 (私はexact sameクエリでこのバックアップデータベースにクエリを実行し、-exact same結果を取得できる必要があります。)
このデータはonlyであり、SELECT
ステートメントに使用されます。バックアップアプリケーションがAS/400からデータをコピーしている場合を除いて、INSERT
/UPDATE
/DELETE
ステートメントを実行しません。
通常、SQL Serverは、基になるテーブルを直接使用するよりも、インデックスを使用する方が適切であると判断した場合に、インデックスを使用します。
コストベースのオプティマイザは、問題のインデックスを実際に使用する方がコストがかかると考えているようです。 SELECT *
を実行する代わりに、単にSELECT T1Col1
を使用すると、インデックスが使用される場合があります。
SELECT *
とは、SQL Serverにテーブルのすべての列を返すように指示することです。これらの列を返すには、SQL Server mustWHERE
ステートメントの条件に一致する行のページをテーブル自体(クラスター化インデックスまたはヒープ)から読み取ります。 SQL Serverは、テーブルから残りの列を取得するために必要な読み取り量を考えているため、テーブルを直接スキャンすることもできます。実際のクエリと、クエリで使用される実際の実行プランを確認すると便利です。
インデックスを使用するため、select *
の場合、SQL Serverは最初に、where句にある値と一致するインデックスから各行を読み取る必要があります。これに基づいて、各行のクラスター化インデックス値を取得し、クラスター化インデックス(=キー検索)とは別に各値をシークする必要があります。値は一意ではないというので、SQL Serverは統計を使用して、このキールックアップを実行する必要がある回数を推定します。
ほとんどの場合、非クラスター化インデックス+キールックアップのスキャンの推定コストは、クラスター化インデックスのスキャンの推定コストを上回っています。そのため、インデックスは無視されます。
set statistics io on
次に、インデックスヒントを使用して、インデックスを使用する場合のI/Oコストが実際に小さいかどうかを確認します。差が大きい場合、統計が古くなっている場合は統計を調べることができます。
また、SQLが正確な値ではなく実際に変数を使用している場合、これはパラメータスニッフィング(=プランの作成に使用された以前の値がテーブルに多数の行を含んでいた)が原因である可能性もあります。
それが理由かもしれません。オプティマイザはコストベースであり、各実行パスの「コスト」に基づいて、選択するパスを決定します。 「最大」のコストは、ディスクからメモリにデータを取得することです。オプティマイザが、インデックスとデータの両方を読み取るのに時間がかかると計算した場合、インデックスをスキップすることがあります。行が大きいほど、取得するディスクブロックが多くなります。