web-dev-qa-db-ja.com

日付範囲クエリのSQLインデックス

数日間、データベースのパフォーマンスの改善に苦労してきましたが、SQL Serverデータベースでのインデックス作成に関して、まだ混乱している問題がいくつかあります。

できる限り参考になるように努めます。

私のデータベースには現在約10万行が含まれており、今後も成長し続けるので、より高速に動作させる方法を見つけようとしています。

私もこの表に書き込んでいるので、提案によって書き込み時間が大幅に短縮される場合は、お知らせください。

全体的な目標は、日付範囲内にある特定の名前を持つすべての行を選択することです。

それは通常、たくさんの中から3,000行以上を選択することです...

テーブルスキーマ:

CREATE TABLE [dbo].[reports]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [IsDuplicate] [bit] NOT NULL,
    [IsNotValid] [bit] NOT NULL,
    [Time] [datetime] NOT NULL,
    [ShortDate] [date] NOT NULL,
    [Source] [nvarchar](350) NULL,
    [Email] [nvarchar](350) NULL,

    CONSTRAINT [PK_dbo.reports] 
        PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]

これは私が使用しているSQLクエリです。

SELECT * 
FROM [db].[dbo].[reports]
WHERE Source = 'name1' 
  AND ShortDate BETWEEN '2017-10-13' AND '2017-10-15'

私が理解したように、書き込み時間をそれほど損なうことなく効率を改善するための私の最善のアプローチは、SourceShortDateに非クラスター化インデックスを作成することです。

私がそうしたのは、インデックススキーマです。

CREATE NONCLUSTERED INDEX [Source&Time] 
ON [dbo].[reports]([Source] ASC, [ShortDate] ASC)

今、私たちは私を完全に失ったトリッキーな部分に到達しています、上記のインデックスは時々機能し、時々半分は機能し、時々まったく機能しません...

(それが重要であるかどうかはわかりませんが、現在データベース行の90%が同じソースを持っていますが、これは長い間そうではありません)

  1. 以下のクエリでは、インデックスはまったく使用されていません。SQLServer 2014を使用しています。実行プランでは、クラスター化インデックススキャンのみを使用していると記載されています。

    SELECT * 
    FROM [db].[dbo].[reports]
    WHERE Source = 'name1' 
      AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
    
  2. このクエリでは、インデックスはまったく使用されませんが、SQL Serverから、最初に日付、2番目にソースのインデックスを作成するように提案されています...インデックスはクエリの順序で作成する必要があると読みましたですか?また、選択しているすべての列を含めると表示されていますが、これは必須ですか?...もう一度、検索している列のみをインデックスに含める必要があることを読みました。

    SELECT * 
    FROM [db].[dbo].[reports]
    WHERE Source = 'name1' 
      AND ShortDate = '2017-10-13'
    

    SQL Serverインデックスの提案-

    /* The Query Processor estimates that implementing the following 
       index could improve the query cost by 86.2728%. */
    
    /*
    USE [db]
    GO
    
    CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
    ON [dbo].[reports] ([ShortDate], [Source])
    INCLUDE ([id], [IsDuplicate], [IsNotValid], [Time], [Email])
    GO
    */
    

SQL Serverで作成するように提案されたインデックスを使用してみましたが、機能します。上記の両方のクエリを使用して、非クラスター化インデックスを100%使用しているようです。

このインデックスを使用しようとしましたが、含まれている列を削除すると機能しません...選択しているすべての列をインデックスに含める必要があるようです。

ところで、すべての列を含めれば、作成したインデックスを使用するときにも機能します。

要約すると、Source + ShortDateShortDate + Sourceを作成するときに両方とも機能したため、インデックスの順序は問題ではないようです。

しかし、何らかの理由で、すべての列を含める必要があります...(これは、このテーブルへの書き込みに大幅に影響しますか?)

読んでくれてありがとうございます。私の目標は、このようなことが起こる理由と他に何をすべきかを理解することです(他のプロジェクトにも適用する必要があるため、解決策だけではありません)。

乾杯:)

10
Ben

SQL Serverでのインデックス作成は、長い経験(および何時間ものフラストレーション)からのノウハウであり、黒魔術です。それ以上に打ちのめしてはいけません-それはSOのような場所が理想的です-多くの頭脳、何時間もの最適化からの多くの経験、あなたが利用できるものです。

インデックスはクエリの順序で作成する必要があると読みましたか?

