Was ist der Sinn und Nutzen der Verwendung von SqlCommand.Prepare ()?

14

Ich bin auf Entwicklercode gestoßen, in dem die Methode SqlCommand.Prepare () (siehe MSDN) vor der Ausführung von SQL-Abfragen ausgiebig verwendet wird. Und ich frage mich, was der Nutzen davon ist?

Stichprobe:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

Ich habe ein bisschen rumgespielt und nachgezeichnet. Die Ausführung des Befehls nach dem Aufruf der Prepare()Methode bewirkt, dass Sql Server die folgende Anweisung ausführt:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

Danach, wenn der Parameter seinen Wert erhält und SqlCommand.ExecuteNonQuery()aufgerufen wird, wird Folgendes auf Sql-Server ausgeführt:

exec sp_execute 1,@id=20

Für mich sieht es so aus, als würde die Anweisung kompiliert, sobald sie Prepare()ausgeführt wird. Ich frage mich, was ist der Vorteil davon? Bedeutet dies, dass es in den Plan-Cache gestellt wird und wieder verwendet werden kann, sobald die endgültige Abfrage mit den gewünschten Parameterwerten ausgeführt wird?

Ich habe herausgefunden (und in einer anderen Frage dokumentiert ), dass SqlCommands, die mit SqlParameters ausgeführt werden, immer in sp_executesqlProzeduraufrufe eingeschlossen sind. Dadurch kann SQL Server die Pläne unabhängig von den Parameterwerten speichern und wiederverwenden.

In Bezug darauf frage ich mich, ob die prepare()Methode nutzlos oder veraltet ist oder ob ich hier etwas vermisse?

Magier
quelle
Ich dachte immer, es hätte etwas mit dem Verhindern von SQL Injection zu tun, also würden Sie die Abfrage so vorbereiten, dass bestimmte Variablen bestimmter Typen akzeptiert werden. Auf diese Weise schlägt die Abfrage fehl, wenn Sie keine Variablen dieses Typs übergeben
Mark Sinkinson,
Obwohl er sagt , dass diese PHP Erklärung ist wahrscheinlich genauso gültig für .NET php.net/manual/en/pdo.prepared-statements.php
Mark Sinkinson
"Ja, Sir. Fahrer, bereiten Sie sich auf den Auszug vor." "Was bereiten Sie vor ?! Sie bereiten sich immer vor! Gehen Sie einfach!"
Jon of All Trades

Antworten:

19

Das separate Vorbereiten eines SQL-Stapels von der Ausführung des vorbereiteten SQL-Stapels ist ein Konstrukt, das effektiv ist **Für SQL Server nutzlos, da Ausführungspläne zwischengespeichert werden. Die Schritte der Vorbereitung (Parsen, Binden von Parametern und Kompilieren) und der Ausführung sind nur dann sinnvoll, wenn kein Caching stattfindet. Der Zweck besteht darin, die für das Parsen und Kompilieren aufgewendete Zeit zu sparen, indem ein vorhandener Plan wiederverwendet wird. Dies ist die Aufgabe des internen Plan-Cache. Da jedoch nicht alle RDBMS diese Zwischenspeicherebene (eindeutig aufgrund der Existenz dieser Funktionen) ausführen, muss der Clientcode anfordern, dass ein Plan "zwischengespeichert" wird, und diese Cache-ID speichern und dann erneut verwenden es. Dies ist eine zusätzliche Belastung für den Client-Code (und den Programmierer, sich daran zu erinnern, dies zu tun und es richtig zu machen), die nicht erforderlich ist. Die MSDN-Seite für die IDbCommand.Prepare () -Methode besagt tatsächlich :

Der Server speichert die Pläne automatisch zwischen, damit sie bei Bedarf wiederverwendet werden können. Daher ist es nicht erforderlich, diese Methode direkt in Ihrer Clientanwendung aufzurufen.

