この派生テーブルがパフォーマンスを改善するのはなぜですか?


18

json文字列をパラメーターとして受け取るクエリがあります。jsonは、緯度と経度のペアの配列です。入力例は次のとおりです。

declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';

それは、1,3,5,10マイルの距離で、地理的なポイントの周りのPOIの数を計算するTVFを呼び出します。

create or alter function [dbo].[fn_poi_in_dist](@geo geography)
returns table
with schemabinding as
return 
select count_1  = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 1,1,0e))
      ,count_3  = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 3,1,0e))
      ,count_5  = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 5,1,0e))
      ,count_10 = count(*)
from dbo.point_of_interest
where LatLong.STDistance(@geo) <= 1609.344e * 10

jsonクエリの目的は、この関数を一括呼び出しすることです。このように呼び出すと、パフォーマンスは非常に低く、わずか4ポイントで10秒近くかかります。

select row=[key]
      ,count_1
      ,count_3
      ,count_5
      ,count_10
from openjson(@json)
cross apply dbo.fn_poi_in_dist(
            geography::Point(
                convert(float,json_value(value,'$[0]'))
               ,convert(float,json_value(value,'$[1]'))
               ,4326))

計画= https://www.brentozar.com/pastetheplan/?id=HJDCYd_o4

ただし、派生テーブル内で地理の構成を移動すると、パフォーマンスが大幅に向上し、クエリが約1秒で完了します。

select row=[key]
      ,count_1
      ,count_3
      ,count_5
      ,count_10
from (
select [key]
      ,geo = geography::Point(
                convert(float,json_value(value,'$[0]'))
               ,convert(float,json_value(value,'$[1]'))
               ,4326)
from openjson(@json)
) a
cross apply dbo.fn_poi_in_dist(geo)

計画= https://www.brentozar.com/pastetheplan/?id=HkSS5_OoE

計画は実質的に同一に見えます。どちらも並列処理を使用せず、両方とも空間インデックスを使用します。スロープランには、ヒントで削除できる追加の遅延スプールがありoption(no_performance_spool)ます。ただし、クエリのパフォーマンスは変わりません。まだずっと遅いままです。

追加されたヒントを使用して両方をバッチで実行すると、両方のクエリの重みが等しくなります。

SQLサーバーのバージョン= Microsoft SQL Server 2016(SP1-CU7-GDR)(KB4057119)-13.0.4466.4(X64)

私の質問は、なぜこれが重要なのですか?派生テーブル内の値を計算する必要があるかどうかを知るにはどうすればよいですか?


1
「重量」とは、推定コスト%を意味しますか?その数は、あなたがたUDFに持っている場合は特に、事実上無意味である、JSONなどの地理、経由CLR
アーロン・ベルトラン

私は承知していますが、IO統計を見ると同じです。両方とも、point_of_interestテーブルで358306論理読み取りを行い、両方とも4602回インデックスをスキャンし、両方とも作業テーブルとワークファイルを生成します。見積者は、これらの計画は同一であると考えていますが、パフォーマンスはそうではないと言います。
マイケルB

ここでは実際のCPUが問題であるように思われます。これはおそらく、I / Oではなく、Martinが指摘したことによるものです。残念ながら、推定コストはCPUとI / Oの組み合わせに基づいており、実際に発生することを常に反映しているわけではありません。SentryOne Plan Explorer私はそこで働いていますが、ツールは文字列なしで無料です)を使用して実際のプランを生成し、実際のコストをCPUのみに変更すると、そのCPU時間すべてが費やされた場所のより良いインジケーターを取得できます。
アーロンバートランド

1
@MartinSmithオペレーターごとではありません。これらはステートメントレベルで表面化します。現在、これらの追加のメトリックが下位レベルで追加される前は、DMVの初期実装に依存しています。そして、私たちはあなたがすぐに見る他のものに取り組んで少し忙しかったです。:-)
アーロンバートランド

1
PS直線距離の計算を行う前に単純な算術ボックスを実行することで、さらにパフォーマンスが向上する場合があります。つまり、最初に|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n、より複雑な処理を行う前に値をフィルタリングしますsqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)。さらに良いことに、最初に上限と下限を計算してから、を計算しLatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBoundます。(これは擬似コードであり、適切に適応します。)
ErikE

回答:


15

パフォーマンスの違いが見られる理由を説明する部分的な答えを与えることができます-それでもいくつかの未解決の質問が残っています(SQL Serverは、式を列として投影する中間テーブル式を導入せずに、より最適なプランを作成できますか?)


違いは、高速プランでは、JSON配列要素を解析してGeographyを作成するために必要な作業が4回行われるopenjsonことです(関数から出力される各行に対して1回)- 低速プランでは100,000 以上行われます。

高速計画では...

geography::Point(
                convert(float,json_value(value,'$[0]'))
               ,convert(float,json_value(value,'$[1]'))
               ,4326)

関数のExpr1000左側の計算スカラーで割り当てられopenjsonます。これgeoは、派生テーブルの定義に対応します。

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

高速プランでは、フィルターとストリームの集約参照Expr1000。遅い計画では、基礎となる完全な表現を参照します。

ストリーム集約プロパティ

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

フィルターは116,995回実行され、各実行には式の評価が必要です。ストリーム集約には、集約のために110,520行が流れ、この式を使用して3つの個別の集約を作成します。110,520 * 3 + 116,995 = 448,555。個々の評価に18マイクロ秒かかったとしても、クエリ全体でさらに8秒の時間がかかります。

この効果は、プランXMLの実際の時間統計で確認できます(遅いプランの場合は赤で、速いプランの場合は青で、時間はミリ秒で示されます)。

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

ストリーム集合体は、その直接の子よりも6.209秒長い経過時間を持っています。そして、子時間の大部分はフィルターによって占められました。これは、余分な式の評価に対応します。


ちなみに...一般に、ラベルのような基になる式が一度だけ計算され、再評価されないことは確かはありませんExpr1000が、この場合は実行タイミングの不一致から明らかになります。


余談ですが、クエリを切り替えてクロス適用を使用して地理を生成すると、高速なプランも取得できます。cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f
マイケルB

残念ながら、高速なプランを生成するためのもっと簡単な方法があるかどうか疑問に思っています。
マイケルB

アマチュアの質問は申し訳ありませんが、画像にはどのツールが表示されていますか?
BlueRaja-ダニープルフホイフト

1
@ BlueRaja-DannyPflughoeftこれらは管理スタジオに表示される実行計画です(SSMSで使用されるアイコンは、質問の理由である場合、最近のバージョンで更新されています)
Martin Smith
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.