Gespeicherte Prozeduren vs. Inline-SQL

27

Ich weiß, dass gespeicherte Prozeduren über den Ausführungspfad effizienter sind (als die Inline-SQL in Anwendungen). Wenn ich gedrückt werde, weiß ich nicht genau, warum.

Ich würde gerne die technischen Gründe dafür erfahren (so, dass ich es später jemandem erklären kann).

Kann mir jemand helfen, eine gute Antwort zu formulieren?

webdad3
quelle
1
Eine ordnungsgemäß parametrisierte Abfrage ist aus Sicht der Leistung genauso gut wie eine gespeicherte Prozedur. Beide werden vor der ersten Verwendung kompiliert, beide verwenden den zwischengespeicherten Ausführungsplan bei nachfolgenden Ausführungen erneut, beide Pläne werden im selben Plan-Cache gespeichert und beide werden mit demselben Namen behandelt. In SQL Server gibt es heute keinen Leistungsvorteil mehr für eine gespeicherte Prozedur.
marc_s
@marc_s das ist wahr, wenn die Abfragen identisch sind. Wie ich jedoch in meiner Antwort ausgeführt habe, gibt es einige Merkmale von Ad-hoc-Abfragen, die auch bei Abfragen, die identisch zu sein scheinen , zu Leistungsproblemen führen können .
Aaron Bertrand

Antworten:

42

Ich glaube, dass dieses Gefühl zu einem bestimmten Zeitpunkt wahr war, aber nicht in aktuellen Versionen von SQL Server. Das ganze Problem war, dass in früheren Zeiten Ad-hoc-SQL-Anweisungen nicht richtig optimiert werden konnten, da SQL Server nur auf Stapelebene optimieren / kompilieren konnte. Jetzt haben wir eine Optimierung auf Anweisungsebene, sodass eine ordnungsgemäß parametrisierte Abfrage, die von einer Anwendung stammt, denselben Ausführungsplan wie die in eine gespeicherte Prozedur eingebettete Abfrage verwenden kann.