Es sei darauf hingewiesen , dass, soweit meine Tests zeigt (was dem entspricht , was man in der Frage zeigen), Aufruf SqlCommand.Prepare () , wird nicht tun ein -nur Operation „vorbereiten“: es nennt sp_prepexec die vorbereitet und führt die SQL ; Es wird nicht sp_prepare aufgerufen, das nur analysiert und kompiliert wird (und keine Parameterwerte, nur deren Namen und Datentypen berücksichtigt). Daher kann es keinen Vorteil des Aufrufs vor dem Kompilieren geben, SqlCommand.Prepareda er eine sofortige Ausführung ausführt (vorausgesetzt, das Ziel besteht darin, die Ausführung aufzuschieben). JEDOCH gibt es ein interessantes Potential geringen Vorteil zu nennen SqlCommand.Prepare(), auch wenn es nicht nennen sp_prepexecstatt sp_prepare. Bitte beachten Sie den Hinweis (** ) unten für Details.

Meine Tests zeigen , dass selbst wenn SqlCommand.Prepare()genannt wird (bitte beachten Sie , dass dieser Aufruf nicht nicht erteilen alle Befehle auf SQL Server), die ExecuteNonQueryoder ExecuteReaderdas folgt vollständig ausgeführt wird, und wenn es ExecuteReader ist, es gibt Reihen. SQL Server Profiler zeigt jedoch an, dass der erste SqlCommand.Execute______()Aufruf nach dem SqlCommand.Prepare()Aufruf als "Prepare SQL" .Execute___()-Ereignis registriert wird und nachfolgende Aufrufe als "Exec Prepared SQL" -Ereignisse registriert werden.


Code testen

Es wurde auf PasteBin hochgeladen unter: http://pastebin.com/Yc2Tfvup . Der Code erstellt eine .NET / C # -Konsolenanwendung, die ausgeführt werden soll, während eine SQL Server Profiler-Ablaufverfolgung ausgeführt wird (oder eine Sitzung für erweiterte Ereignisse). Es wird nach jedem Schritt angehalten, damit klar wird, welche Anweisungen eine bestimmte Wirkung haben.


AKTUALISIEREN

Gefundene weitere Informationen und ein kleiner möglicher Grund, einen Anruf zu vermeiden SqlCommand.Prepare(). Eines ist mir bei meinen Tests aufgefallen, und was für jeden, der diese Testkonsolen-App ausführt, zu beachten ist: Es wird nie ein expliziter Anruf getätigt sp_unprepare. Ich habe ein bisschen gegraben und den folgenden Beitrag in den MSDN-Foren gefunden:

SqlCommand - Vorbereiten oder nicht vorbereiten?

Die akzeptierte Antwort enthält eine Reihe von Informationen, die wichtigsten Punkte sind jedoch:

  • ** Die Vorbereitung spart Ihnen in erster Linie die Zeit, die Sie für die drahtlose Übertragung der Abfragezeichenfolge benötigen.
  • Eine vorbereitete Abfrage garantiert nicht, dass ein zwischengespeicherter Plan gespeichert wird, und ist nicht schneller als eine Ad-hoc-Abfrage, sobald sie den Server erreicht.
  • RPCs (CommandType.StoredProcedure) profitieren nicht von der Vorbereitung.
  • Auf dem Server ist ein vorbereitetes Handle im Grunde ein Index für eine speicherinterne Zuordnung, die TSQL enthält, das ausgeführt werden soll.
  • Die Karte wird pro Verbindung gespeichert, eine Querverbindung ist nicht möglich.
  • Durch das Zurücksetzen, das gesendet wird, wenn der Client die Verbindung aus dem Pool erneut verwendet, wird die Zuordnung gelöscht.

Ich habe auch den folgenden Beitrag des SQLCAT-Teams gefunden, der verwandt zu sein scheint, aber einfach ein spezifisches Problem für ODBC sein könnte, während SqlClient beim Entsorgen von SqlConnection korrekt aufräumt. Es ist schwer zu sagen, da in der SQLCAT-Veröffentlichung keine zusätzlichen Tests erwähnt werden, die dies als Ursache belegen könnten, z. B. das Löschen des Verbindungspools usw.

Achten Sie auf die vorbereiteten SQL-Anweisungen


FAZIT

Vorausgesetzt, dass:

  • Der einzige wirkliche Vorteil eines Anrufs besteht SqlCommand.Prepare()darin, dass Sie den Abfragetext nicht erneut über das Netzwerk senden müssen.
  • Aufrufen sp_prepareund sp_prepexecSpeichern des Abfragetexts als Teil des Verbindungsspeichers (z. B. SQL Server-Speicher)

