it-swarm.com.de

Warum verbessert diese abgeleitete Tabelle die Leistung?

Ich habe eine Abfrage, die einen JSON-String als Parameter verwendet. 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 wird eine TVF aufgerufen, die die Anzahl der POIs um einen geografischen Punkt in Entfernungen von 1,3,5,10 Meilen 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

Mit der JSON-Abfrage soll diese Funktion als Massenaufruf aufgerufen werden. 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

Wenn Sie jedoch die Konstruktion der Geografie in eine abgeleitete Tabelle verschieben, verbessert sich die Leistung erheblich 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 faule Spule auf dem langsamen Plan, die ich mit dem Hinweis option(no_performance_spool) beseitigen kann. 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-DDR) (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?

18
Michael B

Ich kann Ihnen eine teilweise Antwort geben, die erklärt, warum Sie den Leistungsunterschied sehen - obwohl dies noch einige offene Fragen offen lässt (wie kann SQL Server das erzeugen optimalerer Plan ohne Einführung eines Zwischentabellenausdrucks, der den Ausdruck als Spalte projiziert?)


Der Unterschied besteht darin, dass im Schnellplan die zum Parsen der JSON-Array-Elemente und zum Erstellen der Geografie erforderliche Arbeit viermal ausgeführt wird (einmal für jede von der Funktion openjson ausgegebene Zeile), während mehr als 100.000 = ausgeführt werden mal das im langsamen Plan.

Im schnellen Plan ...

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

Wird Expr1000 Im Berechnungsskalar links von der Funktion openjson zugewiesen. Dies entspricht geo in Ihrer abgeleiteten Tabellendefinition.

(enter image description here

Im Schnellplan die Filter- und Stream-Aggregatreferenz Expr1000. Im langsamen Plan verweisen sie auf den vollständigen zugrunde liegenden Ausdruck.

Eigenschaften von Stream-Aggregaten

(enter image description here

Der Filter wird 116.995 Mal ausgeführt, wobei jede Ausführung eine Ausdrucksbewertung erfordert. Das Stream-Aggregat enthält 110.520 Zeilen, die zur Aggregation fließen, und erstellt mit diesem Ausdruck drei separate Aggregate. 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 den Effekt davon in der tatsächlichen Zeitstatistik in der Plan-XML sehen (unten rot vom langsamen Plan und blau für den schnellen Plan - die Zeiten sind in ms angegeben).

(enter image description here

Das Stream-Aggregat hat eine verstrichene Zeit, die 6,209 Sekunden länger ist als die seines unmittelbaren untergeordneten Elements. Und der Großteil der Kinderzeit wurde vom Filter in Anspruch genommen. Dies entspricht den zusätzlichen Ausdrucksauswertungen.


Übrigens .... Im Allgemeinen ist es ist keine sichere Sache , dass zugrunde liegende Ausdrücke mit Bezeichnungen wie Expr1000 Nur einmal berechnet und nicht neu bewertet werden, sondern in diesem Fall eindeutig aus dem Hier kommt es zu Abweichungen beim Ausführungszeitpunkt.

15
Martin Smith