Ich bevorzuge gespeicherte Prozeduren von der DBA-Seite aus den folgenden Gründen (und einige von ihnen können einen großen Einfluss auf die Leistung haben):

  • Wenn ich über mehrere Apps verfüge, die dieselben Abfragen wiederverwenden, kapselt eine gespeicherte Prozedur diese Logik, anstatt dieselbe Ad-hoc-Abfrage mehrmals in verschiedenen Codebasen abzulegen. Bei Anwendungen, die dieselben Abfragen wiederverwenden, kann es auch vorkommen, dass der Cache aufgebläht wird, sofern sie nicht wörtlich kopiert werden. Sogar Unterschiede in Groß- und Kleinschreibung und Leerraum können dazu führen, dass mehrere Versionen desselben Plans gespeichert werden (Verschwendung).
  • Ich kann überprüfen und Fehler beheben, was eine Abfrage tut, ohne Zugriff auf den Quellcode der Anwendung zu haben oder teure Traces auszuführen, um genau zu sehen, was die Anwendung tut.
  • Ich kann auch steuern (und im Voraus wissen), welche Abfragen die Anwendung ausführen kann, auf welche Tabellen sie zugreifen kann und in welchem ​​Kontext usw. Wenn die Entwickler Ad-hoc-Abfragen in ihre Anwendung schreiben, müssen sie dies entweder tun Jedes Mal, wenn sie Zugang zu einem Tisch benötigen, den ich nicht kenne oder nicht vorhersagen kann, oder wenn ich weniger verantwortungsbewusst / begeistert und / oder sicherheitsbewusst bin, werde ich einfach dafür werben user zu dbo damit sie aufhören mich zu nerven. In der Regel geschieht dies, wenn die Entwickler die DBAs in der Überzahl haben oder wenn die DBAs hartnäckig sind. Dieser letzte Punkt ist unser Nachteil, und wir müssen die von Ihnen benötigten Abfragen besser beantworten.
  • In diesem Zusammenhang ist eine Reihe gespeicherter Prozeduren eine sehr einfache Möglichkeit, genau zu erfassen, welche Abfragen auf meinem System ausgeführt werden. Sobald eine Anwendung Prozeduren umgehen und ihre eigenen Ad-hoc-Abfragen senden darf, muss ich einen Trace ausführen, der einen gesamten Geschäftszyklus abdeckt, oder den gesamten Anwendungscode (wieder das) analysieren Ich habe möglicherweise keinen Zugriff auf), um etwas zu finden, das wie eine Abfrage aussieht. In der Lage zu sein, die Liste der gespeicherten Prozeduren anzuzeigen (und eine einzelne Quelle sys.sql_modulesfür Verweise auf bestimmte Objekte zu verwenden), erleichtert das Leben aller erheblich.
  • Ich kann viel mehr tun, um SQL-Injection zu verhindern. Selbst wenn ich Eingaben nehme und sie mit dynamischem SQL ausführe, kann ich viel steuern, was passieren darf. Ich habe keine Kontrolle darüber, was ein Entwickler beim Erstellen von Inline-SQL-Anweisungen tut.
  • Ich kann die Abfrage (oder die Abfragen) optimieren, ohne Zugriff auf den Quellcode der Anwendung zu haben, die Fähigkeit, Änderungen vorzunehmen, die Kenntnisse der Anwendungssprache, um dies effektiv zu tun, und die Autorität (ohne Rücksicht auf den Aufwand), neu zu kompilieren und erneut bereitzustellen die App usw. Dies ist besonders problematisch, wenn die App verteilt wird.
  • Ich kann bestimmte festgelegte Optionen innerhalb der gespeicherten Prozedur erzwingen, um zu verhindern, dass einzelne Abfragen in der Anwendung langsamer, in SSMS schneller werden. Probleme. Dies bedeutet, dass für zwei verschiedene Anwendungen, die eine Ad-hoc-Abfrage aufrufen, eine vorhanden sein SET ANSI_WARNINGS ONkann und eine andere vorhanden sein SET ANSI_WARNINGS OFFkann und jede eine eigene Kopie des Plans hat. Der Plan, den sie erhalten, hängt von den verwendeten Parametern, den vorhandenen Statistiken usw. ab, wenn die Abfrage zum ersten Mal aufgerufen wird. Dies kann zu unterschiedlichen Plänen und damit zu einer sehr unterschiedlichen Leistung führen.
  • Ich kann Dinge wie Datentypen und die Verwendung von Parametern steuern, im Gegensatz zu bestimmten ORMs - einige frühere Versionen von Dingen wie EF würden eine Abfrage basierend auf der Länge eines Parameters parametrisieren. Wenn ich also einen Parameter N'Smith 'und einen anderen N' hätte Johnson: Ich würde zwei verschiedene Versionen des Plans bekommen. Sie haben das behoben. Sie haben das behoben, aber was ist noch kaputt?
  • Ich kann Dinge tun, die ORMs und andere "hilfreiche" Frameworks und Bibliotheken noch nicht unterstützen können.

Trotzdem wird diese Frage wahrscheinlich mehr religiöse Argumente hervorrufen als technische Auseinandersetzungen. Wenn wir das sehen, werden wir es wahrscheinlich herunterfahren.

Aaron Bertrand
quelle
2
Ein weiterer Grund für gespeicherte Prozeduren? Bei langen, komplizierten Abfragen müssen Sie die Abfrage jedes Mal an den Server senden, es sei denn, es handelt sich um einen Sproc. Dann drücken Sie einfach "exec sprocname" und einige Parameter. Dies kann in einem langsamen (oder ausgelasteten) Netzwerk einen Unterschied machen.
David Crowell
0

Obwohl ich den Einsender respektiere, bin ich mit der gegebenen Antwort demütig nicht einverstanden und nicht aus "religiösen Gründen". Mit anderen Worten, ich glaube, es gibt keine Möglichkeit, die Microsoft zur Verfügung gestellt hat, die den Bedarf an Anleitungen zur Verwendung gespeicherter Prozeduren verringert.

