GROUP BY句を使用した場合よりも、GROUP BY句を使用した場合の方が、集計クエリが大幅に高速になるのはなぜですか?


12

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番目のクエリは並列処理を使用しますが、最初のクエリは使用しません。

Query1実行計画 Query2実行計画

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

詳細情報のマーティンの要求に応えて:

両方CreatedDateSomeIndexedValueそれらの上に別の非ユニークな、非クラスタ化インデックスを持っています。SomeIndexedValue別のテーブルのPK(int)を指す数値を格納している場合でも、実際にはvarchar(7)フィールドです。2つのテーブル間の関係は、データベースで定義されていません。データベースを変更することはまったく想定されておらず、データをクエリするクエリのみを記述できます。

MyTableには300万件を超えるレコードが含まれており、各レコードには所属するグループが割り当てられています(SomeIndexedValue)。グループは、1〜200,000レコードの任意の場所にできます。

回答:


8

おそらくインデックスをCreatedDate最低から最高まで順番に追跡し、ルックアップを行ってSomeIndexedValue = 1述語を評価しているようです。

最初に一致する行を見つけると、それが行われますが、そのような行を見つける前に予想よりもはるかに多くのルックアップを行う可能性があります(述語に一致する行が日付に従ってランダムに分散されると仮定します)

同様の問題については、こちらの回答をご覧ください

このクエリの理想的なインデックスは1 SomeIndexedValue, CreatedDateです。それを追加できないか、少なくとも既存のインデックスをSomeIndexedValueカバーCreatedDateされた列としてカバーできないと仮定すると、次のようにクエリを書き直すことができます

SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1

その特定の計画を使用しないようにします。


2

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 Standardでは、ビューを作成し、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

MAXDOP並列度の最大値を設定します。これにより、クエリが使用できるプロセッサの数が制限されます。これは基本的に、2番目のクエリの実行を1番目のクエリと同じくらい遅くします。並列処理を使用する機能が削除されているためです。
レイチェル

@レイチェルは同意します。しかし、いくつかの基本的なルールを設定しない限り、何も比較できません。64コアで実行されている並列プロセスを、1つのコアで実行されている単一スレッドと簡単に比較することはできません。最後に、すべてのマシンに少なくとも1つの論理CPUがあることを望みます=-)
ooutwire

0

私の意見では、問題の理由は、SQLサーバーオプティマイザーがBESTプランを探しているのではなく、最適なプランを探しているためです。並列化を強制した後、クエリがはるかに速く実行されたという事実から明らかです、オプティマイザーが持っていたことそれ自体では行われません。

また、異なる形式でクエリを書き換えることが並列化の違いである多くの状況を見てきました(たとえば、SQLに関するほとんどの記事はパラメータ化を推奨していますが、スニッフィングされたパラメータが非-並列化されたクエリ、または2つのクエリをUNION ALLと組み合わせることにより、並列化を排除できる場合があります。

そのため、一時テーブル、テーブル変数、cte、派生テーブル、パラメータ化などのクエリを作成するさまざまな方法を試して、インデックス、インデックス付きビュー、またはフィルタリングされたインデックスを使用して、最高の計画を得るために。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.