Zwischenspeichert SQL Server das Ergebnis einer Funktion mit mehreren Anweisungen und Tabellenwerten?

22

Eine Tabellenwertfunktion mit mehreren Anweisungen gibt das Ergebnis in einer Tabellenvariablen zurück.

Werden diese Ergebnisse jemals wiederverwendet oder wird die Funktion bei jedem Aufruf vollständig ausgewertet?

Paul White sagt GoFundMonica
quelle

Antworten:

23

Die Ergebnisse einer Funktion mit mehreren Anweisungen und Tabellenwerten (msTVF) werden niemals zwischengespeichert oder über Anweisungen (oder Verbindungen) hinweg wiederverwendet. Es gibt jedoch verschiedene Möglichkeiten, wie ein msTVF-Ergebnis in derselben Anweisung wiederverwendet werden kann . Insofern wird eine msTVF nicht unbedingt bei jedem Aufruf neu aufgefüllt.

Beispiel msTVF

Diese (absichtlich ineffiziente) msTVF gibt einen bestimmten Bereich von Ganzzahlen mit einem Zeitstempel in jeder Zeile zurück:

IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
    DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table 
(
    n integer PRIMARY KEY, 
    ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
    WHILE @From <= @To
    BEGIN
        INSERT @T (n)
        VALUES (@From);

        SET @From = @From + 1;
    END;
    RETURN;
END;

Statische Tabellenvariable

Wenn alle Parameter des Funktionsaufrufs Konstanten (oder Laufzeitkonstanten) sind, füllt der Ausführungsplan das Ergebnis der Tabellenvariablen einmal aus. Der Rest des Plans kann mehrmals auf die Tabellenvariable zugreifen. Die statische Natur der Tabellenvariablen ist aus dem Ausführungsplan ersichtlich. Beispielsweise:

SELECT
    IR.n,
    IR.ts 
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
    IR.n;

Gibt ein Ergebnis ähnlich dem folgenden zurück:

Einfaches Ergebnis

Der Ausführungsplan lautet:

Einfacher Ausführungsplan

Der Operator Sequence ruft zuerst den Operator Table Valued Function auf, der die Tabellenvariable auffüllt (beachten Sie, dass dieser Operator keine Zeilen zurückgibt). Als Nächstes ruft die Sequenz ihre zweite Eingabe auf, die den Inhalt der Tabellenvariablen zurückgibt (in diesem Fall unter Verwendung eines Clustered-Index-Scans).

Das Werbegeschenk, dass der Plan ein "statisches" Tabellenvariablenergebnis verwendet, ist der Operator "Tabellenwertfunktion" unter einer Sequenz. Die Tabellenvariable muss einmal vollständig gefüllt werden, bevor der Rest des Plans ausgeführt werden kann.

Mehrfachzugriffe

Um zu zeigen, dass auf das Ergebnis der Tabellenvariablen mehrmals zugegriffen wird, verwenden wir eine zweite Tabelle mit Zeilen, die von 1 bis 5 nummeriert sind:

IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
    DROP TABLE dbo.T;

CREATE TABLE dbo.T (i integer NOT NULL);

INSERT dbo.T (i) 
VALUES (1), (2), (3), (4), (5);

Und eine neue Abfrage, die diese Tabelle mit unserer Funktion verknüpft (dies könnte auch als eine geschrieben werden APPLY):

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
    ON IR.n = T.i;

Das Ergebnis ist:

Join Ergebnis

Der Ausführungsplan:

Plan beitreten

Wie zuvor füllt die Sequenz zuerst die Tabellenvariable msTVF result. Als nächstes werden verschachtelte Schleifen verwendet, um jede Zeile aus der Tabelle zu verknüpfenT mit einer Zeile aus dem msTVF-Ergebnis zu verbinden. Da die Funktionsdefinition einen hilfreichen Index für die Tabellenvariable enthielt, kann eine Indexsuche verwendet werden.

Der entscheidende Punkt ist, dass der Plan zwei separate Operatoren für das Ergebnis der msTVF-Tabellenvariablen enthält, wenn die Parameter der msTVF Konstanten sind (einschließlich Variablen und Parameter) oder als Laufzeitkonstanten für die Anweisung von der Ausführungsengine behandelt werden: einen zum Auffüllen der Tabelle; eine andere, um auf die Ergebnisse zuzugreifen, möglicherweise mehrmals auf die Tabelle zuzugreifen und möglicherweise Indizes zu verwenden, die in der Funktionsdefinition deklariert sind.

Korrelierte und nicht konstante Parameter

Um die Unterschiede hervorzuheben, wenn korrelierte Parameter (äußere Referenzen) oder nicht konstante Funktionsparameter verwendet werden, werden wir den Inhalt der Tabelle ändern, Tdamit die Funktion viel mehr zu tun hat:

TRUNCATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50001), (50002), (50003), (50004), (50005);