Jede Anleitung, die einem Entwickler zur Verfügung gestellt wird, der die Verwendung von SQL-Abfragen mit Rohtext bevorzugt, muss mit zahlreichen Einschränkungen versehen sein. Ich halte es daher für den klügsten Rat, die Verwendung gespeicherter Prozeduren zu fördern und Ihre Entwicklerteams davon abzuhalten, sich an der Praxis zu beteiligen Einbetten von SQL-Anweisungen in Code oder Senden von unformatierten, textbasierten SQL-Anforderungen außerhalb von SQL-SPROCs (gespeicherten Prozeduren).

Ich denke, die einfache Antwort auf die Frage, warum ein SPROC verwendet wird, lautet, wie der Absender vermutet: SPROCs werden analysiert, optimiert und kompiliert. Daher werden ihre Abfrage- / Ausführungspläne zwischengespeichert, da Sie eine statische Darstellung einer Abfrage gespeichert haben und diese normalerweise nur um Parameter variieren. Dies gilt nicht für kopierte / eingefügte SQL-Anweisungen, die sich wahrscheinlich ändern von Seite zu Seite und von Komponente / Schicht und sind oft so variabel, dass von Aufruf zu Aufruf verschiedene Tabellen, sogar Datenbanknamen, angegeben werden können. Unter Berücksichtigung dieser Art von dynamischem Ad-hocDurch die SQL-Übermittlung wird die Wahrscheinlichkeit, dass das DB-Modul den Abfrageplan für Ihre Ad-hoc-Anweisungen nach sehr strengen Regeln wiederverwendet, erheblich verringert. Hier unterscheide ich zwischen dynamischen Ad-hoc-Abfragen (im Sinne der aufgeworfenen Frage) und der Verwendung des effizienten Systems SPROC sp_executesql.

Insbesondere gibt es die folgenden Komponenten:

  • Serielle und parallele Abfragepläne, die den Benutzerkontext nicht enthalten und die Wiederverwendung durch die DB-Engine ermöglichen.
  • Ausführungskontext, der die Wiederverwendung eines Abfrageplans durch einen neuen Benutzer mit unterschiedlichen Datenparametern ermöglicht.
  • Prozedur-Cache, den die DB-Engine abfragt, um die angestrebten Effizienzen zu erzielen.

Wenn eine SQL-Anweisung von einer Webseite ausgegeben wird, die als "Ad-hoc-Anweisung" bezeichnet wird, sucht das Modul nach einem vorhandenen Ausführungsplan, um die Anforderung zu verarbeiten. Da es sich um von einem Benutzer gesendeten Text handelt, wird dieser aufgenommen, analysiert, kompiliert und ausgeführt, sofern er gültig ist. Zu diesem Zeitpunkt wird es eine Abfrage von Null erhalten. Abfragekosten werden verwendet, wenn das DB-Modul seinen Algorithmus verwendet, um zu bestimmen, welche Ausführungspläne aus dem Cache entfernt werden sollen.

Ad-hoc-Abfragen erhalten standardmäßig einen ursprünglichen Abfragekostenwert von Null. Bei der anschließenden Ausführung des exakt gleichen Ad-hoc-Abfragetexts durch einen anderen Benutzerprozess (oder denselben) werden die aktuellen Abfragekosten auf die ursprünglichen Kompilierungskosten zurückgesetzt. Da die Kosten für die Kompilierung von Ad-hoc-Abfragen gleich Null sind, ist die Möglichkeit der Wiederverwendung nicht gut. Natürlich ist Null die am wenigsten wertvolle Ganzzahl, aber warum sollte sie entfernt werden?

Wenn Speicherprobleme auftreten und wenn Sie eine häufig verwendete Site haben, verwendet das DB-Modul einen Bereinigungsalgorithmus, um zu ermitteln, wie es den vom Prozedurcache verwendeten Speicher wiederherstellen kann. Es verwendet die aktuellen Abfragekosten, um zu entscheiden, welche Pläne entfernt werden sollen. Wie Sie sich vorstellen können, werden Pläne mit einem Preis von Null als erstes aus dem Cache entfernt, da Null im Wesentlichen "keine aktuellen Benutzer dieses Plans oder Verweise auf diesen Plan" bedeutet.

  • Hinweis: Ad-hoc-Ausführungspläne - Die aktuellen Kosten werden von jedem Benutzerprozess um die ursprünglichen Kompilierungskosten des Plans erhöht. Die maximalen Kosten eines Plans dürfen jedoch nicht höher sein als die ursprünglichen Kompilierungskosten ... bei Ad-hoc-Abfragen ... Null. Es wird also um diesen Wert "erhöht" ... Null - was im Wesentlichen bedeutet, dass es der Plan mit den niedrigsten Kosten bleibt.

