Die Erklärung scheint an eine Kombination gebunden zu sein aus: a) einem Detail aus dem verlinkten Blog, das in dieser Frage nicht erwähnt wurde, b) der Pragmatik von TVPs, die in die Art und Weise passen, wie Parameter immer ein- und ausgereicht wurden, c) und der Natur von Tabellenvariablen.
Das fehlende Detail im verlinkten Blog-Beitrag ist genau, wie Variablen in gespeicherte Prozeduren und Funktionen eingegeben und aus ihnen entfernt werden (dies bezieht sich auf die Formulierung in der Frage "Eine sicherere Version der Referenzübergabe, wenn es sich um OUTPUT-Parameter handelt"). :
TSQL verwendet eine Copy-In / Copy-Out-Semantik, um Parameter an gespeicherte Prozeduren und Funktionen zu übergeben.
... wenn der gespeicherte Prozess die Ausführung beendet (ohne einen Fehler zu melden), wird eine Kopie erstellt, die den übergebenen Parameter mit allen Änderungen aktualisiert, die am gespeicherten Prozess vorgenommen wurden.
Der eigentliche Vorteil dieses Ansatzes liegt im Fehlerfall. Wenn während der Ausführung einer gespeicherten Prozedur ein Fehler auftritt, werden alle an den Parametern vorgenommenen Änderungen nicht an den Aufrufer weitergegeben.
Wenn das Schlüsselwort OUTPUT nicht vorhanden ist, erfolgt keine Ausgabe.
Fazit:
Parameter für gespeicherte Prozesse spiegeln niemals die teilweise Ausführung des gespeicherten Prozesses wider, wenn ein Fehler aufgetreten ist.
Teil 1 dieses Puzzles ist, dass Parameter immer "nach Wert" übergeben werden. Erst wenn der Parameter als markiert ist OUTPUT
und die gespeicherte Prozedur erfolgreich abgeschlossen wurde, wird der aktuelle Wert zurückgesendet. Wenn die OUTPUT
Werte wirklich "als Referenz" übergeben würden, wäre der Zeiger auf die Position dieser Variablen im Speicher das Übergebene und nicht der Wert selbst. Und wenn Sie den Zeiger (dh die Speicheradresse) übergeben, werden alle vorgenommenen Änderungen sofort wiedergegeben, auch wenn die nächste Zeile der gespeicherten Prozedur einen Fehler verursacht und die Ausführung abgebrochen wird.
Fazit Teil 1: Variablenwerte werden immer kopiert; Sie werden nicht durch ihre Speicheradresse referenziert.
Unter Berücksichtigung von Teil 1 kann eine Richtlinie, bei der Variablenwerte immer kopiert werden, zu Ressourcenproblemen führen, wenn die übergebene Variable sehr groß ist. Ich habe nicht getestet , um zu sehen , wie Blob - Typen behandelt werden ( VARCHAR(MAX)
, NVARCHAR(MAX)
, VARBINARY(MAX)
, XML
, und diejenigen , die nicht mehr verwendet werden sollen: TEXT
, NTEXT
, und IMAGE
), aber es ist sicher zu sagen , dass jede Tabelle mit Daten in ziemlich groß sein könnte weitergegeben werden. Für diejenigen, die die TVP-Funktion entwickeln, wäre es sinnvoll, eine echte "Pass-by-Reference" -Fähigkeit zu wünschen, um zu verhindern, dass ihre coole neue Funktion eine gesunde Anzahl von Systemen zerstört (dh einen skalierbareren Ansatz wünscht). Wie Sie in der Dokumentation sehen können , haben sie Folgendes getan:
Transact-SQL übergibt tabellenwerte Parameter als Referenz an Routinen, um zu vermeiden, dass eine Kopie der Eingabedaten erstellt wird.
Dieses Speicherverwaltungsproblem war auch kein neues Konzept, da es in der SQLCLR-API enthalten ist, die in SQL Server 2005 eingeführt wurde (TVPs wurden in SQL Server 2008 eingeführt). Bei der Übergabe NVARCHAR
und VARBINARY
Daten in SQLCLR Code (dh Eingangsparameter auf den .NET - Methoden innerhalb einer SQLCLR Assembly), haben Sie die Möglichkeit mit dem „by value“ -Ansatz zu gehen entweder über SqlString
oder SqlBinary
jeweils, oder Sie können mit dem „Verweis gehen msgstr "Annäherung unter Verwendung von entweder SqlChars
oder SqlBytes
. Die Typen SqlChars
und SqlBytes
ermöglichen das vollständige Streaming der Daten in die .NET CLR, sodass Sie kleine Teile mit großen Werten abrufen können, anstatt einen gesamten Wert von 200 MB (bis zu 2 GB, rechts) zu kopieren.
Zusammenfassend lässt sich sagen, dass TVPs von Natur aus dazu neigen, viel Speicher zu verbrauchen (und damit die Leistung zu verschlechtern), wenn sie im Modell "Immer den Wert kopieren" bleiben. Daher machen TVPs einen echten "Pass by Reference".
Das letzte Stück ist, warum Teil 2 wichtig ist: Warum sollte man eine TVP wirklich "per Referenz" einreichen, anstatt eine Kopie davon zu erstellen, um irgendetwas zu ändern? Und das wird durch das Entwurfsziel beantwortet, das Teil 1 zugrunde liegt: Gespeicherte Prozeduren, die nicht erfolgreich abgeschlossen werden, sollten in keiner Weise die Eingabeparameter ändern, unabhängig davon, ob sie als markiert sind OUTPUT
oder nicht. Das Zulassen von DML-Vorgängen hat unmittelbare Auswirkungen auf den Wert des TVP, wie er im aufrufenden Kontext vorhanden ist (da durch Referenzübergabe das Übergebene geändert wird, nicht eine Kopie des Übergebenen).
Irgendwo unterhält sich jemand an dieser Stelle wahrscheinlich mit seinem Monitor und sagt: "Bauen Sie einfach eine automagische Einrichtung ein, um alle Änderungen an den TVP-Parametern rückgängig zu machen, wenn sie an die gespeicherte Prozedur übergeben wurden. Duh. Problem gelöst." Nicht so schnell. Hier kommt die Art der Tabellenvariablen ins Spiel: Änderungen an Tabellenvariablen sind nicht an Transaktionen gebunden! Es gibt also keine Möglichkeit, die Änderungen rückgängig zu machen. Tatsächlich ist dies ein Trick, der verwendet wird, um Informationen zu speichern, die innerhalb einer Transaktion generiert wurden, wenn ein Rollback erforderlich ist :-).
Zusammenfassend lässt sich sagen, dass in Teil 3: Tabellenvariablen keine Änderungen "rückgängig" gemacht werden können, wenn ein Fehler auftritt, durch den die gespeicherte Prozedur abgebrochen wird. Dies verstößt gegen das Entwurfsziel, dass Parameter niemals die teilweise Ausführung widerspiegeln (Teil 1).
TYPE
Variable oder eineDECLARE x as TABLE (...)
) mit einer gespeicherten Prozedur zu ändern ? Kann ich dies mit einer Funktion tun, obwohl der Speicherbedarf größer ist, als mit,set @tvp = myfunction(@tvp)
wenn derRETURNS
Wert meiner Funktion eine Tabelle mit derselben DDL wie der TVP-Typ ist?