集計クエリが_GROUP BY
_句を使用した場合よりも使用した場合の方が速くなる理由を知りたいと思います。
たとえば、このクエリは実行に約10秒かかります
_SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
_
これは1秒未満かかりますが
_SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate
_
この場合、CreatedDate
は1つだけなので、グループ化されたクエリは、グループ化されていないクエリと同じ結果を返します。
2つのクエリの実行プランが異なることに気付きました。2番目のクエリはParallelismを使用していますが、最初のクエリはそうではありません。
GROUP BY句がない場合、SQLサーバーが集計クエリを異なる方法で評価するのは正常ですか?そして、_GROUP BY
_句を使用せずに最初のクエリのパフォーマンスを向上させるためにできることはありますか?
編集
OPTION(querytraceon 8649)
を使用して並列処理のコストオーバーヘッドを0に設定できることを学びました。これにより、クエリで並列処理が使用され、実行時間が2秒に短縮されます。このクエリヒントを使用します。
_SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)
_
クエリはユーザーが選択したときに値を入力することを目的としているため、グループ化されたクエリのように瞬時に実行するのが理想的です。現在、クエリをラップしているだけですが、それが理想的な解決策ではないことはわかっています。
_SELECT Min(CreatedDate)
FROM
(
SELECT Min(CreatedDate) as CreatedDate
FROM MyTable WITH (NOLOCK)
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate
) as T
_
#2を編集
Martinの詳細情報のリクエスト への応答:
CreatedDate
とSomeIndexedValue
の両方に、個別の非一意の非クラスター化インデックスがあります。 SomeIndexedValue
は、別のテーブルのPK(int)を指す数値を格納しますが、実際にはvarchar(7)フィールドです。 2つのテーブル間の関係はデータベースで定義されていません。私はデータベースをまったく変更することは想定されておらず、データをクエリするクエリしか記述できません。
MyTable
には300万件を超えるレコードが含まれており、各レコードには所属するグループが割り当てられています(SomeIndexedValue
)。グループは、1〜200,000レコードの任意の場所にすることができます
おそらくCreatedDate
のインデックスを最低から最高まで順にたどり、ルックアップを行ってSomeIndexedValue = 1
述語を評価しているようです。
最初に一致する行が見つかると、それが行われますが、そのような行が見つかる前に、予想よりもはるかに多くのルックアップを実行している可能性があります(述語に一致する行が日付に従ってランダムに分散されていると想定しています)。
このクエリの理想的なインデックスは、SomeIndexedValue, CreatedDate
のインデックスです。これを追加できないか、少なくとも既存のインデックスをSomeIndexedValue
カバーCreatedDate
にインクルード列として作成できないと想定すると、クエリを次のように書き直すことができます。
SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1
その特定の計画を使用しないようにします。
MAXDOPを制御して、AdventureWorks.Production.TransactionHistoryなどの既知のテーブルを選択できますか?
私があなたのセットアップを繰り返すとき
--#1
SELECT MIN(TransactionDate)
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001
OPTION( MAXDOP 1) ;
--#2
SELECT MIN(TransactionDate)
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO
コストは同じです。
余談ですが、私はインデックス付けされた値でインデックスがシークすることを期待します(実現させます)。そうしないと、ストリーム集約ではなくハッシュ一致が表示される可能性があります。集計する値を含む非クラスター化インデックスを使用してパフォーマンスを改善したり、集計を列として定義するインデックス付きビューを作成したりできます。次に、インデックス付きIDによって、集計を含むクラスター化インデックスにヒットします。 SQL標準では、ビューを作成してWITH(NOEXPAND)ヒントを使用するだけです。
例(インデックス付きビューでは機能しないため、MINは使用していません):
USE AdventureWorks ;
GO
-- Covering Index with Include
CREATE INDEX IX_CoverAndInclude
ON Production.TransactionHistory(TransactionDate)
INCLUDE (Quantity) ;
GO
-- Indexed View
CREATE VIEW dbo.SumofQtyByTransDate
WITH SCHEMABINDING
AS
SELECT
TransactionDate
, COUNT_BIG(*) AS NumberOfTransactions
, SUM(Quantity) AS TotalTransactions
FROM Production.TransactionHistory
GROUP BY TransactionDate ;
GO
CREATE UNIQUE CLUSTERED INDEX SumofAllChargesIndex
ON dbo.SumofQtyByTransDate (TransactionDate) ;
GO
--#1
SELECT SUM(Quantity)
FROM AdventureWorks.Production.TransactionHistory
WITH (INDEX(0))
WHERE TransactionID = 100001
OPTION( MAXDOP 1) ;
--#2
SELECT SUM(Quantity)
FROM AdventureWorks.Production.TransactionHistory
WITH (INDEX(IX_CoverAndInclude))
WHERE TransactionID = 100001
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO
--#3
SELECT SUM(Quantity)
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO
私の意見では、問題の理由は、SQLサーバーオプティマイザーがBESTプランを探しているのではなく、適切なプランを探しているためです。並列処理を強制した後、クエリがはるかに速く実行されたという事実から明らかであり、オプティマイザーはそれ自体では行われません。
クエリを別の形式で書き換えることが並列化の違いである多くの状況も見ました(たとえば、SQLに関するほとんどの記事では、パラメーター化を推奨していますが、パラメーターのスニッフィングがnonと同じである場合でも、並列化が失敗することがあります-並列化されたもの、または2つのクエリをUNION ALLと組み合わせることで、並列化を排除できる場合があります。
そのため、正しい解決策は、一時テーブル、テーブル変数、CTE、派生テーブル、パラメータ化などのさまざまな方法でクエリを記述し、インデックス、インデックス付きビュー、またはフィルター処理されたインデックスを操作することです。最良の計画を得るために。