パラメータとして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 Serverのバージョン= Microsoft SQL Server 2016(SP1-CU7-GDR)(KB4057119)-13.0.4466.4(X64)
だから私の質問は、なぜこれが問題になるのですか?派生テーブル内の値を計算する必要があるかどうかを知るにはどうすればよいですか?
パフォーマンスの違いが見られる理由を説明する部分的な答えをお教えしますが、それでもまだ(canなどの未解決の質問が残ります)SQL Serverは、式を列として投影する中間テーブル式を導入せずに、より最適なプランを作成しますか?)
違いは、高速計画では、JSON配列要素を解析して地理を作成するために必要な作業が4回(openjson
関数から発行された各行に対して1回)実行されるのに対し、100,000以上timesスロープランの場合。
高速計画では...
geography::Point(
convert(float,json_value(value,'$[0]'))
,convert(float,json_value(value,'$[1]'))
,4326)
openjson
関数の左側にある計算スカラーのExpr1000
に割り当てられます。これは、派生テーブル定義のgeo
に対応します。
高速計画では、フィルターおよびストリーム集約参照Expr1000
を使用します。スロープランでは、基礎となる式全体を参照します。
ストリーム集約プロパティ
フィルターは116,995回実行され、実行ごとに式の評価が必要です。ストリーム集約には、集約のためにそれに流れ込む110,520行があり、この式を使用して3つの個別の集約を作成します。 110,520 * 3 + 116,995 = 448,555
。個々の評価に18マイクロ秒かかる場合でも、クエリ全体で最大8秒の追加時間が追加されます。
これの影響は、プランXMLの実際の時間統計で確認できます(低速プランでは赤で、高速プランでは青で注釈されています-時間はミリ秒単位です)。
ストリーム集合体の経過時間は、その直接の子より6.209秒長くなっています。そして、子供の時間の大部分はフィルターによって吸収されました。これは追加の式評価に対応します。
ちなみに...一般的には 確かではありませんExpr1000
のようなラベルが付いた基礎となる式は1回だけ計算され、再評価されませんが、この場合は実行から明らかにここでタイミングの不一致が発生します。