web-dev-qa-db-ja.com

SQL Serverがインデックスを無視するのはなぜですか?

テーブル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ステートメントを実行しません。

16
Der Kommissar

通常、SQL Serverは、基になるテーブルを直接使用するよりも、インデックスを使用する方が適切であると判断した場合に、インデックスを使用します。

コストベースのオプティマイザは、問題のインデックスを実際に使用する方がコストがかかると考えているようです。 SELECT *を実行する代わりに、単にSELECT T1Col1を使用すると、インデックスが使用される場合があります。

SELECT *とは、SQL Serverにテーブルのすべての列を返すように指示することです。これらの列を返すには、SQL Server mustWHEREステートメントの条件に一致する行のページをテーブル自体(クラスター化インデックスまたはヒープ)から読み取ります。 SQL Serverは、テーブルから残りの列を取得するために必要な読み取り量を考えているため、テーブルを直接スキャンすることもできます。実際のクエリと、クエリで使用される実際の実行プランを確認すると便利です。

18
Max Vernon

インデックスを使用するため、select *の場合、SQL Serverは最初に、where句にある値と一致するインデックスから各行を読み取る必要があります。これに基づいて、各行のクラスター化インデックス値を取得し、クラスター化インデックス(=キー検索)とは別に各値をシークする必要があります。値は一意ではないというので、SQL Serverは統計を使用して、このキールックアップを実行する必要がある回数を推定します。

ほとんどの場合、非クラスター化インデックス+キールックアップのスキャンの推定コストは、クラスター化インデックスのスキャンの推定コストを上回っています。そのため、インデックスは無視されます。

set statistics io on次に、インデックスヒントを使用して、インデックスを使用する場合のI/Oコストが実際に小さいかどうかを確認します。差が大きい場合、統計が古くなっている場合は統計を調べることができます。

また、SQLが正確な値ではなく実際に変数を使用している場合、これはパラメータスニッフィング(=プランの作成に使用された以前の値がテーブルに多数の行を含んでいた)が原因である可能性もあります。

10
James Z

それが理由かもしれません。オプティマイザはコストベースであり、各実行パスの「コスト」に基づいて、選択するパスを決定します。 「最大」のコストは、ディスクからメモリにデータを取得することです。オプティマイザが、インデックスとデータの両方を読み取るのに時間がかかると計算した場合、インデックスをスキップすることがあります。行が大きいほど、取得するディスクブロックが多くなります。

1
Marco