MAXを使用したGROUP BY対MAXのみ


8

私はプログラマーで、次のような大きなテーブルを扱っています。

UpdateTime, PK, datetime, notnull
Name, PK, char(14), notnull
TheData, float

にクラスター化インデックスがあります Name, UpdateTime

私は何が速くなるべきか疑問に思っていました:

SELECT MAX(UpdateTime)
FROM [MyTable]

または

SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM [MyTable]
    group by [UpdateTime]
   ) as t

このテーブルへの挿入は、同じ日付の 50,000行のチャンクです。したがって、グループ化することでMAX計算が容易になると考えました。

最大150,000行を見つけようとする代わりに、3行にグループ化すると、計算MAXが速くなりますか?私の仮定は正しいですか、グループ化もコストがかかりますか?

回答:


12

あなたのスキーマに応じてテーブルbig_tableを作成しました

create table big_table
(
    updatetime datetime not null,
    name char(14) not null,
    TheData float,
    primary key(Name,updatetime)
)

次に、次のコードで50,000行をテーブルに入力しました。

DECLARE @ROWNUM as bigint = 1
WHILE(1=1)
BEGIN
    set @rownum  = @ROWNUM + 1
    insert into big_table values(getdate(),'name' + cast(@rownum as CHAR), cast(@rownum as float))
    if @ROWNUM > 50000
        BREAK;  
END

次に、SSMSを使用して両方のクエリをテストしたところ、最初のクエリでTheDataのMAXを探し、2番目のクエリでupdatetimeのMAXを探していることがわかりました。

したがって、最初のクエリを変更して、最大更新時間も取得しました

set statistics time on -- execution time
set statistics io on -- io stats (how many pages read, temp tables)

-- query 1
SELECT MAX([UpdateTime])
FROM big_table

-- query 2
SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM big_table
    group by [UpdateTime]
   ) as t


set statistics time off
set statistics io off

Statistics Timeを使用すると、各ステートメントの解析、コンパイル、および実行に必要なミリ秒数が返されます

統計IOを使用して、ディスクアクティビティに関する情報を取得します。

STATISTICS TIMEおよびSTATISTICS IOは有用な情報を提供します。使用された一時テーブルなど(作業テーブルによって示されます)。また、キャッシュから読み取られたデータベースページの数を示す、読み取られた論理ページの数。

次に、CTRL + Mで実行プランをアクティブにし(実際の実行プランの表示をアクティブにします)、F5で実行します。

これにより、両方のクエリを比較できます。

これはメッセージタブの出力です

-クエリ1

テーブル 'big_table'。スキャンカウント1、論理読み取り543、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Server実行時間: CPU時間= 16ミリ秒、経過時間= 6ミリ秒

-クエリ2

テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

テーブル 'big_table'。スキャンカウント1、論理読み取り543、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Server実行時間: CPU時間= 0ミリ秒、経過時間= 35ミリ秒

どちらのクエリでも543回の論理読み取りが行われますが、2番目のクエリの経過時間は35msですが、最初のクエリは最初の6msしかありません。また、2番目のクエリではtempdbの一時テーブルが使用されることにも注意してください。これは、worktableという単語で示されています。worktableのすべての値が0であっても、作業はtempdbで行われました。

次に、[メッセージ]タブの横にある実際の実行計画タブからの出力があります。

ここに画像の説明を入力してください

MSSQLが提供する実行プランによると、指定した2番目のクエリの合計バッチコストは64%ですが、最初のクエリは合計バッチの36%だけなので、最初のクエリに必要な作業は少なくなります。

SSMSを使用すると、クエリをテストして比較し、MSSQLがクエリをどのように解析しているか、どのオブジェクト(テーブル、インデックス、統計など)がそれらのクエリを満たすために使用されているかを正確に確認できます。

テスト時に、可能であればテストの前にキャッシュを消去することを覚えておくと、もう1つの注意事項があります。これは、比較が正確であることを保証するのに役立ちます。これは、ディスクアクティビティについて考えるときに重要です。すべてのキャッシュをクリアするために、DBCC DROPCLEANBUFFERSDBCC FREEPROCCACHEから始めます。ただし、実際に使用している本番サーバーでこれらのコマンドを使用しないように注意してください。サーバーからすべてをディスクからメモリに効率的に読み取らせます。

ここに関連するドキュメントがあります。

  1. DBCC FREEPROCCACHEを使用してプランキャッシュをクリアする
  2. DBCC DROPCLEANBUFFERSを使用して、バッファープールからすべてをクリアします。

ご使用の環境によっては、これらのコマンドを使用できない場合があります。

10/28 12:46 pmに更新

実行計画のイメージと統計の出力を修正しました。


深い回答をありがとう、コードのはげた行に注意してください。50,000行の各グループには同じ日付があり、他のチャンクとは異なります。だから私はgetdate()ループから抜け出す必要があります
Ofiris 2013年

1
@Ofiris様、こんにちは。私が出した答えは、実際にはあなたが自分で比較するのを助けるためだけです。自分で結論を出すために使用できるさまざまなコマンドとツールの使い方を説明するためだけに、ランダムなジャンクデータを作成しました。
Craig Efrein 2013年

