sql >> データベース >  >> RDS >> Sqlserver

subquery と leftjoin with group はどちらが速いですか?

    このドキュメント は、SQL Server で実行中の合計を計算するための優れたリソースです。 OVER を取得するためのキャンペーンの一環として SQL Server チームに提出された Itzik Ben Gan による 節は、最初の SQL Server 2005 実装からさらに拡張されました。その中で彼は、何万行ものカーソルを取得すると、セットベースのソリューションを実行する方法を示しています。 SQL Server 2012 は確かに OVER を拡張しました 句を使用すると、この種のクエリがはるかに簡単になります。

    SELECT col1,
           SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING)
    FROM   @tmp 
    

    ただし、SQL Server 2005 を使用しているため、これは利用できません。

    Adam Machanic ここに表示 CLR を使用して標準の TSQL カーソルのパフォーマンスを向上させる方法。

    このテーブル定義について

    CREATE TABLE RunningTotals
    (
    ind int identity(1,1) primary key,
    col1 int
    )
    

    ALLOW_SNAPSHOT_ISOLATION ON を使用して、データベースに 2,000 行と 10,000 行の両方を持つテーブルを作成します この設定がオフになっているもの (この理由は、最初の結果が設定がオンの DB にあり、結果の不可解な側面につながったためです)。

    すべてのテーブルのクラスター化インデックスには、1 つのルート ページしかありませんでした。それぞれのリーフ ページ数は次のとおりです。

    +-------------------------------+-----------+------------+
    |                               | 2,000 row | 10,000 row |
    +-------------------------------+-----------+------------+
    | ALLOW_SNAPSHOT_ISOLATION OFF  |         5 |         22 |
    | ALLOW_SNAPSHOT_ISOLATION ON   |         8 |         39 |
    +-------------------------------+-----------+------------+
    

    次のケースをテストしました (リンクは実行計画を示します)

    <オール>
  1. 左結合とグループ化
  2. 相関サブクエリ 2000 行計画10000 行計画
  3. Mikael の (更新された) 回答からの CTE
  4. CTE 以下
  5. 追加の CTE オプションを含める理由は、ind が 列が連続しているとは限りません。

    SET STATISTICS IO ON;
    SET STATISTICS TIME ON;
    DECLARE @col1 int, @sumcol1 bigint;
    
    WITH    RecursiveCTE
    AS      (
            SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total
            FROM RunningTotals
            ORDER BY ind
            UNION   ALL
            SELECT  R.ind, R.col1, R.Total
            FROM    (
                    SELECT  T.*,
                            T.col1 + Total AS Total,
                            rn = ROW_NUMBER() OVER (ORDER BY T.ind)
                    FROM    RunningTotals T
                    JOIN    RecursiveCTE R
                            ON  R.ind < T.ind
                    ) R
            WHERE   R.rn = 1
            )
    SELECT  @col1 =col1, @sumcol1=Total
    FROM    RecursiveCTE
    OPTION  (MAXRECURSION 0);
    

    すべてのクエリに CAST(col1 AS BIGINT) がありました 実行時のオーバーフロー エラーを回避するために追加されました。さらに、それらすべてについて、上記のように結果を変数に割り当てて、考慮から結果を送り返すのに費やす時間をなくしました。

    結果

    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    |                  |          |        |          Base Table        |         Work Table         |     Time        |
    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    |                  | Snapshot | Rows   | Scan count | logical reads | Scan count | logical reads | cpu   | elapsed |
    | Group By         | On       | 2,000  | 2001       | 12709         |            |               | 1469  | 1250    |
    |                  | On       | 10,000 | 10001      | 216678        |            |               | 30906 | 30963   |
    |                  | Off      | 2,000  | 2001       | 9251          |            |               | 1140  | 1160    |
    |                  | Off      | 10,000 | 10001      | 130089        |            |               | 29906 | 28306   |
    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    | Sub Query        | On       | 2,000  | 2001       | 12709         |            |               | 844   | 823     |
    |                  | On       | 10,000 | 2          | 82            | 10000      | 165025        | 24672 | 24535   |
    |                  | Off      | 2,000  | 2001       | 9251          |            |               | 766   | 999     |
    |                  | Off      | 10,000 | 2          | 48            | 10000      | 165025        | 25188 | 23880   |
    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    | CTE No Gaps      | On       | 2,000  | 0          | 4002          | 2          | 12001         | 78    | 101     |
    |                  | On       | 10,000 | 0          | 20002         | 2          | 60001         | 344   | 342     |
    |                  | Off      | 2,000  | 0          | 4002          | 2          | 12001         | 62    | 253     |
    |                  | Off      | 10,000 | 0          | 20002         | 2          | 60001         | 281   | 326     |
    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    | CTE Alllows Gaps | On       | 2,000  | 2001       | 4009          | 2          | 12001         | 47    | 75      |
    |                  | On       | 10,000 | 10001      | 20040         | 2          | 60001         | 312   | 413     |
    |                  | Off      | 2,000  | 2001       | 4006          | 2          | 12001         | 94    | 90      |
    |                  | Off      | 10,000 | 10001      | 20023         | 2          | 60001         | 313   | 349     |
    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    

    相関サブクエリと GROUP BY の両方 バージョンは、RunningTotals でクラスター化されたインデックス スキャンによって駆動される「三角形」のネストされたループ結合を使用します テーブル (T1 ) そして、そのスキャンによって返された行ごとに、テーブルをシークします (T2 ) T2.ind<=T1.ind での自己結合 .

    これは、同じ行が繰り返し処理されることを意味します。 T1.ind=1000 の場合 行が処理され、自己結合が取得され、すべての行が ind <= 1000 で合計されます 、次に T1.ind=1001 の次の行 同じ 1000 行が再び取得されます 追加の 1 行などと一緒に合計されます。

    2,000 行のテーブルに対するこのような操作の総数は 2,001,000 で、10,000 行の場合は 50,005,000 またはそれ以上です (n² + n) / 2 明らかに指数関数的に成長しています。

    2,000 行の場合、GROUP BY の主な違いは サブクエリのバージョンは、前者が結合後にストリーム集計を持っているため、それにフィードする 3 つの列があることです (T1.ind , T2.col1 , T2.col1 ) と GROUP BY T1.ind のプロパティ 一方、後者はスカラー集計として計算され、結合前のストリーム集計では T2.col1 しかありません それにフィードし、GROUP BY はありません プロパティはまったく設定されていません。この単純な配置は、CPU 時間の削減という点で測定可能な利点があることがわかります。

    10,000 行の場合、サブクエリ プランにさらに違いがあります。 熱心なスプール を追加します すべての ind,cast(col1 as bigint) をコピーします 値を tempdb に .スナップショット分離がオンになっている場合、これはクラスター化されたインデックス構造よりもコンパクトに機能し、最終的な効果は読み取り数を約 25% 削減することです (ベース テーブルはバージョン管理情報用にかなりの空き領域を保持するため)。このオプションがオフの場合、コンパクトではなくなります (おそらく bigint が原因です)。 vs int 違い)、より多くの読み取り結果が得られます。これにより、サブクエリとバージョンごとのグループの間のギャップが減少しますが、サブクエリが引き続き優先されます。

    しかし、明らかな勝者は Recursive CTE でした。 「ギャップなし」バージョンでは、ベース テーブルからの論理読み取りは 2 x (n + 1) になりました n を反映 index は 2 レベルのインデックスをシークして、すべての行と、何も返さずに再帰を終了する最後の追加の行を取得します。それでも、22 ページのテーブルを処理するには 20,002 回の読み取りが必要です!

    再帰 CTE バージョンの論理作業テーブルの読み取りが非常に多くなります。ソース行ごとに 6 回のワークテーブル読み取りでうまくいくようです。これらは、前の行の出力を格納し、次の反復で再度読み取られるインデックス スプールから取得されます (これについては、Umachandar Jayachandran こちら )。数が多いにもかかわらず、これは依然として最高のパフォーマーです。



    1. GroupBy句を含むSQLコンマ区切り行

    2. MySQLトランザクションをテストする方法は?

    3. MySQLフィールドの先頭と末尾の空白を削除するにはどうすればよいですか?

    4. 非常に小さなMySQLテーブルはインデックスを無視しますか?