Daher ist es sehr wahrscheinlich, dass ein solcher Plan zuerst geräumt wird, wenn Speicherdruck entsteht.

Wenn Sie also einen Server mit viel Arbeitsspeicher haben, der "über Ihre Anforderungen hinaus" geht, tritt dieses Problem möglicherweise nicht so häufig auf wie bei einem ausgelasteten Server, der nur "genügend" Arbeitsspeicher hat, um seine Arbeitslast zu bewältigen. (Die Speicherkapazität und Auslastung des Servers ist leider etwas subjektiv / relativ, der Algorithmus jedoch nicht.)

Wenn ich in Bezug auf einen oder mehrere Punkte sachlich falsch liege, bin ich auf jeden Fall bereit, korrigiert zu werden.

Zuletzt schrieb der Autor:

"Jetzt haben wir eine Optimierung auf Anweisungsebene, sodass eine ordnungsgemäß parametrisierte Abfrage, die von einer Anwendung stammt, denselben Ausführungsplan wie die in eine gespeicherte Prozedur eingebettete Abfrage verwenden kann."

Ich glaube, der Autor bezieht sich auf die Option "Für Ad-hoc-Workloads optimieren".

In diesem Fall ist mit dieser Option ein zweistufiger Prozess möglich, bei dem nicht sofort der vollständige Abfrageplan an den Prozedurcache gesendet wird. Es wird dort nur ein kleinerer Abfragestub gesendet. Wenn ein genauer Abfrageaufruf zurück an den Server gesendet wird, während sich der Abfragestub noch im Prozedurcache befindet, wird zu diesem Zeitpunkt der vollständige Abfrageausführungsplan im Prozedurcache gespeichert. Dies spart Speicher, der es dem Auslagerungsalgorithmus bei Speicherdruckereignissen möglicherweise ermöglicht, den Stub seltener auszuräumen als bei einem größeren Abfrageplan, der zwischengespeichert wurde. Dies hängt wiederum vom Arbeitsspeicher und der Auslastung Ihres Servers ab.

Sie müssen diese Option jedoch aktivieren, da sie standardmäßig deaktiviert ist.

Abschließend möchte ich betonen, dass Entwickler SQL häufig nur deshalb in Seiten, Komponenten und andere Bereiche einbetten, weil sie flexibel sein und dynamische SQL-Abfragen an das Datenbankmodul senden möchten. In einem realen Anwendungsfall ist es daher unwahrscheinlich, dass derselbe Text, Call-over-Call, übermittelt wird, wie auch die von uns angestrebten Zwischenspeicherungs- / Effizienzvorteile, wenn Ad-hoc-Abfragen an SQL Server gesendet werden.

Weitere Informationen finden Sie unter:

https://technet.microsoft.com/en-us/library/ms181055(v=sql.105).aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

Am besten
Henry

