Zwischenspeichert SQL Server berechnete Werte in einer Abfrage?

10

Jedes Mal, wenn ich auf diese Art von Abfragen stoße, frage ich mich, wie SQL Server das schaffen würde. Wenn ich einen Abfragetyp ausführe, für den eine Berechnung erforderlich ist, und diesen Wert dann an mehreren Stellen verwende, z. B. in selectund order by, wird SQL Server ihn für jede Zeile zweimal berechnen oder zwischengespeichert? Wie funktioniert dies außerdem mit benutzerdefinierten Funktionen?

Beispiele:

SELECT CompanyId, Count(*)
FROM Sales
ORDER BY Count(*) desc

SELECT Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STX, Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STY
FROM Table

SELECT Id, udf.MyFunction(Id)
FROM Table
ORDER BY udf.MyFunction(Id)

Gibt es eine Möglichkeit, es effizienter zu gestalten, oder ist SQL Server intelligent genug, um es für mich zu handhaben?

Jonas Stawski
quelle
"es kommt darauf an" hier ist eine Ausstellung rextester.com/DXOB90032
Martin Smith
Was Sie mit rextester.com/ARSO25902
Martin Smith
@MartinSmith Verwenden Sie nicht eine nicht deterministische Funktion? Wenn dies der Fall ist, würde ich erwarten, dass SQL es zweimal ausführt.
Jonas Stawski
Es gibt immer eine Ausnahme! Sie können es versuchen SELECT RAND() FROM Sales order by RAND()- dies wird nur einmal ausgewertet, da es sowohl nicht deterministisch als auch eine Laufzeitkonstante ist.
Martin Smith

Antworten:

11

Das SQL Server-Abfrageoptimierungsprogramm kann wiederholt berechnete Werte in einem einzigen Compute Scalar-Operator kombinieren. Ob dies der Fall ist oder nicht, hängt von der Kalkulation des Abfrageplans und den Eigenschaften des berechneten Werts ab. Wie erwartet wird dies nicht für berechnete Werte durchgeführt, die nicht deterministisch sind, was einige Ausnahmen wie z RAND(). Dies wird auch nicht für benutzerdefinierte Funktionen ausgeführt.

Ich werde mit einem benutzerdefinierten Funktionsbeispiel beginnen. Hier ist ein hervorragendes Beispiel für eine benutzerdefinierte Funktion:

CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;

Ich möchte auch eine Tabelle erstellen und 100 Zeilen einfügen:

CREATE TABLE X_100 (N BIGINT NOT NULL);

WITH
L0   AS(SELECT 1 AS c UNION ALL SELECT 1),
L1   AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2   AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3   AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4   AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5   AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO X_100 WITH (TABLOCK)
SELECT n
FROM Nums WHERE n <= 100;

Die dbo.NULL_FUNCTIONFunktion ist deterministisch. Wie oft wird es für die folgende Abfrage ausgeführt?

SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;

Basierend auf dem Abfrageplan wird dies einmal für jede Zeile oder 100 Mal ausgeführt:

Abfrageplan 1

In SQL Server 2016 wurde die DMV sys.dm_exec_function_stats eingeführt . Wir können Schnappschüsse dieser DMV machen, um zu sehen, wie oft eine UDF von einer Abfrage ausgeführt wird.

SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');

Das Ergebnis ist 100, daher wurde die Funktion 100 Mal ausgeführt.

Versuchen wir eine andere einfache Abfrage:

SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n) 
FROM X_100;

Der Abfrageplan schlägt vor, dass die Funktion 200 Mal ausgeführt wird:

Abfrageplan 2

Die Ergebnisse von sys.dm_exec_function_statslegen nahe, dass die Funktion 200 Mal ausgeführt wurde.

Beachten Sie, dass Sie den Abfrageplan nicht immer verwenden können, um herauszufinden, wie oft ein Berechnungsskalar ausgeführt wird. Das folgende Zitat stammt aus " Skalare, Ausdrücke und Leistung des Ausführungsplans berechnen ":

Dies führt dazu, dass die Leute glauben, dass sich Compute Scalar wie die meisten anderen Operatoren verhält: Während Zeilen durch den Scalar fließen, werden die Ergebnisse aller Berechnungen, die der Compute Scalar enthält, zum Stream hinzugefügt. Dies ist im Allgemeinen nicht wahr. Trotz des Namens berechnet Compute Scalar nicht immer etwas und enthält nicht immer einen einzelnen Skalarwert (dies kann beispielsweise ein Vektor, ein Alias ​​oder sogar ein Boolesches Prädikat sein). Meistens definiert ein Compute Scalar einfach einen Ausdruck. Die eigentliche Berechnung wird verschoben, bis etwas später im Ausführungsplan das Ergebnis benötigt.

Versuchen wir ein anderes Beispiel. Für die folgende Abfrage würde ich hoffen, dass die UDF einmal berechnet wird:

WITH NULL_FUNCTION_CTE (NULL_VALUE) AS
(
SELECT DISTINCT dbo.NULL_FUNCTION(0)
)
SELECT n , cte.NULL_VALUE
FROM X_100
CROSS JOIN NULL_FUNCTION_CTE cte;

Der Abfrageplan schlägt vor, dass er einmal berechnet wird:

Abfrageplan

Die DMV enthüllt jedoch die Wahrheit. Der Berechnungsskalar wird verschoben, bis er benötigt wird, was sich im Join-Operator befindet. Es wird 100 mal ausgewertet.

Sie haben auch gefragt, was Sie tun können, um den Optimierer zu ermutigen, den gleichen Ausdruck nicht mehrmals neu zu berechnen. Das Beste, was Sie tun können, ist zu vermeiden, skalare UDFs in Ihrem Code zu verwenden. Diese haben eine Reihe von Leistungsproblemen außerhalb dieser Frage, darunter das Aufblasen von Speicherzuweisungen, das Erzwingen der Ausführung der gesamten Abfrage MAXDOP 1, schlechte Kardinalitätsschätzungen und die zusätzliche CPU-Auslastung. Wenn Sie eine UDF verwenden müssen und der Wert dieser UDF eine Konstante ist, können Sie sie außerhalb der Abfrage berechnen und in eine lokale Variable einfügen.

Bei Abfragen ohne UDFs können Sie vermeiden, Ausdrücke zu schreiben, die dasselbe Ergebnis zurückgeben, aber nicht genau auf dieselbe Weise eingegeben werden. Für dieses nächste Beispiel verwende ich die öffentlich verfügbare AdventureworksDW2016CTP3-Datenbank, aber wirklich jede Datenbank reicht aus. Wie oft wird COUNT(*)für diese Abfrage berechnet?

SELECT OrderDateKey, COUNT(*) 
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;

Für diese Abfrage können wir dies anhand des Operators Hash Match (Aggregat) herausfinden.

Hash-Match

Das COUNT(*)wird einmal für jeden eindeutigen Wert von berechnet OrderDateKey. Das Einbeziehen der ORDER BYKlausel führt nicht dazu, dass sie zweimal berechnet wird. Den Ausführungsplan sehen Sie hier .

Stellen Sie sich nun eine Abfrage vor, die genau dieselben Ergebnisse liefert, aber anders geschrieben ist:

SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;

Das Abfrageoptimierungsprogramm ist nicht intelligent genug, um sie zu kombinieren. Daher wird zusätzliche Arbeit geleistet:

Hash Match 2

Joe Obbish
quelle