Ich habe eine Abfrage, die eine JSON-Zeichenfolge als Parameter nimmt. Der json ist ein Array von Breiten- und Längengradpaaren. Eine Beispieleingabe könnte die folgende sein.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Es ruft eine TVF auf, die die Anzahl der POIs um einen geografischen Punkt in 1,3,5,10 Meilen Entfernung berechnet.
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
Die Absicht der json-Abfrage besteht darin, diese Funktion als Massenaufruf aufzurufen. Wenn ich es so nenne, ist die Leistung sehr schlecht und dauert fast 10 Sekunden für nur 4 Punkte:
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))
plan = https://www.brentozar.com/pastetheplan/?id=HJDCYd_o4
Durch das Verschieben der Geografiekonstruktion in eine abgeleitete Tabelle wird die Leistung jedoch erheblich verbessert, und die Abfrage wird in etwa 1 Sekunde abgeschlossen.
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)
plan = https://www.brentozar.com/pastetheplan/?id=HkSS5_OoE
Die Pläne sehen praktisch identisch aus. Keiner verwendet Parallelität und beide verwenden den räumlichen Index. Es gibt eine zusätzliche träge Spule auf dem langsamen Plan, die ich mit dem Hinweis beseitigen kann option(no_performance_spool)
. Die Abfrageleistung ändert sich jedoch nicht. Es bleibt immer noch viel langsamer.
Wenn Sie beide mit dem hinzugefügten Hinweis in einem Stapel ausführen, werden beide Abfragen gleich gewichtet.
SQL Server-Version = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Meine Frage ist also, warum das wichtig ist? Wie kann ich wissen, wann ich Werte in einer abgeleiteten Tabelle berechnen soll oder nicht?
quelle
point_of_interest
Tabelle durch, scannen den Index 4602-mal und generieren eine Arbeitstabelle und eine Arbeitsdatei. Der Schätzer ist der Ansicht, dass diese Pläne identisch sind, die Leistung jedoch etwas anderes vorgibt.|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n
umso komplizierter ist, bevor Sie ihn ausführensqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)
. Und noch besser, berechnen Sie zuerst die obere und untere GrenzeLatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound
. (Dies ist ein Pseudocode, passen Sie ihn entsprechend an.)Antworten:
Ich kann Ihnen eine teilweise Antwort geben, die erklärt, warum Sie den Leistungsunterschied sehen - obwohl noch einige offene Fragen offen sind (wie kann SQL Server den optimaleren Plan erstellen, ohne einen Zwischentabellenausdruck einzuführen, der den Ausdruck als Spalte projiziert?).
Der Unterschied besteht darin, dass im schnellen Plan die zum Parsen der JSON-Array-Elemente und zum Erstellen der Geografie erforderlichen Arbeiten viermal ausgeführt werden (einmal für jede Zeile, die von der
openjson
Funktion ausgegeben wird) - während dies mehr als das 100.000- fache des langsamen Plans ist.Im schnellen Plan ...
Wird
Expr1000
im Berechnungsskalar links von deropenjson
Funktion zugewiesen . Dies entsprichtgeo
in Ihrer abgeleiteten Tabellendefinition.Im Schnellplan die Filter- und Stream-Aggregatreferenz
Expr1000
. Im langsamen Plan verweisen sie auf den vollständigen zugrunde liegenden Ausdruck.Aggregateigenschaften streamen
Der Filter wird 116.995 Mal ausgeführt, wobei jede Ausführung eine Ausdrucksbewertung erfordert. In das Stream-Aggregat fließen 110.520 Zeilen zur Aggregation ein, und mithilfe dieses Ausdrucks werden drei separate Aggregate erstellt.
110,520 * 3 + 116,995 = 448,555
. Selbst wenn jede einzelne Auswertung 18 Mikrosekunden dauert, ergibt dies eine zusätzliche Zeit von 8 Sekunden für die gesamte Abfrage.Sie können die Auswirkung davon in der aktuellen Zeitstatistik im Plan-XML sehen (unten in rot vom langsamen Plan und in blau von den schnellen Planzeiten in ms).
Das Stream-Aggregat hat eine abgelaufene Zeit, die 6,209 Sekunden länger ist als das unmittelbar untergeordnete Element. Und der Großteil der Kinderzeit wurde vom Filter aufgenommen. Dies entspricht den zusätzlichen Ausdrucksauswertungen.
Übrigens ... Im Allgemeinen ist es nicht sicher, dass zugrunde liegende Ausdrücke mit Bezeichnungen wie
Expr1000
nur einmal berechnet und nicht neu bewertet werden. In diesem Fall ergibt sich dies jedoch eindeutig aus der Abweichung des Ausführungszeitpunkts.quelle
cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f