Henry
quelle
4
Ich habe mehrere Absätze Ihres Beitrags zwei- oder dreimal sorgfältig durchgelesen, und ich habe immer noch keine Ahnung, welche Gedanken Sie vermitteln möchten. In einigen Fällen scheinen Sie am Ende der Sätze das genaue Gegenteil von dem zu sagen, was der Satz zu Beginn zu sagen versucht hat. Sie müssen diese Einsendung wirklich sorgfältig prüfen und bearbeiten.
Pieter Geerkens
Danke für das Feedback Pieter. Wenn dies der Fall ist, kann es sein, dass ich meine Sätze verkürzen sollte, um den Punkt klarer zu machen. Können Sie bitte ein Beispiel dafür geben, wo ich das Gegenteil des ursprünglichen Gedankens zu sagen scheine? Sehr geschätzt.
Henry
Nein, ich meinte nicht Optimieren für Ad-hoc-Workloads, sondern Optimierung auf Anweisungsebene. In SQL Server 2000 würde beispielsweise eine gespeicherte Prozedur als Ganzes kompiliert, sodass die App keinen Plan für ihre eigene Ad-hoc-Abfrage wiederverwenden konnte, der mit einer bestimmten Prozedur übereinstimmte. Ich werde sagen, dass ich Pieter zustimme - viele der Dinge, die Sie sagen, sind schwer zu befolgen. Dinge wie "Ich glaube, es gibt keine Möglichkeit, die Microsoft zur Verfügung gestellt hat, die den Bedarf an Anleitungen zur Verwendung gespeicherter Prozeduren verringert." sind unnötig komplex und erfordern viel zu viel Analyse, um verstanden zu werden. MEINER BESCHEIDENEN MEINUNG NACH.
Aaron Bertrand
1
Es scheint, als ob Ihre Abneigung gegen "Ad-hoc" -SQL auf der Idee beruht, dass sich das SQL zwischen den Ausführungen irgendwie ändert. Dies ist völlig unwahr, wenn es um die Parametrisierung geht.
b_levitt
0

TLDR: Es gibt keinen nennenswerten Leistungsunterschied zwischen den beiden, solange Ihre Inline-SQL parametrisiert ist.

Dies ist der Grund, warum ich gespeicherte Prozeduren langsam auslaufe:

  • Wir betreiben eine Beta-Anwendungsumgebung - eine Umgebung parallel zur Produktion, in der die Produktionsdatenbank gemeinsam genutzt wird. Da sich DB-Code auf Anwendungsebene befindet und Änderungen an der DB-Struktur nur selten vorkommen, können Mitarbeiter neue Funktionen über die Qualitätssicherung hinaus bestätigen und Bereitstellungen außerhalb des Produktionsbereitstellungsfensters ausführen, jedoch weiterhin Produktionsfunktionen und nicht kritische Korrekturen bereitstellen. Dies wäre nicht möglich, wenn sich die Hälfte des Anwendungscodes in der DB befände.

  • Wir üben Devops auf Datenbankebene (Octopus + Dacpacs). Während Business Layer und höher im Grunde genommen gelöscht und ersetzt werden können und die Wiederherstellung genau umgekehrt, gilt dies nicht für die inkrementellen und potenziell destruktiven Änderungen, die an den Datenbanken vorgenommen werden müssen. Aus diesem Grund ziehen wir es vor, unsere DB-Bereitstellungen leichter und seltener durchzuführen.

  • Um nahezu exakte Kopien desselben Codes für optionale Parameter zu vermeiden, verwenden wir häufig das Muster 'where @var is null oder @ var = table.field'. Mit einem gespeicherten Prozess erhalten Sie wahrscheinlich trotz unterschiedlicher Absichten den gleichen Ausführungsplan. Auf diese Weise treten entweder Leistungsprobleme auf oder es werden zwischengespeicherte Pläne mit Hinweisen zum erneuten Kompilieren entfernt. Mit einem einfachen Code, der einen "Signatur" -Kommentar an das Ende des SQL anfügt, können wir jedoch verschiedene Pläne erzwingen, basierend darauf, welche Variablen null waren (nicht als anderer Plan für alle Variablenkombinationen zu interpretieren - nur null vs nicht null).

  • Ich kann dramatische Änderungen an den Ergebnissen vornehmen, mit nur geringfügigen Änderungen im laufenden Betrieb an der SQL. Zum Beispiel kann ich eine Anweisung haben, die mit zwei CTEs, "Raw" und "ReportReady", abgeschlossen wird. Es gibt nichts, was besagt, dass beide CTEs verwendet werden müssen. Meine SQL-Anweisung kann dann sein:

    ...

    wähle * aus {(Format)} "

