Verschachtelungsebene der selbstreferenzierenden Skalarfunktion beim Hinzufügen einer Auswahl überschritten

24

Zweck

Beim Versuch, ein Testbeispiel für eine selbstreferenzierende Funktion zu erstellen, schlägt eine Version fehl, während eine andere erfolgreich ist.

Der einzige Unterschied besteht darin SELECT, dass der Funktionskörper hinzugefügt wird , was zu einem unterschiedlichen Ausführungsplan für beide führt.


Die Funktion, die funktioniert

CREATE FUNCTION dbo.test5(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN  dbo.test5(1) + dbo.test5(2)
END
)
END;

Funktion aufrufen

SELECT dbo.test5(3);

Kehrt zurück

(No column name)
3

Die Funktion, die nicht funktioniert

CREATE FUNCTION dbo.test6(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.test6(1) + dbo.test6(2))
END
)END;

Funktion aufrufen

SELECT dbo.test6(3);

oder

SELECT dbo.test6(2);

Ergibt den Fehler

Die maximale Schachtelungsebene für gespeicherte Prozeduren, Funktionen, Auslöser oder Ansichten wurde überschritten (Limit 32).

Die Ursache erraten

Es gibt einen zusätzlichen Berechnungsskalar im geschätzten Plan der fehlgeschlagenen Funktion, der aufruft

<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END">

Und expr1000 zu sein

<ColumnReference Column="Expr1000" />
<ScalarOperator ScalarString="[dbo].[test6]((1))+[dbo].[test6]((2))">

Dies könnte die rekursiven Referenzen über 32 erklären.

Die eigentliche Frage

Das Addierte SELECTbewirkt, dass sich die Funktion selbst immer wieder aufruft, was zu einer Endlosschleife führt. Warum fügt ein SELECTAddiertes dieses Ergebnis hinzu?


zusätzliche Information

Geschätzte Ausführungspläne

DB <> Geige

Build version:
14.0.3045.24

Getestet auf den Kompatibilitätsstufen 100 und 140

Randi Vertongen
quelle

Antworten:

26

Dies ist ein Fehler in der Projektnormalisierung , der durch die Verwendung einer Unterabfrage in einem case-Ausdruck mit einer nicht deterministischen Funktion angezeigt wird.

Um dies zu erklären, müssen wir im Vorfeld zwei Dinge beachten:

  1. SQL Server kann Unterabfragen nicht direkt ausführen, daher werden sie immer entrollt oder in eine Anwendung konvertiert .
  2. Die Semantik von CASEist so, dass ein THENAusdruck nur ausgewertet werden sollte, wenn die WHENKlausel true zurückgibt.

Die im Problemfall eingeführte (triviale) Unterabfrage führt daher zu einem Apply-Operator (Nested Loops Join). Um die zweite Anforderung zu erfüllen, platziert SQL Server den Ausdruck zunächst dbo.test6(1) + dbo.test6(2)auf der Innenseite des Apply:

hervorgehobener Rechenskalar

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

... mit der CASESemantik, die durch ein Pass-Through- Prädikat für den Join geehrt wird:

[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)

Die Innenseite der Schleife wird nur ausgewertet, wenn die Durchgangsbedingung den Wert false (Bedeutung @i = 3) hat. Das ist alles soweit richtig. Der Berechnungsskalar nach dem Join mit verschachtelten Schleifen berücksichtigt auch die CASESemantik korrekt:

[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

Das Problem ist, dass die Phase der Projektnormalisierung der Abfragekompilierung Expr1000unkorreliert ist und feststellt, dass es sicher ist ( Sprecher: nicht ), sie außerhalb der Schleife zu verschieben:

bewegtes Projekt

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

Dadurch wird die vom Pass-Through- Prädikat * implementierte Semantik gebrochen, sodass die Funktion ausgewertet wird, wenn dies nicht der Fall sein sollte, und eine Endlosschleife entsteht.

Sie sollten diesen Fehler melden. Eine Problemumgehung besteht darin, zu verhindern, dass der Ausdruck aus der Anwendung verschoben wird, indem er korreliert wird (dh @iin den Ausdruck einbezogen wird), aber dies ist natürlich ein Hack. Es gibt eine Möglichkeit, die Projektnormalisierung zu deaktivieren, aber ich wurde zuvor gebeten, sie nicht öffentlich zu teilen, sodass ich dies nicht tun werde.

Dieses Problem tritt in SQL Server 2019 nicht auf, wenn die Skalarfunktion eingebunden ist , da die eingebundene Logik direkt in der analysierten Struktur ausgeführt wird (lange vor der Projektnormalisierung). Die einfache Logik in der Frage kann durch die Inlining-Logik zum nicht-rekursiven vereinfacht werden:

[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))

... was 3 ergibt.

Ein anderer Weg, um das Kernproblem zu veranschaulichen, ist:

-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error() 
RETURNS integer 
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
    RETURN 1/0;
END;
GO
DECLARE @i integer = 1;

SELECT
    CASE 
        WHEN @i = 1 THEN 1
        WHEN @i = 2 THEN 2
        WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
        ELSE NULL
    END;

Reproduziert auf den neuesten Builds aller Versionen von 2008 R2 bis 2019 CTP 3.0.

Ein weiteres Beispiel (ohne Skalarfunktion) von Martin Smith :

SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))

Dies hat alle wichtigen Elemente benötigt:

  • CASE(intern implementiert als ScaOp_IIF)
  • Eine nicht deterministische Funktion ( CRYPT_GEN_RANDOM)
  • Eine Unterabfrage für den Zweig, die nicht ausgeführt werden soll ( (SELECT ...))

* Streng genommen könnte die obige Transformation immer noch korrekt sein, wenn die Bewertung von Expr1000korrekt zurückgestellt wurde, da nur auf die sichere Konstruktion verwiesen wird:

[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

... aber dies erfordert ein internes ForceOrder- Flag (kein Abfragehinweis ), das auch nicht gesetzt ist. In jedem Fall ist die Implementierung der durch die Projektnormalisierung angewendeten Logik falsch oder unvollständig.

Fehlerbericht auf der Azure Feedback-Website für SQL Server.

Paul White sagt GoFundMonica
quelle