web-dev-qa-db-ja.com

結合とウィンドウ関数を使用してリード値とラグ値を取得するパフォーマンスの比較

2,000万行のテーブルがあり、各行にはtimeidvalueの3つの列があります。 idおよびtimeごとに、ステータスのvalueがあります。特定のtimeに対する特定のidのリード値とラグ値を知りたい。

これを達成するために2つの方法を使用しました。 1つの方法は結合を使用し、もう1つの方法はtimeおよびidのクラスター化インデックスを使用したウィンドウ関数lead/lagを使用することです。

これら2つの方法のパフォーマンスを実行時間で比較しました。結合メソッドは16.3秒かかり、ウィンドウ関数メソッドは20秒かかります(インデックスの作成時間は含まれません)。結合メソッドが力ずくでウィンドウ関数が進んでいるように見えるので、これは私を驚かせました。

2つのメソッドのコードは次のとおりです。

インデックスを作成

create clustered index id_time
 on tab1 (id,time)

結合方法

select a1.id,a1.time
   a1.value as value, 
   b1.value as value_lag,
   c1.value as value_lead
into tab2
from tab1 a1
left join tab1 b1
on a1.id = b1.id
and a1.time-1= b1.time
left join tab1 c1
on a1.id = c1.id
and a1.time+1 = c1.time

SET STATISTICS TIME, IO ONを使用して生成されたIO統計:

Statistics for Join Method

joinメソッド の実行計画は次のとおりです。

ウィンドウ関数方式

select id, time, value, 
   lag(value,1) over(partition by id order by id,time) as value_lag,
   lead(value,1) over(partition by id order by id,time) as value_lead
into tab2
from tab1

timeのみで注文すると、0.5秒節約できます。)

Window function method の実行計画は次のとおりです。

IO統計

[Statistics for Window function method 4]


sample_orig_month_1999のデータを確認したところ、生データはidtimeの順に並べられているようです。これがパフォーマンスの違いの理由ですか?

結合メソッドはウィンドウ関数メソッドよりも論理的な読み取りが多いようですが、前者の実行時間は実際には短くなっています。前者の方が並列処理が優れているからでしょうか。

簡潔なコードのため、ウィンドウ関数メソッドが好きですが、この特定の問題に対してそれを高速化する方法はありますか?

Windows 10 64ビットでSQL Server 2016を使用しています。

10
Jason

自己結合と比較して、LEADおよびLAGウィンドウ関数の行モードパフォーマンスが比較的低いことは、目新しいことではありません。たとえば、Michael Zilberstein それについて書いた はSQLblog.comで2012年にさかのぼります。

Plan section

SQL Server 2016には、新しいオプションがあります。これは、ウィンドウ集計のバッチモード処理を有効にすることです。これには、たとえ空であっても、テーブルに何らかの列ストアインデックスが必要です。列ストアインデックスの存在は、オプティマイザがバッチモードプランを検討するために現在必要です。特に、これにより、はるかに効率的なWindow Aggregateバッチモードオペレーターが可能になります。

これをテストするために、空の非クラスター化列ストアインデックスを作成します。

_ -- Empty CS index
CREATE NONCLUSTERED COLUMNSTORE INDEX dummy 
ON dbo.tab1 (id, [time], [value]) 
WHERE id < 0 AND id > 0;
_

クエリ:

_SELECT
    T1.id,
    T1.[time],
    T1.[value],
    value_lag = 
        LAG(T1.[value]) OVER (
            PARTITION BY T1.id
            ORDER BY T1.[time]),
    value_lead =
        LEAD(T1.[value]) OVER (
            PARTITION BY T1.id
            ORDER BY T1.[time])
FROM dbo.tab1 AS T1;
_

これで、次のような実行計画が表示されます。

Batch mode row store plan

...はるかに速く実行される可能性があります。

新しいテーブルに結果を保存するときに、同じプラン形状を取得するには、OPTION (MAXDOP 1)または他のヒントを使用する必要がある場合があります。プランの並列バージョンでは、バッチモードのソート(またはおそらく2つ)が必要ですが、これは少し遅くなる場合があります。それはむしろあなたのハードウェアに依存します。

Batch Mode Window Aggregateオペレーターの詳細については、Itzik Ben-Ganによる以下の記事を参照してください。

11
Paul White 9