高選択性フィールドと低選択性フィールドを持つ複合インデックス順のフィールド順


11

30億行を超えるSQL Serverテーブルがあります。クエリの1つに非常に長い時間がかかるため、最適化を検討しています。クエリは次のようになります。

SELECT [Enroll_Date]
      ,Count(*) AS [Record #]
      ,Count(Distinct UserID) AS [User #]
  FROM UserTable
  GROUP BY [Enroll_Date]

[Enroll_Date]は選択可能な値が50未満の選択性の低い列ですが、UserID列は2億を超える個別の値を持つ選択性の高い列です。私の研究に基づいて、私はこれらの2つの列に非クラスター化複合インデックスを作成する必要があると考えています。理論的には、高選択性の列が最初の列である必要があります。しかし、私の場合、group by句で低選択性カラムを使用しているため、うまくいくかどうかはわかりません。

このテーブルにはクラスター化インデックスがありません。


実際の実行プランのxmlを投稿できますか(pastebinを使用してここにリンクしてください)?使用しているSQLサーバーのバージョンは何ですか?
Kin Shah

3
選択性の高い列を最初に持つインデックスは、特定のクエリでは役に立たなくなります。
ypercubeᵀᴹ

(通常)インデックスの最初のキー列として選択性の高い列を使用することをお勧めします。このシナリオでは、ご想像のとおり、まったく役に立ちません。2つのインデックスが必要になる場合があります。最初にenroll_dateを使用し、次にuser_idを使用するとどうなりますか?
ポールバービン2016

回答:


12

@AaronBertrandのソリューションの代替手段として(インデックス付きビューを作成できない場合、または作成したくない場合)、にインデックスを作成することをお勧めします(Enroll_Date, UserID)。このタイプの質問がテーブルで非常に一般的である場合、これはおそらくクラスター化インデックスであるべきです。

一般に、選択性の高いインデックスを一般的な「ベストプラクティス」としてお勧めするのではなく、どのインデックスがクエリに最高のパフォーマンスをもたらすかを調べます。

インデックスをオンに(Enroll_Date, UserID)すると、ストリーム集約を使用した高度に最適化された非ブロッククエリプランがクエリに提供されます。

ストリーム集計クエリプラン

このコンテキストでの「非ブロッキング」とは、クエリが大量のデータをバッファリングする必要がないことを意味します(たとえば、並べ替えやハッシュ集計のように)、(a)すぐに行を返し始め、( b)作業メモリを実質的に消費しない。


おかしい、4秒間隔で同じ答え。
usr

11

アーロンズの答えは素晴らしい解決策です。あなたがそのアプローチをとりたくないと仮定して、質問に答えます。

通常、投稿したクエリは、最初にをグループ化し(Enroll_Date, UserID)、次にをグループ化することによって実行され(Enroll_Date)ます。この最適化はSQL Server 2012の新機能COUNT DISTINCTです。単一のの場合に有効になります。

特定の順序でのこれら2つの列のインデックスは(Enroll_Date, UserID)、インデックススキャンを2つの連続するストリーム集約に集める効率的な計画を取得するのに十分です。反対の順序では、その計画は有効になりません。

したがって、順序を使用し(Enroll_Date, UserID)ます。ここには選択の余地はありません。


5秒間隔で同じソリューション。よく演奏されました。:)
Daniel Hutmacher

@DanielHutmacher OMG、3回目の投稿とほぼ一致するようになりますか?あなたに+1!どうすれば同じ答えを賛成できませんか?
usr

マトリックスのグリッチ。:)
Daniel Hutmacher

どうもありがとうございました。私はインデックスを作成しており、完了したら改善を投稿します。サーバーのバージョンはAWS上のMicrosoft SQL Server 2008 R2ですが、それでも、それが唯一の選択です。
Thinkinger

アーロンズのアプローチを受け入れない場合の@Thinkingerには、難しい選択があります:)
usr

11

インデックス付きビューの理想的なシナリオのように聞こえます。これにより、クエリ時間ではなく書き込み時間に計算と集計の料金を支払うことができます。

CREATE VIEW dbo.MyIndexedView
WITH SCHEMABINDING
AS 
  SELECT Enroll_Date, UserID, RawCount = COUNT_BIG(*)
  FROM dbo.UserTable
  GROUP BY Enroll_Date, UserID;
GO

CREATE UNIQUE CLUSTERED INDEX CIX_miv ON dbo.MyIndexedView(Enroll_Date, UserID);

これを作成するには時間がかかります。もちろん、ベーステーブルのインデックスと同じように、すべてのDML操作全体でメンテナンスが必要になります。

これで、このビューに対するクエリは非常によく似たものになります。ビューの各行は個別のユーザー/日付の組み合わせを表すため、ベーステーブルの行の総数は1であるCOUNT(*)で計算できます。すでに部分的に集計されていますが、日付ごとのSUMを使用してそれらを追加するだけです。

SELECT Enroll_Date, 
  [Record #] = SUM(RawCount),
  [User #] = COUNT(*)
FROM dbo.MyIndexedView WITH (NOEXPAND)
GROUP BY Enroll_Date; 

これthisを思い出した後、NOEXPANDヒントを追加しました。

このクエリが現在のクエリよりも高速であることは間違いありません(ただし、その程度ではありません)。ただし、日付ごとに正確に1人のユーザーがいるというまれなケース(この場合、同じ量のデータには読み取り)と私たちが知っている列は、ベーステーブルのインデックス内の唯一の列です。読み取り時のパフォーマンスの向上が、ワークロードの書き込み部分に影響を与える余分な作業に見合うかどうかは、私たちには言えません-トレードオフを測定するためにテストする必要があります(インデックスは無料です)。

また、特定の明確に定義された範囲(たとえば、現在の四半期または年初から現在まで)に対してEnroll_Dateに対して同じ共通のWHERE句を頻繁に使用する場合は、I / Oをさらに削減する一致するフィルター処理されたインデックスを追加できます(ただし、常にトレード・オフ)。

また、クラスター化インデックスをベーステーブルに配置することも検討してください。これは、ヒープの恩恵を受ける非常にまれなユースケースの1つではないようです。


ITで確認したところ、このようなビューを作成できないようです。しかし、それでもあなたのアドバイスを明記してください、そしてそれはそれを使うことができる他の人を助けるでしょう。
Thinkinger

1
ITは、インデックス付きビューとベーステーブルの追加のインデックスまたは異なるインデックスの間に大きな違いがあると思いますか?多くの人がインデックス付きビューについて誤解しているので、好奇心旺盛ではありません。私はそれらをテーブルの追加のより細いクラスター化インデックスと考えたいが、行数は少ない。
アーロンバートランド

@Thinkingerも、インデックス付きビューはEEのみではありません。インデックス付きビューのマッチングはEEのみです。NOEXPANDを使用してそれらを直接ターゲティングできます。
usr
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.