Auf diese Weise kann ich für einen optimierten API-Aufruf und einen Bericht, der detaillierter sein muss, genau dieselbe Geschäftslogikmethode verwenden, um sicherzustellen, dass keine komplizierte Logik dupliziert wird.

  • Wenn Sie eine "procs only" -Regel haben, erhalten Sie eine Menge Redundanz in der überwiegenden Mehrheit Ihres SQL, was letztendlich zu CRUD führt. Sie binden alle Parameter und listen alle diese Parameter in der proc-Signatur auf (und jetzt Wenn Sie sich in einer anderen Datei in einem anderen Projekt befinden, ordnen Sie diese einfachen Parameter ihren Spalten zu. Dies schafft eine ziemlich unzusammenhängende Entwicklungserfahrung.

Es gibt triftige Gründe, procs zu verwenden:

  • Sicherheit - Sie haben hier eine weitere Ebene, die die App durchlaufen muss. Wenn das Anwendungsdienstkonto keine Tabellen berühren darf, sondern nur die Berechtigung zum Ausführen von Prozessen hat, haben Sie zusätzlichen Schutz. Das ist nicht selbstverständlich, da es Kosten verursacht, aber es ist eine Möglichkeit.

  • Wiederverwendung - Während ich sagen würde, dass die Wiederverwendung größtenteils auf Unternehmensebene erfolgen sollte, um sicherzustellen, dass Sie nicht mit der Datenbank zusammenhängende Geschäftsregeln umgehen, gibt es immer noch den gelegentlichen, einfachen Typ von Utility-Prozessen und -Funktionen, die "überall" verwendet werden.

Es gibt einige Argumente, die Procs nicht wirklich unterstützen oder die IMO einfach zu mildern sind:

  • Wiederverwendung - Ich erwähnte dies oben als "Plus", wollte aber auch hier erwähnen, dass die Wiederverwendung größtenteils auf Unternehmensebene erfolgen sollte. Ein Vorgang zum Einfügen eines Datensatzes sollte nicht als "wiederverwendbar" betrachtet werden, wenn die Business-Schicht möglicherweise auch andere Nicht-DB-Services überprüft.

  • Cache-Plan aufgebläht - dies ist nur dann ein Problem, wenn Sie Werte verketten und nicht parametrisieren. Die Tatsache, dass Sie selten mehr als einen Plan pro Proc erhalten, schmerzt Sie tatsächlich oft, wenn Sie ein 'oder' in einer Abfrage haben

  • Anweisungsgröße - Ein zusätzliches KByte an SQL-Anweisungen über dem Prozessnamen ist im Vergleich zu den zurückkommenden Daten normalerweise vernachlässigbar. Wenn es für Entities in Ordnung ist, ist es für mich in Ordnung.

  • Anzeigen der genauen Abfrage - Um das Auffinden von Abfragen im Code zu vereinfachen, müssen Sie lediglich den aufrufenden Speicherort als Kommentar zum Code hinzufügen. Das Kopieren von Code von c # -Code in ssms ist so einfach wie die kreative Interpolation und Verwendung von Kommentaren:

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
  • SQL Injection - Parametrisieren Sie Ihre Abfragen. Getan. Dies kann tatsächlich rückgängig gemacht werden, wenn der Prozess stattdessen dynamisches SQL verwendet.

  • Bereitstellung umgehen - Wir üben Devops auch auf Datenbankebene aus, daher ist dies für uns keine Option.

  • "Langsam in der Anwendung, schnell in SSMS" - Dies ist ein Plan-Caching-Problem, das beide Seiten betrifft. Die gesetzten Optionen bewirken lediglich die Kompilierung eines neuen Plans, der das Problem für THE ONE SET OFF-Variablen zu beheben scheint. Dies beantwortet nur, warum Sie unterschiedliche Ergebnisse sehen - die eingestellten Optionen selbst beheben NICHT das Problem des Parameter-Schnüffelns.

  • Inline-SQL-Ausführungspläne werden nicht zwischengespeichert - einfach falsch. Eine parametrisierte Anweisung wird ebenso wie der Proc-Name schnell gehasht, und dann wird ein Plan von diesem Hash gesucht. Es ist zu 100% dasselbe.

  • Um klar zu sein, ich spreche von rohem Inline-SQL-Code, der nicht aus einem ORM generiert wurde. Wir verwenden nur Dapper, das bestenfalls ein Mikro-ORM ist.

https://weblogs.asp.net/fbouma/38178

https://stackoverflow.com/a/15277/852208

b_levitt
quelle