Die folgende geänderte Abfrage verwendet jetzt einen äußeren Verweis auf eine Tabelle Tin einem der Funktionsparameter:

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

Diese Abfrage dauert ungefähr 8 Sekunden , um folgende Ergebnisse zurückzugeben:

Korreliertes Ergebnis

Beachten Sie den Zeitunterschied zwischen den Zeilen in der Spalte ts. Die WHEREKlausel begrenzt das Endergebnis für eine Ausgabe mit vernünftiger Größe, aber die ineffiziente Funktion benötigt noch eine Weile, um die Tabellenvariable mit ungeraden 50.000 Zeilen zu füllen (abhängig vom korrelierten Wert von ifrom table T).

Der Ausführungsplan lautet:

Korrelierter Ausführungsplan

Beachten Sie das Fehlen eines Sequenzoperators. Jetzt gibt es einen einzelnen Tabellenwertfunktionsoperator, der die Tabellenvariable auffüllt und ihre Zeilen bei jeder Iteration zurückgibt des Joins mit verschachtelten Schleifen .

Um es klar auszudrücken: Mit nur 5 Zeilen in Tabelle T wird der Operator "Tabellenwertfunktion" fünfmal ausgeführt. Es generiert 50.001 Zeilen in der ersten Iteration, 50.002 in der zweiten ... und so weiter. Die Tabellenvariable wird zwischen den Iterationen "weggeworfen" (abgeschnitten), sodass jeder der fünf Aufrufe eine vollständige Grundgesamtheit darstellt. Aus diesem Grund ist es so langsam und es dauert ungefähr dieselbe Zeit, bis jede Zeile im Ergebnis angezeigt wird.

Randnotizen:

Das obige Szenario wurde natürlich absichtlich entworfen, um zu zeigen, wie schlecht die Leistung sein kann, wenn die msTVF bei jeder Iteration viele Zeilen auffüllt.

Eine sinnvolle Implementierung des obigen Codes würde beide msTVF-Parameter auf setzen iund die redundante WHEREKlausel entfernen . Die Tabellenvariable wird bei jeder Iteration immer noch abgeschnitten und neu aufgefüllt, jedoch nur mit jeweils einer Zeile.

Wir könnten auch die Minimal- und Maximalwerte iabrufen Tund sie in einem vorherigen Schritt in Variablen speichern. Das Aufrufen der Funktion mit Variablen anstelle von korrelierten Parametern würde die Verwendung des 'statischen' Tabellenvariablenmusters ermöglichen, wie bereits erwähnt.

Caching für unveränderte korrelierte Parameter

Wenn Sie noch einmal auf die ursprüngliche Frage zurückkommen, bei der das statische Sequenzmuster nicht verwendet werden kann, kann SQL Server dies vermeiden Abschneiden und erneute Auffüllen der msTVF-Tabellenvariablen , wenn sich keiner der korrelierten Parameter seit der vorherigen Iteration eines Nested-Loop-Joins geändert hat.

Um dies zu demonstrieren, werden wir den Inhalt von Tdurch fünf identische i Werte ersetzen :

TRUNCATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50005), (50005), (50005), (50005), (50005);

Die Abfrage mit einem korrelierten Parameter noch einmal:

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

Diesmal erscheinen die Ergebnisse in ungefähr 1,5 Sekunden :

Identische Zeilenergebnisse

Beachten Sie die identischen Zeitstempel in jeder Zeile. Das zwischengespeicherte Ergebnis in der Tabellenvariablen wird für nachfolgende Iterationen wiederverwendet, bei denen der korrelierte Wert iunverändert bleibt. Die Wiederverwendung des Ergebnisses ist viel schneller als das Einfügen von jeweils 50.005 Zeilen.

Der Ausführungsplan sieht sehr ähnlich aus:

Planen Sie identische Zeilen

Der Hauptunterschied liegt in den Eigenschaften " Tatsächliche Rückspulungen" und " Tatsächliche Rückspulungen" des Operators "Tabellenwertfunktion":

Operator-Eigenschaften

Wenn sich die korrelierten Parameter nicht ändern, kann SQL Server die aktuellen Ergebnisse in der Tabellenvariablen wiedergeben (zurückspulen). Wenn sich die Korrelation ändert, muss SQL Server die Tabellenvariable abschneiden und neu füllen (erneut binden). Der eine Rebind erfolgt bei der ersten Iteration; Die vier nachfolgenden Iterationen sind alle Rückläufe, da der Wert von T.iunverändert bleibt.

Paul White sagt GoFundMonica
quelle