1
tempdbで作業は行われませんでした。作業テーブルは、ハッシュアグリゲートがtempdbに確保するために予約されたメモリが不足しているためにハッシュアグリゲートが流出した場合に、パーティションを管理するためのものです。コストは「実際の」計画であっても常に見積もりであることを強調してください。これらはオプティマイザの推定値であり、実際のパフォーマンスとはあまり関係がない場合があります。バッチの%をプライマリチューニングメトリックとして使用しないでください。バッファのクリアは、コールドキャッシュのパフォーマンスをテストする場合にのみ重要です。
ポールホワイト9

1
@PaulWhite様、こんにちは。追加情報をお寄せいただきありがとうございます。より正確な方法についてのご提案を心より感謝いたします。しかし、「使用しないでください」という文章を書くとき、専門家のアドバイスを提供するのではなく、命令を与えると誤解しないでください。宜しくお願いします。
Craig Efrein 2013年

@CraigEfreinたぶん。許可されているコメントスペースに収まるように簡潔にしていました。
ポールホワイト9

6

このテーブルへの挿入は、同じ日付の50,000行のチャンクです。したがって、グループ化することでMAXの計算が容易になると考えました。

SQL Serverがインデックススキップスキャンを実装している場合、書き換えは役に立った可能性がありますが、そうではありません。

インデックススキップスキャンを使用すると、データベースエンジンは、間にあるすべての重複(または無関係なサブキー)をスキャンする代わりに、次の異なるインデックス値にシークできます。あなたの場合、スキップスキャンはエンジンがMAX(UpdateTime)最初のを見つけ、2番目のにNameスキップすることMAX(UpdateTime)を可能にしNameます...など。最後のステップは、MAX(UpdateTime)名前ごとに1つの候補からを見つけることです。

これは、再帰的なCTEを使用してある程度シミュレートできますが、少し面倒であり、組み込みのスキップスキャンほど効率的ではありません。

WITH RecursiveCTE
AS
(
    -- Anchor: MAX UpdateTime for
    -- highest-sorting Name
    SELECT TOP (1)
        BT.Name,
        BT.UpdateTime
    FROM dbo.BigTable AS BT
    ORDER BY
        BT.Name DESC,
        BT.UpdateTime DESC

    UNION ALL

    -- Recursive part
    -- MAX UpdateTime for Name
    -- that sorts immediately lower
    SELECT
        SubQuery.Name,
        SubQuery.UpdateTime
    FROM 
    (
        SELECT
            BT.Name,
            BT.UpdateTime,
            rn = ROW_NUMBER() OVER (
                ORDER BY BT.Name DESC, BT.UpdateTime DESC)
        FROM RecursiveCTE AS R
        JOIN dbo.BigTable AS BT
            ON BT.Name < R.Name
    ) AS SubQuery
    WHERE
        SubQuery.rn = 1
)
-- Final MAX aggregate over
-- MAX(UpdateTime) per Name
SELECT MAX(UpdateTime) 
FROM RecursiveCTE
OPTION (MAXRECURSION 0);

再帰CTE計画

このプランは、個別のごとにシングルトンシークを実行し、候補からName最高のものUpdateTimeを見つけます。テーブルの単純なフルスキャンと比較した場合のパフォーマンスは、あたりの重複の数Name、およびシングルトンシークによってタッチされたページがメモリ内にあるかどうかによって異なります。

代替ソリューション

このテーブルに新しいインデックスを作成できる場合、このクエリの適切な選択は、インデックスUpdateTimeのみです。

CREATE INDEX IX__BigTable_UpdateTime 
ON dbo.BigTable (UpdateTime);

このインデックスにより、実行エンジンUpdateTimeはインデックスbツリーの最後までシングルトンシークして最高を見つけることができます。

新しいインデックス計画

この計画は、(Bツリーレベルをナビゲートするために)いくつかの論理I / Oを消費し、すぐに完了します。プランのインデックススキャンは、新しいインデックスのフルスキャンではないことに注意してください。インデックスの1つの「終わり」から1つの行を返すだけです。

テーブルに完全に新しいインデックスを作成したくない場合は、一意のUpdateTime値のみを含むインデックス付きビューを検討することができます。

CREATE VIEW dbo.BigTableUpdateTimes
WITH SCHEMABINDING AS
SELECT 
    UpdateTime, 
    NumRows = COUNT_BIG(*)
FROM dbo.BigTable AS BT
GROUP BY
    UpdateTime;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.BigTableUpdateTimes (UpdateTime);

これには、一意のUpdateTime値と同じ数の行だけを含む構造を作成するという利点がありますが、ベーステーブルのデータを変更するすべてのクエリには、インデックス付きビューを維持するための実行プランに追加の演算子が追加されます。最大UpdateTime値を見つけるクエリは次のようになります。

SELECT MAX(BTUT.UpdateTime)
FROM dbo.BigTableUpdateTimes AS BTUT
    WITH (NOEXPAND);

インデックス付きビューの計画

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