これを読んだ場合-それは完全にNOT TRUE-列の順序は適切です-異なる方法で:クエリのインデックス定義でn左端の列を指定した場合にのみ、複合インデックス(複数の列で構成される)が考慮されます。

古典的な例:(都市、姓、名)のインデックスが付いた電話帳。このようなインデックスが使用される可能性があります

  • WHERE句で3つの列すべてを指定するクエリ内
  • cityおよびlastnameを使用するクエリ内( "Detroit"内のすべての "Miller"を検索)
  • または都市のみでフィルタリングするクエリ

NEVER EVERfirstname ...... that'sのみを検索する場合に使用できます。注意する必要がある複合インデックスに関するトリック。ただし、常にインデックスのすべての列を使用する場合、通常、その順序は実際には関係ありません。クエリオプティマイザーがこれを処理します。


含まれる列-それらは非クラスター化インデックスのリーフレベルに格納されますonly[〜#〜] not [〜#〜]ですインデックスの検索構造の一部であり、WHERE句に含まれている列のフィルター値を指定することはできません。

これらの含まれている列の主な利点は次のとおりです。非クラスター化インデックスを検索すると、最終的に探している値が実際に見つかります。その時点で何が利用できますか?非クラスター化インデックスは、非クラスター化インデックス定義(ShortDateSource)に列を格納し、クラスタリングキーを格納します(ある場合-とすべき! =)-他には何もありません。

したがって、この場合、一致が見つかり、クエリがそのテーブルからeverythingを要求すると、SQL ServerはKey lookupと呼ばれる処理を実行する必要があります(多くの場合、 ブックマークルックアップ)として、クラスター化されたキーを受け取り、クラスター化されたインデックスに対してSeek操作を実行して、あなたが探しているすべての値を含む実際のデータページに。

インデックスに含まれる列がある場合、non-clusteredインデックスのリーフレベルページには

  • 非クラスター化インデックスで定義された列
  • クラスタリングキー列
  • これらすべての追加の列INCLUDEステートメントで定義されているとおり

これらの列がクエリを「カバー」する場合。クエリに必要なすべての値を提供し、非クラスター化インデックスで検索した値が見つかるとSQL Serverが実行されます-非クラスター化インデックスのリーフレベルページから必要なすべての値を取得できます- 不要実際の値を取得するために、クラスタリングインデックスへの別の(高価な)キー検索を実行します。

このため、常に明示的に指定SELECTで本当に本当に必要な列のみを試みると有益です-この場合、あなたはあなたのSELECTのすべての値を提供する効率的なカバーインデックスを作成できるかもしれません-常にSELECT *を使用すると本当に難しいか、不可能に近い.....

13
marc_s

一般に、インデックスを最も選択的なもの(つまり、最も可能性の高いレコードを除外する)から最も選択性の低いものにしたいとします。列のカーディナリティが低い場合、クエリオプティマイザーはそれを無視することがあります。

これは直感的に理解できます。電話帳を持っていて、 "smith"という名前を探し、最初に "A"を使用している場合は、最初に "smith"を検索し、次に "A"を検索します。 、頭文字が「A」であるすべての人ではなく、「Smith」と呼ばれる人を除外します。結局、オッズは26人に1人が頭文字「A」を持っているということです。

したがって、あなたの例では、短い日付の値の範囲が広いと思います。これが、クエリオプティマイザが除外しようとしている最初の列です。 「ソース」にいくつかの異なる値があるため、クエリオプティマイザーはそれを無視する場合があります。その場合、そのインデックスの2番目の列も使用されません。

インデックス内のwhere句の順序は重要ではありません。それらをラウンドスワップしてまったく同じ結果を得ることができるため、クエリオプティマイザーはそれらを無視します。

編集:

だから、はい、インデックスを作成します。並べ替えるカードの山があるとします。最初の実行では、できるだけ多くのカードを削除したいとします。すべてが均等に分散していると想定します。100万行を超える1000個のshort_dateがある場合、最初の実行がshort_dateで開始されると、最終的に1000アイテムになります。ソースで並べ替えると、100000行になります。

1
Neville Kuyt

インデックスに含まれる列は、選択している列用です。あなたがするという事実のためにselect *(これは良い方法ではありません)、列の値を取得するためにテーブル全体を検索する必要があるため、インデックスは使用されません。

あなたのシナリオでは、デフォルトのクラスター化インデックス(存在する場合)を削除し、次のステートメントで新しいクラスター化インデックスを作成します。

USE [db]
GO
CREATE CLUSTERED INDEX CIX_reports
    ON [dbo].[reports] ([ShortDate],[Source])
GO
0