Warum müssen TVPs READONLY sein und warum können Parameter anderer Typen nicht READONLY sein


19

Gemäß diesem Blog werden Parameter für eine Funktion oder eine gespeicherte Prozedur im Wesentlichen als Pass-by-Value- OUTPUTParameter behandelt, sofern es sich nicht um Parameter handelt, und im Wesentlichen als sicherere Version von Pass-by-Reference- OUTPUTParametern.

Zuerst dachte ich, das Ziel, TVP zur Deklaration READONLYzu zwingen, sei, den Entwicklern klar zu signalisieren, dass TVP nicht als OUTPUTParameter verwendet werden kann, aber es muss mehr passieren, weil wir Nicht-TVP nicht als deklarieren können READONLY. Zum Beispiel schlägt Folgendes fehl:

create procedure [dbo].[test]
@a int readonly
as
    select @a

Meldung 346, Ebene 15,
Status 1, Prozedurtest Der Parameter "@a" kann nicht READONLY deklariert werden, da er kein tabellenwertiger Parameter ist.

  1. Da Statistiken nicht auf TVP gespeichert werden, welche Gründe sprechen dafür , DML-Vorgänge zu verhindern?
  2. Hat es etwas damit zu tun, dass TVP OUTPUTaus irgendeinem Grund keine Parameter sein soll?

Antworten:


19

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.

  1. 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 OUTPUTWerte 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.

  2. 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 NVARCHARund VARBINARYDaten 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 SqlStringoder SqlBinaryjeweils, oder Sie können mit dem „Verweis gehen msgstr "Annäherung unter Verwendung von entweder SqlCharsoder SqlBytes. Die Typen SqlCharsund SqlBytesermö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".

  3. 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 OUTPUToder 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).

Ergo: Das READONLYSchlüsselwort wird benötigt, um DML-Vorgänge auf TVPs zu verhindern, da es sich um Tabellenvariablen handelt, die tatsächlich "als Referenz" übergeben werden. Daher werden alle Änderungen sofort wiedergegeben, selbst wenn die gespeicherte Prozedur einen Fehler feststellt und keine vorhanden ist andere Möglichkeit, das zu verhindern.

Darüber hinaus können Parameter anderer Datentypen nicht verwendet werden, READONLYda sie bereits Kopien des übergebenen Datentyps sind und daher nichts schützen, was noch nicht geschützt ist. Dies und die Art und Weise, wie die Parameter der anderen Datentypen als Lese- / Schreibzugriff gedacht waren, würde es wahrscheinlich noch mehr Arbeit bedeuten, diese API so zu ändern, dass sie jetzt ein Nur-Lese-Konzept enthält.


Sehr ausführliche Erklärung. Vielen Dank. Gibt es also keine Möglichkeit, eine übergebene Tabellenvariable (entweder eine Benutzer-TVP- TYPEVariable oder eine DECLARE 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 der RETURNSWert meiner Funktion eine Tabelle mit derselben DDL wie der TVP-Typ ist?
mpag

@mpag Danke. Ein TVP ist eine Tabellenvariable, es gibt keinen Unterschied. Sie übergeben den Typ nicht, sondern eine Tabellenvariable, die aus einem Typ oder einer expliziten Schemadeklaration erstellt wurde. Sie können auch keine SETTabellenvariable, zumindest nicht, die mir bekannt ist. Und selbst wenn Sie: a) nicht über den =Operator auf eine Ergebnismenge zugreifen können und b) die TVP weiterhin als markiert ist READONLY, würde dies die Einstellung verletzen. Speichern Sie den Inhalt einfach in einer temporären Tabelle oder einer anderen Tabellenvariablen, die Sie innerhalb des Prozesses erstellen.
Solomon Rutzky

Danke noch einmal. Ich habe mich im Wesentlichen für einen temporären Tabellenansatz entschieden.
mpag

5

Community Wiki Antwort generiert aus einem Kommentar zu der Frage von Martin Smith

Zu diesem Thema gibt es einen aktiven Connect-Artikel (eingereicht von Erland Sommarskog):

Lockern Sie die Einschränkung, dass Tabellenparameter nur lesbar sein dürfen, wenn SPs sich gegenseitig aufrufen

Die einzige Antwort von Microsoft lautet bisher (Hervorhebung hinzugefügt):

Danke für das Feedback dazu. Wir haben ähnliche Rückmeldungen von einer großen Anzahl von Kunden erhalten. Das Ermöglichen des Lese- / Schreibens von Tabellenwertparametern ist sowohl für die SQL Engine als auch für die Client-Protokolle mit erheblichem Aufwand verbunden. Aufgrund von Zeit- / Ressourcenbeschränkungen und anderen Prioritäten können wir diese Arbeit im Rahmen der SQL Server 2008-Version nicht aufnehmen. Wir haben dieses Problem jedoch untersucht und haben es fest im Blick, um es im Rahmen der nächsten Version von SQL Server zu beheben. Wir schätzen und begrüßen das Feedback hier.

Srini Acharya
Senior Program Manager
Relationale SQL Server-Engine

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.