Ich würde von Anrufen abraten, SqlCommand.Prepare()da der einzige potenzielle Vorteil darin besteht, Netzwerkpakete zu speichern, während der Nachteil mehr Serverspeicher beansprucht. Während der Speicherbedarf in den meisten Fällen wahrscheinlich sehr gering ist, ist die Netzwerkbandbreite selten ein Problem, da die meisten DB-Server über mindestens 10 Megabit (heutzutage eher 100 Megabit oder Gigabit) direkt mit den App-Servern verbunden sind. und manche sind sogar auf der gleichen Box ;-). Das und der Speicher sind fast immer eine knappere Ressource als die Netzwerkbandbreite.

Ich nehme an, für alle, die Software schreiben, die eine Verbindung zu mehreren Datenbanken herstellen kann, könnte die Bequemlichkeit einer Standardschnittstelle diese Kosten-Nutzen-Analyse ändern. Aber auch in diesem Fall, ich habe zu glauben , dass es immer noch leicht genug , um zu abstrahieren „Standard“ DB - Schnittstelle ist , die für die verschiedenen Anbieter ermöglicht (und damit Unterschiede zwischen ihnen, wie nicht , um zu Anruf Prepare()) gegeben , dass dies ist ein grundlegende Object Orientierte Programmierung, die du wahrscheinlich schon in deinem App-Code machst ;-).

Solomon Rutzky
quelle
Vielen Dank für diese ausführliche Antwort. Ich habe Ihre Schritte getestet und hatte zwei Abweichungen von Ihren Erwartungen / Ergebnissen: In Schritt 4 habe ich ein zusätzliches Ergebnis erhalten. In Schritt 10 habe ich ein anderes plan_handle als in Schritt 2.
Magier
1
@Magier Du bist dir bei Schritt 4 nicht sicher ... vielleicht hattest du andere SET-Optionen oder irgendwie war der Abfragetext zwischen den beiden unterschiedlich? Sogar ein einzelner (sichtbarer oder nicht sichtbarer) Unterschied ist ein anderer Plan. Das Kopieren und Einfügen von dieser Webseite hilft möglicherweise nicht. Kopieren Sie daher die Abfrage (alles zwischen den einzelnen Anführungszeichen) vom sp_preparein den Aufruf an sp_executesql, um sicherzugehen. Für Schritt 10, ja, ich habe gestern mehr Tests durchgeführt und habe einige Gelegenheiten mit unterschiedlichen Griffen für gesehen sp_prepare, aber immer noch nie die gleichen für sp_executesql. Ich werde noch eine Sache testen und meine Antwort aktualisieren.
Solomon Rutzky
1
@Magier Der Test, den ich zuvor durchgeführt habe, deckte es tatsächlich ab und ich glaube nicht, dass es eine echte Wiederverwendung des Plans gibt, insbesondere angesichts des MSDN-Forumsbeitrags, der besagt, dass nur SQL zwischengespeichert wird. Ich bin immer noch gespannt, wie das plan_handle wiederverwendet werden sp_executesqlkann oder nicht. Unabhängig davon ist die Analysezeit immer noch aktiv, sp_preparewenn der Plan aus dem Cache entfernt wurde, sodass ich diesen Teil meiner Antwort entfernen werde (interessant, aber irrelevant). Dieser Teil war sowieso eher eine interessante Randnotiz, da er Ihre direkte Frage nicht ansprach. Alles andere gilt noch.
Solomon Rutzky
1
Dies war die Art von Erklärung, nach der ich gesucht habe.
CSharpie
5

In Anbetracht dessen frage ich mich, ob die prepare () -Methode nutzlos oder veraltet ist oder ob ich hier etwas vermisse.

Ich würde sagen, dass die Prepare-Methode einen begrenzten Wert für SqlCommand-Welt hat, nicht, dass sie völlig nutzlos ist. Ein Vorteil ist, dass Prepare Teil der IDbCommand-Schnittstelle ist, die SqlCommand implementiert. Auf diese Weise kann derselbe Code mit anderen DBMS-Anbietern ausgeführt werden (für die möglicherweise Prepare erforderlich ist), ohne dass eine bedingte Logik erforderlich ist, um zu bestimmen, ob Prepare aufgerufen werden soll oder nicht.

Prepare hat beim .NET-Provider für SQL Server keinen Wert, abgesehen von diesen Anwendungsfällen, AFAIK.

Dan Guzman
quelle