Microsoft SQL Server-Abfrageprozessor - Interna und Architektur

Veröffentlicht: 15. Sep 2000 | Aktualisiert: 14. Jun 2004

Von Hal Berenson und Kalen Delaney

Auf dieser Seite

Einführung Einführung
SQL Server ist ein Client/Server-System SQL Server ist ein Client/Server-System
Clientarchitektur Clientarchitektur
Clientschnittstellen Clientschnittstellen
Der Server aus der Perspektive des Clients Der Server aus der Perspektive des Clients
Serverarchitektur Serverarchitektur
Client/Server-Kommunikation bei der SQL-Verarbeitung Client/Server-Kommunikation bei der SQL-Verarbeitung
Das Vorbereiten/Ausführen-Modell Das Vorbereiten/Ausführen-Modell
Aufrufen von gespeicherten Prozeduren Aufrufen von gespeicherten Prozeduren
SQL Manager SQL Manager
Kompilierung und Ausführung Kompilierung und Ausführung
Prozedurcache Prozedurcache
Verarbeitung der SQL-Anforderung des Clients Verarbeitung der SQL-Anforderung des Clients
Kompilierung Kompilierung
Optimierung Optimierung
Ausführung Ausführung
Zusammenfassung Zusammenfassung

Dieser Artikel untersucht, wie Microsoft SQL Server-Abfragen auf dem Client verarbeitet werden, wie verschiedene Clients mit SQL Server kommunizieren und welche Schritte SQL Server zum Verarbeiten der Clientanforderungen ausführt.

 

Einführung

Die Interna und die Architektur von Microsoft® SQL Server™ sind ein sehr umfassendes Thema. Daher beschränken wir uns in diesem Artikel auf die Bereiche, die für Entwickler von Interesse sind, und konzentrieren uns dabei auf die Themen, die in anderen Abhandlungen noch nicht ausführlich behandelt wurden. Bei der Diskussion der Architektur von SQL Server befassen wir uns in erster Linie damit, wie Abfragen auf dem Client verarbeitet werden, wie verschiedene Clients mit SQL Server kommunizieren und welche Schritte SQL Server zum Verarbeiten der Clientanforderungen ausführt. Für die weiteren Aspekte von SQL Server stehen Ihnen zahlreiche andere Informationsquellen zur Verfügung. Das von Microsoft Press veröffentlichte Werk Inside SQL Server 7.0 von Ron Soukup und Kalen Delaney behandelt die Mechanismen und das Verhalten des Speichermoduls sehr ausführlich, befasst sich aber nur am Rande mit dem Abfrageprozessor. Diese Lücke soll durch diesen Artikel geschlossen werden.

Wir hoffen, dass Ihnen diese Informationen bei Ihrer Arbeit helfen und Sie damit noch bessere Anwendungen erstellen können. Mit Hilfe dieses Artikels sollten Sie Leistungsprobleme aus einem völlig neuen Blickwinkel betrachten und besser verstehen können.

 

SQL Server ist ein Client/Server-System

Schon seit Jahren spricht man von SQL Server als einem Client/Server-System. Tatsächlich war Sybase DataServer, aus dem SQL Server ursprünglich entwickelt wurde, das erste kommerzielle relationale Datenbanksystem, das als Client/Server-System entwickelt wurde. Was aber bedeutet das? Es bedeutet nicht nur, dass SQL Server ein Two-tier-System ist. Im herkömmlichen Sinne ist ein Two-tier-System ein System, bei dem die Clientanwendung auf einem Computer ausgeführt wird und Anforderungen an einen Server auf einem anderen Computer sendet. Bei SQL Server bedeutet Client/Server, dass sich ein Stück von SQL Server, der Client-API-Teil, getrennt von der eigentlichen Serverkomponente irgendwo an einem Remotestandort in der Prozessstruktur befindet.

Im klassischen Two-tier-Modell befindet sich der Clientteil, der große Mengen Clientanwendungslogik und Geschäftslogik umfasst, auf dem Desktopcomputer und sendet Anforderungen direkt an das Datenbanksystem. Der Client erhält dann als Antwort auf seine Anforderungen Daten vom Server.

Dasselbe Modell gilt bei Systemen mit drei Ebenen. SQL Server wurde lange Zeit mit Transaktionsverarbeitungsmonitoren wie Tuxedo von BEA und ACMSxp von Compaq verwendet, die das klassische Three-tier-Modell der 70er und 80er Jahre repräsentieren. Das Three-tier-Modell dominiert auch in den modernen webbasierten Anwendungen, wie Microsoft MTS und dem neuen COM+ 1.0. Aus der Perspektive von SQL Server ist der Client einer Three-tier-Lösung eine Logik in der mittleren Ebene. Diese mittlere Ebene kommuniziert direkt mit dem Datenbanksystem. Der eigentliche Desktop, oder reduzierte Client, verwendet einige anderen Mechanismen und kommuniziert normalerweise direkt mit der mittleren Ebene, nicht mit dem Datenbanksystem. Abbildung 1 zeigt diese Struktur.

Bild01

Abbildung 1. Three-tier-Systemmodell

 

Clientarchitektur

Vom Standpunkt der Architektur aus betrachtet, spielt es für die relationale Serverkomponente von SQL Server keine Rolle, wo ein Client ausgeführt wird. Wenn Sie die Anwendung auf dem Computer ausführen, auf dem auch SQL Server ausgeführt wird, ändert sich das Client/Server-Modell, so weit es SQL Server betrifft, dadurch nicht. Der Server führt einen separaten Multithread-Prozess aus und bearbeitet die von Clients eingehenden Anforderungen – unabhängig davon, wo sich diese Clients befinden. Der Clientcode selbst ist eine separate DLL (Dynamic Link Library), die innerhalb der Clientanwendung ausgeführt wird. Die tatsächliche Schnittstelle zu SQL Server ist das TDS-Protokoll (Tabellenförmiger Datenstrom), das zwischen dem Client und dem Server verwendet wird.

Eine häufig gestellte Frage ist "Was ist die systemeigene Schnittstelle von SQL Server?". Lange Zeit sträubten sich viele Entwickler gegen Schnittstellen wie ODBC (Offene Datenbankverbindung), da sie glaubten, dass DB-Library – die von Sybase entwickelte Client-API – die systemeigene Schnittstelle zu SQL Server ist. In der Tat hat der relationale Server von SQL Server keine systemeigene API. Seine Schnittstelle ist TDS, das Protokoll für den Kommunikationsstream zwischen dem Client und dem Server. TDS schließt die vom Client an den Server gesendeten SQL-Anweisungen sowie die vom Server zurückgesendeten Resultsets ein. Jede API, die TDS direkt verarbeitet, ist eine systemeigene SQL Server-Schnittstelle.

Betrachten wir einmal die in Abbildung 2 dargestellten Komponenten des Clients. Einige Teile der Clientarchitektur werden hier nicht behandelt, da sie über SQL Server hinausgehen. Wenn Sie eine Anwendung erstellen, spielen diese Teile jedoch durchaus eine Rolle. Über die verschiedenen Objektmodelle sind Entwickler in der Regel am besten informiert. Wenn Sie eine ASP- oder Microsoft Visual Basic®-Anwendung erstellen, kommunizieren Sie über ADO mit der Datenbank. Sie rufen APIs der niedrigen Ebene, wie ODBC oder OLE-DB, nicht direkt auf – ADO gehört zu OLE-DB und RDO zu ODBC. Dieses Objektmodell, das der am häufigsten verwendete Teil des Programmiermodells ist, ist also im eigentlichen Sinne kein Teil der Clientarchitektur von SQL Server. Darüber hinaus gibt es einige zusätzliche Komponenten, die auf dieser Ebene überhalb der SQL Server-Infrastruktur eingesetzt werden können. Eine solche Komponente ist z.B. Session Pooling Service Provider von OLE-DB.

Bild02

Abbildung 2. Clientarchitektur

 

Clientschnittstellen

Es gibt zwei Schnittstellen, die für SQL Server 7.0 als systemeigen betrachtet werden können – OLE-DB und ODBC. Die DB-Library-Schnittstelle ist insofern auch systemeigen, als dass sie TDS verwendet. Sie verwendet jedoch eine ältere Version von TDS, die eine Konvertierung auf dem Server erfordert. Während bestehende DB-Library-Anwendungen weiterhin mit SQL Server 7.0 funktionieren, sind viele neue Features und Leistungsverbesserungen nur über ODBC und OLE DB verfügbar. Eine Aktualisierung von DB-Library auf die neuen Funktionen von SQL Server 7.0 würde zu vielen Inkompatibilitäten mit existierenden Anwendungen führen und folglich Änderungen der Anwendungen erforderlich machen. ODBC hat DB-Library vor fünf Jahren als bevorzugte API für neue SQL Server-Anwendungen ersetzt. Die Einführung einer neuen inkompatiblen Version von DB-Library war daher nicht sinnvoll.

Diese Client-APIs verfügen jeweils über drei Komponenten (siehe Abbildung 2 oben). Durch den oberen Teil werden die spezifischen API-Details implementiert, z.B. das Aussehen eines Rowsets, eines Cursors usw. Die TDS-Formatierung nimmt eine Anforderung (z.B. eine SQL-Anweisung), verpackt sie als TDS-Nachricht, sendet sie an SQL Server, erhält die Ergebnisse und leitet diese dann an die Schnittstellenimplementation weiter.

Es gibt auch allgemeinen Bibliothekscode, der von allen Providern verwendet wird. Die BCP-Einrichtung ist z.B. eine Bibliothek, die sowohl von ODBC als auch von OLE-DB aufgerufen wird. DTC ist ein weiteres Beispiel. Die kanonische SQL-Syntax für ODBC, die CALL-Syntax mit Parametermarkierungen, wird ebenfalls von allen Providern verwendet.

Das TDS-Protokoll ist mit Ausnahme der oben genannten Einschränkung, dass DB-Library weiterhin SQL Server 6.5 verwendet, für alle APIs gleich. ODBC und OLE-DB verwenden für die Kommunikation mit SQL Server 7.0 die Version 7.0, sie können jedoch auch mit SQL Server 6.5 oder 6.0 kommunizieren. Darüber hinaus gibt es die Netzwerkbibliotheken, die Abstraktionsschicht, in der sowohl der Client als auch der Server eine Netzwerkabstraktionsschnittstelle verwenden und sich nicht um IPX oder TCP/IP kümmern müssen. Auf die Funktionsweise der Netzwerkbibliotheken möchten wir hier nicht ausführlich eingehen. Es genügt zu wissen, dass ihre grundsätzliche Funktion darin besteht, Netzwerkkommunikationsdetails der unteren Ebene gegenüber der restlichen Software zu verbergen.

 

Der Server aus der Perspektive des Clients

Wie bereits erwähnt, kommuniziert ein Client in erster Linie über TDS-Nachrichten mit SQL Server. TDS ist ein einfaches Protokoll. Wenn SQL Server eine Nachricht empfängt, ist dies mit einem Ereignis zu vergleichen. Zunächst sendet ein Client auf einer Verbindung eine Anmeldenachricht (bzw. ein Ereignis) und erhält darauf eine Erfolgs- oder Fehlerantwort. Wenn Sie die Nachricht als SQL-Anweisung senden möchten, können Sie eine SQL-Sprachnachricht verwenden, die der Client verpacken und an SQL Server senden kann. Auch wenn Sie eine gespeicherte Prozedur, eine Systemprozedur oder eine gespeicherte Pseudosystemprozedur (wird im Folgenden behandelt) aufrufen möchten, kann der Client eine RPC-Nachricht senden. Dies entspricht einem RPC-Ereignis auf dem SQL Server. In den letzten beiden Fällen sendet der Server die Ergebnisse in einem Tokendatenstrom zurück. Microsoft dokumentiert die eigentlichen TDS-Nachrichten nicht, da diese als privater Vertrag zwischen SQL Server-Komponenten betrachtet werden.

Im Katalog gespeicherte Prozeduren sind ein weiterer wichtiger Teil der Client/Server-Interaktion. Sie wurden erstmals in SQL Server 6.0 in ODBC verwendet und umfassten Prozeduren wie sp_tables und sp_columns. Die ODBC- und OLE-DB-APIs definieren standardmäßige Methoden zum Beschreiben der Metadaten über Datenbankobjekte. Diese Standards müssen für alle Arten von RDBMS-Servern anwendbar sein und werden nicht auf die Systemtabellen von SQL Server abgestimmt. Anstatt den Client mehrere Systemtabellenanforderungen an den Server senden zu lassen und diese Standardsicht der Metadaten auf dem Client zu erstellen, wurde eine Gruppe systemgespeicherter Prozeduren erstellt. Diese befinden sich auf dem Server und senden die Informationen im richtigen Format für die jeweilige API zurück. Auf diese Weise können viele wichtige Metadatenanforderungen in einem einzigen Roundtrip verarbeitet werden.

Die für ODBC geschriebenen Prozeduren wurden dokumentiert und eignen sich i.Allg. für Programmierer, die Systemtabelleninformationen benötigen, die nicht über andere Mechanismen bereitgestellt werden. Dadurch können Transact-SQL-Prozeduren und DB-Library-Anwendungen auf Metadaten zugreifen, ohne komplexe Abfragen für die SQL Server-Systemtabellen schreiben zu müssen. Außerdem wird die Anwendung von Änderungen isoliert, die Microsoft zukünftig an den Systemtabellen vornimmt.

OLE DB definiert eine Reihe von Schemarowsets, die zwar analog zu den ODBC-Metadaten sind, sich jedoch von ihnen unterscheiden. Zum Auffüllen dieser Schemarowsets wurde eine neue Gruppe im Katalog gespeicherter Prozeduren erstellt. Diese neuen gespeicherten Prozeduren wurden jedoch nicht dokumentiert, da sie frühere Funktionen duplizieren. Da es mehrere Methoden zum Abrufen von Metadaten gibt, hat sich das SQL Server-Team dagegen entschieden, eine Methode offen zu legen, durch die das Programmiermodell nicht erweitert wird.

Es gibt noch einen dritten Aspekt bezüglich der Art und Weise, wie ein Client mit einem Server kommuniziert. Dieser Aspekt trat in SQL Server 6.0 erstmals auf, wurde jedoch kaum publik gemacht. Es handelt sich dabei um den Begriff der pseudo-systemgespeicherten Prozeduren – diese sind in SQL Server 7.0 von großer Bedeutung. Als serverseitige Cursor erstmals für SQL Server 6.0 entwickelt wurden, mussten die Entwickler entscheiden, wie sie Client/Server-Interaktionen verwalten würden. Da Cursor es ermöglichen, dass Daten Zeile für Zeile zurückgegeben wurden, ohne dass der Client weitere SQL-Anweisungen ausgibt, ließen sie sich nicht problemlos mit den verfügbaren TDS-Nachrichten verwenden. Die Entwickler hätten dem TDS-Protokoll weitere Nachrichten hinzufügen können. Dadurch hätten jedoch zu viele andere Komponenten geändert werden müssen. Aus Gründen der Interoperabilität durfte sich die TDS-Version in SQL Server 6.0 außerdem nicht zu sehr von der Sybase-Version unterscheiden. Daher erarbeiteten die Entwickler einen alternativen Mechanismus. Sie richteten das neue Feature (serverseitige Cursor) so ein, dass es als pseudo-systemgespeicherte Prozeduren erscheint, obwohl gespeicherte Prozeduren in Wirklichkeit nur Einstiegspunkte zur SQL Server-Codebasis sind. Sie werden mit Hilfe von standardmäßigen RPC-TCS-Nachrichten von einer Clientanwendung aufgerufen. Die Bezeichnung "pseudo-systemgespeicherte Prozeduren" rührt daher, dass sie auf dem Client wie eine gespeicherte Prozedur aufgerufen werden. Anders als andere gespeicherte Prozeduren bestehen sie jedoch nicht aus einfachen SQL-Anweisungen. Die meisten dieser pseudo-systemgespeicherten Prozeduren sind privat und nicht dokumentiert. Bei den Cursorprozeduren legen alle APIs ihre eigene Gruppe von Cursor-API-Modellen und ihre eigenen Cursorbearbeitungsfunktionen offen. Daher gibt es keinen Grund, die gespeicherten Prozeduren selbst zu dokumentieren. Sogar in der Transact-SQL-Sprache gibt es eine Syntax, die durch Verwendung von DECLARE, OPEN, FETCH usw. Cursor offen legt. Folglich bestand nicht die Notwendigkeit, pseudo-systemgespeicherte Prozeduren wie sp_cursor offen zu legen, die nur intern verwendet werden.

ODBC und OLE DB legen das Konzept parametrisierter Abfragen und das Vorbereiten/Ausführen-Modell offen. Vor SQL Server 7.0 wurden diese Konzepte mit Hilfe von Code in der Client-API implementiert. In SQL Server 7.0 hat Microsoft den relationalen Server um Unterstützung für diese Konzepte erweitert und diese Unterstützung durch pseudo-systemgespeicherte Prozeduren offen gelegt. Diese Features und die Art und Weise, wie sie vom Server unterstützt werden, werden in diesem Artikel auch noch untersucht. Die Unterstützung für parametrisierte Abfragen über die Prozedur sp_executesql wurde für die direkte Verwendung mit Transact-SQL und DB-Library als besonders nützlich erachtet. Daher wurde diese Prozedur dokumentiert. Die Prozeduren zum Vorbereiten/Ausführen werden ausschließlich vom ODBC-Treiber und OLE DB-Provider verwendet.

Alle Clients, die mit SQL Server kommunizieren können, basieren auf diesen drei Funktionsgruppen: dem TDS-Protokoll, den im Katalog gespeicherten Prozeduren und den pseudo-systemgespeicherten Prozeduren.

 

Serverarchitektur

SQL Server, oder genauer gesagt der relationale Server von SQL Server, wird häufig so beschrieben, dass er aus zwei grundlegenden Hälften besteht – dem relationalen Modul und dem Speichermodul. Wie bereits erwähnt, sind viele Details des Speichermoduls bereits in anderen Informationsquellen dokumentiert. Daher konzentriert sich dieser Artikel auf die Funktionalität des relationalen Moduls. Abbildung 3 zeigt die Hauptkomponenten des relationalen Moduls von SQL Server. Die dargestellten Komponenten können in drei Gruppen von Untersystemen unterteilt werden. Auf der linken Seite befinden sich die Komponenten für das Kompilieren von Abfragen, einschließlich Abfrageoptimierer. Der Optimierer ist einer der geheimnisvollsten Teile aller relationalen Datenbankmodule und gleichzeitig einer der kritischsten Leistungsaspekte. Der Abfrageoptimierer ist dafür zuständig, die nicht prozeduralen SQL-Anforderungen in eine Reihe von Datenträger-E/A-Vorgängen, Filterungen und sonstigen Prozedurlogiken zu übersetzen, mit denen die Anforderung effizient bearbeitet werden kann. Auf der rechten Seite befindet sich die Ausführungsinfrastruktur. Diese besteht aus sehr viel weniger Komponenten. Das Ergebnis der Arbeit der Kompilierungskomponenten ist ein Objekt, das mit Hilfe einiger weniger Dienste direkt ausgeführt werden kann.

Bild03

Abbildung 3. Serverarchitektur

In der Mitte der Abbildung befindet sich der so genannte SQL Manager. Er steuert sämtliche Abläufe innerhalb von SQL Server. RPC-Nachrichten werden vom SQL Manager verarbeitet, und in SQL Server 7.0 stellen diese den Großteil der Funktionsaufrufe dar, die von Clients eingehen. Die zuvor beschriebenen pseudo-systemgespeicherten Prozeduren sind selbstverständlich auch ein Teil des SQL Manager. SQL-Anweisungen, die normalerweise als TDS-SQL-Sprachnachrichten eingehen, werden direkt von der Kompilierungsseite verarbeitet. Dies ist in SQL Server 7.0 weniger üblich als in früheren Versionen, jedoch noch immer gebräuchlich. Die Ergebnisse werden von den Komponenten im Ausführungsmodul zurückgesendet, die zum Formatieren der TDS-Ergebnisnachrichten ODS aufrufen.

Der Großteil dieser Ausgabe stammt von der Ausführungsseite der Abbildung, und die Ergebnisse werden vom Ausdrucksdienst ausgegegeben. Die Ausdrucksdienstbibliothek ist eine Komponente, die Datenkonvertierung, Prädikatevaluierung (Filterung) und arithmetische Berechnungen ausführt. Sie arbeitet auch mit der ODS-Schicht zusammen, um die Ausgabeergebnisse als TDS-Nachrichten zu formatieren.

Einige Komponenten, die innerhalb des relationalen Moduls zusätzliche Dienste bereitstellen, werden hier nur kurz erwähnt. Eine dieser Komponenten ist die Katalogdienstkomponente, die Definitionsanweisungen wie CREATE TABLE, CREATE VIEW usw. verarbeitet. Katalogdienste verarbeiten auch Systemtabellen und stellen die Systemtabellen dar, bei denen es sich um Pseudotabellen handelt. Die Katalogdienstkomponente befindet sich primär im relationalen Modul, ein Drittel der Komponente operiert jedoch im Bereich des Speichermoduls. Sie wird daher als gemeinsam genutzte Komponente behandelt.

Eine weitere Komponente des relationalen Moduls ist UMS (User Mode Scheduler), der interne SQL Server-Planer für Fiber und Threads. Ein hoch entwickelter interner Mechanismus plant entsprechend der Serverkonfiguration, wie die Arbeit Fibers oder Threads zugewiesen wird, und ermöglicht es SQL Server, einen geeigneten Lastenausgleich für die Prozessoren in einem SMP-System auszuführen. UMS verhindert auch, dass SQL Server abstürzt, weil zu viele Threads gleichzeitig ausgeführt werden. Die letzte Komponente sind die bekannten Systemprozeduren, die natürlich Teil des relationalen Moduls sind. Sie sind eindeutig kein Servercode, da Sie den Transact-SQL-Code, der diese Prozeduren definiert, auf einfache Weise mit Hilfe von sp_helptext untersuchen können. Da ihre Aufgabe darin besteht, einfache Serverfunktionen wie Systemtabellen auf einer höheren und für Anwendungen geeigneteren Ebene offen zu legen, werden Systemprozeduren jedoch als Teil des Servers behandelt. Wenn Anwendungsentwickler die Systemprozeduren höherer Ebene – die auch einfacher zu verwenden sind – als Schnittstelle verwendet haben, funktionieren ihre Anwendungen auch dann weiterhin, wenn die einfachen Systemtabellen von Version zu Version geändert werden.

 

Client/Server-Kommunikation bei der SQL-Verarbeitung

Im Folgenden werden wir untersuchen, was auf der Clientseite passiert, wenn eine Clientanwendung mit dem SQL Server kommuniziert. Dies ist ein Beispiel für einen ODBC-Aufruf:

SQLExecDirect(hstmt, "SELECT * FROM parts where partid = <span class=cfe>7</span>", SQL_NTS)

(Es gibt ein nahezu direktes Äquivalent dieses Aufrufs für OLE-DB, mit dem wir uns hier nicht beschäftigen, da die Verarbeitung praktisch identisch mit der für den ODBC-Aufruf ist.) Bei diesem ODBC-Aufruf wird eine SQL-Anweisung zur Ausführung an SQL Server gesendet.

Bei dieser speziellen Abfrage wählen wir alle Spalten in der parts-Tabelle aus, für die die Zeile(n) eine bestimmte Teile-ID enthält bzw. enthalten. Dies ist ein klassisches Beispiel für eine Ad-hoc-SQL-Abfrage. In Versionen vor SQL Server 7.0 war einer der Unterschiede zwischen Ad-hoc-SQL-Abfragen und einer gespeicherten Prozedur, dass die vom Abfrageoptimierer erzeugten Pläne niemals zwischengespeichert wurden. Die Abfragen gingen ein, wurden kompiliert und ausgeführt, und anschließend wurde der Plan gelöscht. In SQL Server 7.0 gibt es, wie wir später sehen werden, einen Mechanismus, mit dem die Pläne für Ad-hoc-Abfragen zwischengespeichert werden können.

Bevor diese Anweisung an SQL Server gesendet werden kann, müssen einige Fragen gestellt werden. Alle Clients bieten eine Form von Cursor. Eine vom Client intern zu stellende Frage muss folglich sein, welche Art von Resultset oder Cursor der Programmierer verwenden möchte. Der schnellste Typ wird in der Dokumentation als Standardresultset bezeichnet. Dieser Cursortyp wurde früher auch als Firehosecursor (Löschschlauch-Cursor) bezeichnet und wird manchmal gar nicht als Cursor betrachtet. Nachdem die SQL-Anforderung an den Server gesendet wurde, beginnt der Server, die Ergebnisse an den Client zurückzusenden, und beendet den Sendevorgang erst dann, wenn der Client das gesamte Resultset verbraucht hat. Dies kann man sich wie einen riesigen Löschschlauch vorstellen, der Daten aus dem Client pumpt.

Wenn der Client festgelegt hat, dass es sich um ein Standardresultset handelt, ist der nächste Schritt festzustellen, ob es Parametermarkierungen gibt. Eine der Möglichkeiten, die Ihnen bei der Verwendung dieses SQLExecDirect-Aufrufs in ODBC (und seines Äquivalents in OLE-DB) zur Verfügung stehen, ist, dass Sie anstatt eines spezifischen Werts (z.B. 7) in der WHERE-Klausel eine Parametermarkierung übergeben können. Dazu ersetzen Sie die Konstante wie folgt durch ein Fragezeichen:

SQLExecDirect(hstmt, "SELECT * FROM parts where partid = <span class=cfe>?</span>", SQL_NTS)

Beachten Sie, dass Sie den eigentlichen Wert des Parameters separat angeben müssen.

Der Client muss wissen, ob diese SQL-Anweisung Parametermarkierungen enthält oder es sich um eine echte nicht parametrisierte Ad-hoc-SQL-Anweisung handelt. Dies wirkt sich darauf aus, wie der Client die Anweisung intern verarbeitet und festlegt, was als Nachrichten an den SQL Server gesendet wird. Wenn die Anweisung kein Fragezeichen enthält, steht fest, dass der Client diese Anforderung einfach als SQL-Sprach-TDS-Nachrichten sendet und die Ergebnisse entgegennimmt. Der Client kann das Ergebnis dann basierend auf den Anwendungsparametern zurückgeben. Bei den internen Entscheidungen des Clients bezüglich der Verarbeitung kann es ein wenig unklar sein, was Sie über die ODBC- bzw. OLE DEB-APIs anfordern. Angenommen, eine Anwendung fordert nicht direkt ein Standardresultset an. Stattdessen wird in ODBC hinsichtlich der Clientinterna ein Firehosecursor (ein Standardresultset) definiert, wenn Sie einen schreibgeschützten Vorwärtscursor anfordern, der jeweils eine Zeile zurückgibt.

Beim Firehosecursor ist ein wesentliches Problem zu bedenken. Der Client kann keine weiteren SQL-Anweisungen an den Server senden, bevor er nicht alle Zeilen verbraucht hat. Da das Resultset sehr viele Zeilen umfassen kann, eignen sich Firehosecursor für einige Anwendungen nicht. Schnelle Vorwärtscursor, die in diesem Artikel noch beschrieben werden, sind ein neues SQL Server 7.0-Feature, das speziell für diesen Fall vorgesehen wurde.

Vor SQL Server Version 7.0 war die Verarbeitung des SQLExecDirect-Aufrufs nahezu identisch, unabhängig davon, ob die Konstante durch Parametermarkierungen ersetzt wurde. Wenn Sie eine Parametermarkierung angegeben haben, hat der Client den über einen anderen Aufruf bereitgestellten Wert verwendet (den Wert "7" im Beispiel oben), und an der Position des Fragezeichens eingefügt. Die neue Anweisung mit dem ersetzten Wert wurde als Ad-hoc-SQL-Anweisung gesendet. Die Verwendung von parametrisierten SQL-Anweisungen auf dem Server wäre dabei nicht von Vorteil gewesen.

Wenn in SQL Server 7.0 jedoch Parametermarkierungen mit SQLExecDirect verwendet werden, ist die an SQL Server gesendete TDS-Nachricht keine SQL-Sprachnachricht. Stattdessen wird sie mit Hilfe der Prozedur sp_executesql gesendet. Sofern es das TDS-Protokoll betrifft, handelt es sich dabei also um einen RPC. Auf der Clientseite ist das Ergebnis praktisch dasselbe – der Client erhält die Daten per Firehosecursor zurück.

Wenn Sie die Daten nicht per Firehosecursor erhalten möchten, können Sie jederzeit einen Block- oder bildlauffähigen Cursor verwenden. Der Datenfluss ist in diesem Fall sehr unterschiedlich. Am Einstiegspunkt sp_cursoropen erfolgt ein Aufruf (eine der pseudo-systemgespeicherten Prozeduren), der den SQL-Text weitergibt. Durch sp_cursoropen wird die SQL-Anweisung bearbeitet und zusätzliche Logik hinzugefügt, um sie bildlauffähig zu machen. Die Prozedur leitet einige Ergebnisse potentiell in eine temporäre Tabelle um und gibt eine Antwort mit einem Handle auf den Cursor zurück, der anzeigt, dass der Cursor jetzt offen ist. Ebenfalls außerhalb des Einflussbereichs der Programmierers ruft der Client sp_cursorfetch auf und ruft eine oder mehrere Zeilen ab, die an die Benutzeranwendung zurückgegeben werden. Der Client kann den Cursor auch mit Hilfe von sp_cursor erneut positionieren oder bestimmte Statistiken ändern. Wenn die Verarbeitung des Cursors abgeschlossen ist, ruft der Client sp_cursorclose auf.

Betrachten wir einmal einen einfachen Fall, in dem nur eine Zeile an den Client zurückgegeben wird. Bei Verwendung des Standardresultsets erfolgt ein Roundtrip der Nachrichten vom Client an den Server und wieder zurück. Die SQL-Nachricht (oder sp_executesql) wird an den Server gesendet, und anschließend kommen die Ergebnisse zurück. Wird für dieselbe Zeile ein Cursor (kein Firehosecursor) verwendet, erfolgt die Verarbeitung auf SQL Server auf die herkömmliche Art: ein Roundtrip zum Öffnen, ein Roundtrip zum Abrufen und ein Roundtrip zum Schließen. Bei diesem Verfahren werden dreimal so viele Nachrichten verwendet wie beim Standardresultset. In SQL Server 7.0 gibt es einen schnellen Vorwärtscursor, der dieselbe Cursorinfrastruktur verwendet. Er verhält sich anders als ein Firehosecursor, da nicht alle Ergebniszeilen vor dem Senden weiterer SQL-Nachrichten verarbeitet werden müssen. Wenn also fünf Zeilen zurückgegeben werden und noch weitere Daten vorhanden sind, können Sie trotzdem eine Aktualisierung an den Server senden.

Ein schneller Vorwärtscursor ist auf dem Server schneller als ein normaler Cursor, und Sie haben die Möglichkeit, zwei weitere Optionen festzulegen. Die eine Option wird als "Autofetch" (automatischer Abruf) und die andere als "Autoclose" (automatisches Schließen) bezeichnet. Autofetch gibt die ersten Zeilen als Teil der Antwortnachricht an den offenen Cursor zurück. Durch Autoclose wird der Cursor nach dem Lesen der letzten Zeile automatisch geschlossen. Da es ein schreibgeschützter Vorwärtscursor ist, können Sie nicht zurück scrollen. SQL Server gibt einfach eine Nachricht zurück, deren letzter Datensatz angibt, dass der Cursor geschlossen wird. Wenn Sie schnelle Vorwärtscursor verwenden, können Sie die Kommunikation bei wenig Zeilen auf nur einen Nachrichten-Roundtrip beschränken. Wenn Sie viele Zeilen haben, müssen Sie zumindest nur die zusätzlichen Kosten für die einzelnen Zeilenblöcke tragen. Bei Verwendung eines schnellen Vorwärtscursors kommt die Cursorverarbeitung der Verarbeitung mit einem Standardresultset bedeutend näher.

Abbildung 4 zeigt den Funktionsablauf des SQLExecDirect-Modells.

Bild04

Abbildung 4. Client/Server-Kommunikation

 

Das Vorbereiten/Ausführen-Modell

Neben dem Modell der direkten Ausführung (in ODBC aufgerufen über SQLExecDirect) gibt es ein weiteres in ODBC und OLE-DB offen gelegtes Ausführungsmodell, das als Vorbereiten/Ausführen-Modell bezeichnet wird. Die Definition der auszuführenden SQL-Anweisung erfolgt in einem von der tatsächlichen SQL-Ausführung getrennten Schritt. Im Folgenden finden Sie ein Beispiel für ODBC:

SQLPrepare(hstmt, "SELECT * FROM parts where partid = <span class=cfe>?</span>", SQL_NTS) 
SQLExecute(hstmt)

Vor SQL Server 7.0 war Vorbereiten/Ausführen niemals ein systemeigenes Modell für SQL Server. In Version 7.0 gibt es zwei pseudo-systemgespeicherte Prozeduren, die eine systemeigene Schnittstelle bereitstellen. Für den Vorbereitungsaufruf betrachten wir wieder, um welchen Cursortyp es sich handelt, und anschließend rufen wir sp_prepare oder sp_cursorprepare auf. Dadurch wird der Kompilierungsteil der Verarbeitung der SQL-Anweisung bzw. der gespeicherten Prozedur ausgeführt, jedoch nicht der Plan. Stattdessen gibt die pseudo-systemgespeicherte Prozedur einen Handle auf den Plan zurück. Ihre Anwendung kann die SQL-Anweisung nun wiederholt ausführen, indem sie z.B. verschiedene Parameterwerte übergibt, ohne erneut kompilieren zu müssen.

In SQL Server 6.5 mussten die zwei Phasen für Vorbereitung und Ausführung emuliert werden, da es keine systemeigene Schnittstelle gab. Dafür gab es zwei Möglichkeiten. Bei der einen Methode fand die Vorbereitungsphase nicht wirklich statt. Es gab nur eine Teilausführung, bei der die Metadaten zurückgegeben wurden (dafür wurde eine entsprechende Option eingestellt), damit SQL Server die Beschreibung des Ergebnisformats an die Anwendung zurücksenden konnte. Bei der anderen Methode erstellte SQL Server eine private temporär gespeicherte Prozedur für den jeweiligen Benutzer, so dass der Plan nicht gemeinsam genutzt wurde. Diese zweite Methode konnte dazu führen, dass die Datenbank tempdb gefüllt wurde. Daher umgingen die meisten Anwendungsentwickler die Verwendung dieser Option, indem sie das entsprechende Kontrollkästchen im Dialogfeld für die ODBC-Konfiguration deaktivierten.

In SQL Server 7.0 ist das Vorbereiten/Ausführen-Modell ein systemeigenes Feature von SQL Server. Nachdem die SQL-Anweisung vorbereitet wurde, wird sie ausgeführt. Im Falle von Standardresultsets ruft der Anwendungsprogrammierer hierfür nur sp_execute mit dem durch die Vorbereitung bereitgestellten Handle auf. Anschließend wird die Anweisung ausgeführt. Bei Verwendung von Cursorn erfolgt die Verarbeitung genau wie bei der anderen Cursorverarbeitung. Sie weist dieselben Eigenschaften auf, einschließlich der Möglichkeit, Autofetch und Autoclose zu verwenden, wenn es sich um einen schnellen Vorwärtscursor handelt.

Abbildung 5 zeigt den Ablauf der Vorbereitung/Ausführung.

Bild05

Abbildung 5. Vorbereiten/Ausführen-Modell

 

Aufrufen von gespeicherten Prozeduren

Gespeicherte Prozeduren werden i.Allg. über ODBC und OLE-DB aufgerufen, indem eine SQL-Anweisung an den SQL Server gesendet wird, der die kanonische ODBC-CALL-Syntax zum Aufrufen einer Prozedur verwendet. Dies könnte wie folgt aussehen:

SQLExecDirect(hstm, "{call addorder(?)}", SQL_NTS)

Im Falle eines Standardresultsets ist dies ein einfacher Ablauf, da hier der eigentliche Verwendungszweck von RPC-Nachrichten zur Anwendung kommt. Der Client sendet eine RPC-Nachricht an den Server und erhält die Ergebnisse der Prozedur zurück. Wenn es sich um einen Cursor handelt, ist der Vorgang etwas komplizierter. Der Client ruft wie bei allen anderen Cursorn auch sp_cursoropen auf. Die Prozedur Sp_cursoropen verfügt über eine Logik, mit der erkannt wird, ob die gespeicherte Prozedur nur eine SELECT-Anweisung enthält. Wenn dies der Fall ist, wird ein Cursor bei dieser SELECT-Anweisung geöffnet. Andernfalls erhält der Client eine Nachricht mit einem Indikator, der besagt "wir haben das für Sie geöffnet, leiten die Ergebnisse aber als Firehosecursor an Sie zurück, und Sie können die Ergebnisse dann dem Benutzer präsentieren."

Abbildung 6 zeigt den Ausführungsablauf für die Verarbeitung der gespeicherten Prozedur.

Bild06

Abbildung 6. Aufrufen von gespeicherten Prozeduren

 

SQL Manager

SQL Manager, über den wir bereits gesprochen haben, ist bei der Serververarbeitung oft die treibende Kraft. Er ist gewissermaßen das Herz des Servers. SQL Manager verarbeitet alle Anforderungen zum Ausführen gespeicherter Prozeduren. Er verwaltet den Prozedurcache, verfügt über pseudo-systemgespeicherte Prozeduren und ist an der automatischen Parametrisierung von Ad-hoc-Abfragen beteiligt, auf die wir später noch eingehen werden. Wenn Sie schon einmal einen ähnlichen Artikel zu SQL 6.5 oder früher gelesen haben, werden Sie dort keine Informationen zu SQL Manager gefunden haben. Stattdessen wurden dort andere Komponenten beschrieben, die einige der Aufgaben ausführen, für die SQL Manager nun zuständig ist. In SQL Server 7.0 wurden diese Komponenten in einer Komponente zusammengefasst, die die Abfrageverarbeitung im System steuert und ausführt.

Normalerweise wird der SQL Manager mit einer RPC-Nachricht aufgerufen, wenn Sie SQL Server auffordern, bestimmte Tasks für Sie auszuführen. Wenn jedoch über eine SQL-Nachricht eine SQL-Sprachanweisung eingeht und im Modul kompiliert wird, wird auch der SQL Manager an diesem Vorgang beteiligt. Er kann beteiligt werden, wenn eine Prozedur einen Batch oder eine EXEC-Anweisung enthält, da durch EXEC der SQL Manager aufgerufen wird. Wenn die SQL-Anweisung die Vorlage für die automatische Parametrisierung durchläuft, die wir in diesem Artikel noch behandeln werden, wird der SQL Manager zum Parametrisieren der Abfragen aufgerufen. Er wird auch aufgerufen, wenn Ad-hoc-Abfragen im Cache gespeichert werden müssen.

 

Kompilierung und Ausführung

Jetzt werden wir untersuchen, wie die Kompilierung und die Ausführung i.Allg. innerhalb von SQL Server ablaufen. Dabei ist zu bedenken, dass Kompilierung und Ausführung in SQL Server zwei unterschiedliche Phasen sind. Der Abstand zwischen der Kompilierung und der Ausführung einer Abfrage kann bei SQL Server sehr gering sein – einige Mikrosekunden oder Sekunden oder aber Minuten, Stunden und sogar Tage. Während der Kompilierung (die auch die Optimierung umfasst) müssen wir darauf achten, welche Art von Informationen wir als Teil der Kompilierung verwenden können. Nicht alle Informationen, die während der Kompilierung zutreffen, sind auch zur Ausführungszeit zutreffend. Sie müssen sich Kompilierung und Ausführung als zwei separate Aktivitäten vorstellen, auch in den Fällen, in denen Sie eine Ad-hoc-SQL-Anweisung senden und diese sofort ausführen.

Wenn eine Abfrage für die Verarbeitung durch SQL Server bereit ist, sucht der SQL Manager im Cache danach. Wenn er sie nicht findet, muss sie kompiliert werden. Der Kompilierungsvorgang besteht aus mehreren Schritten. Zunächst erfolgen Analyse und Normalisierung. Bei der Analyse wird die SQL-Anweisung in Datenstrukturen zerlegt, die vom Computer besser verarbeitet werden können. Die Analyse beinhaltet auch die Überprüfung der Syntax. Auf gültige Tabellen- und Spaltennamen wird während der Analyse nicht überprüft. Diese Aspekte werden während der Normalisierung behandelt. Die Normalisierung dient grundsätzlich dazu, die in der SQL-Anweisung enthaltenen Verweise in ihre eigentliche Eigenschaften in der Datenbank aufzulösen und sicherzustellen, dass die angeforderte Semantik sinnvoll ist. Vom Standpunkt der Semantik aus wäre es z.B. unlogisch zu versuchen, eine Tabelle auszuführen.

Der nächste Schritt ist die Kompilierung des Transact-SQL-Codes. Transact-SQL und SQL selbst werden häufig verwechselt, und auch die Entwickler bei Microsoft verwenden die beiden Begriffe synonym. Es gibt jedoch einen wichtigen Unterschied. SQL umfasst alle DML-Anweisungen: INSERT, UPDATE, DELETE und SELECT. Darüber hinaus verfügt SQL Server über eine Sprache, in der diese DML-Anweisungen eingebunden werden – Transact-SQL oder auch TSQL. TSQL bietet prozedurale Gerüste: IF-Anweisungen, WHILE-Anweisungen, lokale Variablendeklarationen usw. Diese werden innerhalb des Servers sehr unterschiedlich behandelt. Die prozedurale Logik von TSQL wird von einem Modul kompiliert, das weiß, wie prozedurale Tasks verarbeitet werden.

Die SQL-Anweisungen selbst werden vom klassischen Abfrageoptimierer kompiliert. Der Optimierer übersetzt die nicht prozedurale Anforderung einer satzbasierten SQL-Anweisung in eine Prozedur, die effizient ausgeführt werden kann und die gewünschten Ergebnisse zurückgibt. Wenn wir in diesem Artikel von Kompilierung sprechen, meinen wir von jetzt an sowohl die Kompilierung von TSQL als auch die Optimierung der SQL-Anweisungen.

Wie bereits erwähnt, sind Kompilierung und Ausführung zwei verschiedene Phasen der Abfrageverarbeitung. Eine Aufgabe des Optimierers ist es daher, basierend auf einem relativ stabilen Status zu optimieren. Sie werden u.U. feststellen, dass SQL Server eine Anweisung unter bestimmten Bedingungen erneut kompilieren kann. Der Status ist also nicht permanent stabil, darf sich aber auch nicht laufend verändern. Wenn der Optimierer Informationen verwendet, die sich sehr stark und sehr häufig ändern – die Anzahl gleichzeitiger Prozesse oder die Anzahl aktiver Sperren – müssten die Abfragen ständig neu kompiliert werden, und die Kompilierung neigt von Natur aus dazu, eher langsam zu sein. Sie können z.B. eine SQL-Anweisung haben, die in einer Hundertstelsekunde ausgeführt wird, deren Kompilierung jedoch eine halbe Sekunde dauert. Es wäre daher vorteilhaft, wenn SQL Server die Anweisung einmal kompilieren würde und sie anschließend so oft wie gewünscht ausgeführt werden könnte, ohne bei jeder Anweisung erneut die Kompilierungskosten zahlen zu müssen.

Das Endprodukt der Kompilierungsphase ist ein Abfrageplan, der im Prozedurcache gespeichert wird. Einfache bzw. preiswerte Ad-hoc-SQL-Pläne werden nicht wirklich im Prozedurcache gespeichert, aber dies ist für uns im Moment nicht von Bedeutung. Der Cache soll nicht mit Objekten überfüllt werden, die wahrscheinlich nicht wieder verwendet werden, und bei Ad-hoc-SQL-Anweisungen ist es sehr unwahrscheinlich, dass ihr Plan noch einmal verwendet wird. Wenn die Kompilierung der Anweisung bereits so preisgünstig ist (weniger als eine Hundertstelsekunde), gibt es keinen Grund, den Plan im Cache zu speichern und diesen mit Plänen zu überfüllen, die vermutlich nie wieder benötigt werden.

Nachdem der Plan im Cache gespeichert wurde, kehrt der SQL Manager wieder zur Logik zurück, indem er sie ausführt und überprüft, ob sich etwas geändert hat und der Plan erneut kompiliert werden muss. Obwohl nur Mikrosekunden zwischen der Kompilierung und der Ausführung liegen, kann es sein, dass eine DDL-Anweisung (Datenbeschreibungs-/Datendefinitionssprache) ausgeführt wurde, durch die einer wichtigen Tabelle ein Index hinzugefügt wurde. Das ist zwar eher unwahrscheinlich, aber dennoch möglich, und SQL Server muss diese Möglichkeit berücksichtigen. Es gibt einige Gründe, die SQL Server zur erneuten Kompilierung eines gespeicherten Plans veranlassen können. Änderungen der Metadaten, wie das Hinzufügen oder Entfernen von Indizes, sind der wahrscheinlichste Grund. Der Server muss sicherstellen, dass der verwendete Plan den aktuellen Status der Indizes widerspiegelt.

Änderungen an den Statistiken sind ein weiterer Grund für eine erneute Kompilierung. SQL Server unterhält einige Histogramminformationen zu den Daten, die er verarbeitet. Wenn sich die Datenverteilung erheblich verändert, ist möglicherweise ein anderer Abfrageplan erforderlich, um eine effiziente Ausführung zu gewährleisten. SQL Server protokolliert, wie oft Daten in eine Tabelle eingefügt oder gelöscht werden. Wenn die Anzahl von Änderungen ein bestimmtes, gemäß der Tabellengröße definiertes Limit überschreitet, werden die Pläne basierend auf den neuen Verteilungsinformationen erneut kompiliert.

Abbildung 7 zeigt den Kompilierungs- und Ausführungsablauf.

Bild07

Abbildung 7. Kompilierung und Ausführung

Bedenken Sie stets, dass eine Änderung der tatsächlichen Parameterwerte nicht zu einer erneuten Kompilierung eines Plans führt. Dasselbe gilt für Änderungen der Umgebung, z.B. dem verfügbaren Speicherplatz oder der bereits im Cache gespeicherten Menge erforderlicher Daten.

Die Ausführung ist unkompliziert, und wenn Sie nur sehr einfache Abfragen – z.B. "eine Zeile einfügen" oder "eine einzige Auswahl in einer Tabelle mit eindeutigem Index treffen" – hätten, wäre die Verarbeitung sehr einfach. Viele Abfragen erfordern für eine effiziente Ausführung jedoch viel Speicherplatz, oder ihre Ausführung würde durch diesen Speicherplatz zumindest begünstigt werden. In SQL Server 6.5 wurde der für eine Abfrage verfügbare Speicher auf ein halbes MB oder 1 MB beschränkt. Über einen Konfigurationsparameter (sort pages) wurde der von einer Abfrage verwendete Speicherplatz gesteuert. Der Name impliziert, dass im Prinzip nur Sortiervorgänge davon profitieren konnten, wenn mehr Speicherplatz zur Verfügung stand. Unabhängig davon, wie umfassend ein Sortiervorgang war, konnte in SQL Server 6.5 nicht mehr als 1 MB für einen Vorgang verwendet werden. Dies war auch dann der Fall, wenn Sie auf einem Computer mit 1 oder 2 GB Speicherplatz arbeiteten und Millionen von Zeilen sortieren mussten. Es steht außer Frage, dass dies für komplexe Abfragen nicht effizient ist. Daher erweiterten die SQL Server-Entwickler SQL Server 7.0 um die Möglichkeit, für einzelne Abfragen großen Mengen Speicherplatz zu verwenden.

Dadurch entstand ein weiteres Problem. Wenn die Verwendung größerer Speichermengen ermöglicht wird, muss dieser Speicherplatz zwischen den vielen Abfragen verteilt werden, die ihn benötigen könnten. SQL Server behandelt dieses Problem wie folgt: Wenn ein Abfrageplan optimiert wird, definiert der Optimierer zwei Informationen zur Speicherverwendung für diese Abfrage. Zunächst legt er den minimalen Speicherplatz fest, der für die effiziente Ausführung der Abfrage erforderlich ist. Dieser Wert wird zusammen mit dem Abfrageplan gespeichert. Außerdem bestimmt der Optimierer den maximalen Speicherplatz, der für die Abfrage von Nutzen ist. Es wäre z.B. nicht sinnvoll, einem Sortiervorgang 2 GB Speicherplatz zuzuweisen, wenn die gesamte zu sortierende Tabelle nicht größer als 100 MB ist. Sie benötigen nur diese 100 MB. Dies wäre also der Wert für den maximal nutzbaren Speicherplatz, der mit jedem Abfrageplan gespeichert wird.

Wenn SQL Server mit der Ausführung eines Planes beginnt, wird der Plan an eine Routine übergeben, die als Speicherzuteilungsplaner bezeichnet wird. Der Zuteilungsplaner führt einige interessante Funktionen aus. Zunächst überprüft er, ob der Plan einen Sortier- oder Hashvorgang beinhaltet, damit SQL Server weiß, ob die Abfrage viel Speicherplatz erfordert. Wenn dies nicht der Fall ist, gibt es im Speicherzuteilungsplaner keine Verzögerung. Der Plan kann sofort ausgeführt werden. Bei einer normalen Transaktionsverarbeitung wird dieser Mechanismus also vollständig umgangen. Der Speicherzuteilungsplaner verfügt für die Verarbeitung von Anforderungen unterschiedlicher Größe über mehrere Warteschlangen. Der Speicherplaner gibt in der Regel kleineren Anforderungen Priorität. Wenn Sie z.B. eine Abfrage haben, die "oberste 10 auswählen" lautet und nur 20 Zeilen sortiert werden müssen, muss diese den Speicherplaner zwar durchlaufen, die Freigabe und die Planung sollten jedoch recht schnell erfolgen. Der Server versucht, viele solcher Abfragen parallel bzw. gleichzeitig auszuführen.

Wenn Sie sehr umfangreiche Abfragen haben, ist es empfehlenswert, nur wenige davon gleichzeitig auszuführen und ihnen möglichst viel Speicherplatz zuzuweisen. SQL Server definiert einen Wert, der als 4 x (Anzahl von CPUs auf dem System) berechnet wird. Wenn möglich, führt SQL Server diese Anzahl von Abfragen gleichzeitig mit dem minimal erforderlichen Speicherplatz aus. SQL Server versucht, den für Abfragen im System verfügbaren Speicherplatz zu maximieren und gleichzeitig so viele Abfragen wie möglich im System auszuführen.

Die Möglichkeit, den maximal effektiven Speicher zu nutzen, ist bei Vorgängen wie nächtlichen Batchzyklen von großem Vorteil. Sie können so sehr große Berichte generieren oder Indizes neu erstellen. Diese Art von Abfragen können viel Speicherplatz verwenden, und der Mechanismus passt sich den jeweiligen Anforderungen dynamisch an. Wenn nicht viele Abfragen in den Warteschlangen sind, teilt der Speicherzuteilungsplaner den Abfragen häufig den maximalen angeforderten Speicherplatz zu. Fallen tagsüber keine umfangreichen Verarbeitungen an, können viele Abfragen gleichzeitig ausgeführt werden. Diesen Abfragen wird der minimale Speicherplatz zugeteilt, so dass sie effizient ausgeführt werden können, der Speicher aber als gemeinsame Ressource genutzt wird.

Wenn der Planer die Speicherreservierung für eine Anforderung freigibt, wird der Plan "geöffnet" und dadurch die eigentliche Ausführung gestartet. Wenn die Abfrage das Standardresultset-Modell verwendet, wird der Plan ausgeführt, bis er alle Zeilen produziert hat und diese an den Client zurückgesendet wurden. Bei Verwendung eines Cursormodells weicht die Verarbeitung von diesem Schema ab. Jede Clientanforderung gilt nur für einen Zeilenblock, nicht für alle Zeilen. Nachdem die einzelnen Resultsets an den Client zurückgesendet wurden, muss SQL Server warten, bis der Client den nächsten Resultset anfordert. Während des Wartens ruht der gesamte Plan. Das bedeutet, dass einige der Sperren aufgehoben, einige Ressourcen freigegeben und einige Positionsinformationen beseitigt werden. Anhand dieser Informationen kann SQL Server wieder an seine vorherige Position zurückkehren, wenn die nächsten Zeilen angefordert werden. Die Ausführung kann dann fortgesetzt werden.

 

Prozedurcache

Der Prozedurcache von SQL Server wurde in diesem Artikel bereits mehrfach erwähnt. Es ist wichtig zu verstehen, dass sich der Prozedurcache in SQL Server 7.0 sehr von denen der Vorgängerversionen unterscheidet. In früheren Versionen gab es zwei effektive Konfigurationswerte zum Steuern der Größe des Prozedurcaches: Ein Wert legte eine feste Größe für den gesamten verwendbaren Speicherplatz für SQL Server fest, und der andere war ein Prozentsatz dieses Speichers (nach Abzug des Speicherplatzes für feste Erfordernisse), der ausschließlich zum Speichern von Abfrageplänen verwendet wurde. In den Vorgängerversionen wurden die Abfragepläne für Ad-hoc-SQL-Anweisungen ebenfalls nicht zwischengespeichert, sondern nur die Pläne für gespeicherte Prozeduren. In SQL Server 7.0 ist die Gesamtgröße des Speichers standardmäßig dynamisch, und der für Abfragepläne verwendete Speicherplatz ist ebenfalls sehr flexibel.

Eine der ersten Fragen, die SQL Server 7.0 bei der Verarbeitung einer Abfrage stellt, ist: Ist es eine Ad-hoc-Anweisung und ist die Kompilierung kostengünstig? Wenn dies der Fall ist, speichert SQL Server gar nicht zwischen. Es ist günstiger, diese Pläne später erneut zu kompilieren, als teurere Pläne oder Datenseiten aus dem Speicher zu löschen. Wenn die Abfrage weder ad hoc noch kostengünstig zu kompilieren ist, reserviert SQL Server Speicherplatz im Puffercache und speichert den Plan darin. Der Speicherplatz stammt aus dem Puffercache, weil dies die einzige Speicherquelle ist, die für 99 % der Serveranforderungen in SQL Server 7.0 zur Verfügung steht. In einigen wenigen Sonderfällen reserviert SQL Server große Speicherbereiche direkt im Betriebssystem. Diese Fälle sind jedoch extrem selten. Für alle anderen Anforderungen wurde die Verwaltung zentralisiert.

Pläne werden zusammen mit einem Kostenfaktor zwischengespeichert, der die tatsächlichen Kosten widerspiegelt, die beim Erstellen des Plans durch Kompilieren der Abfrage anfallen. Wenn es sich um einen Ad-hoc-Plan handelt, setzt SQL Server den Kostenfaktor auf Null, d.h., der Plan kann sofort aus dem Prozedurcache entfernt werden. Bei Ad-hoc-SQL-Anweisungen ist die Wahrscheinlichkeit, dass sie wieder verwendet werden, sehr gering. Dennoch muss diese Möglichkeit berücksichtigt werden. Wenn der Speicherplatz im System knapp wird, sollten die Pläne für Ad-hoc-Abfragen als Erstes gelöscht werden. Daher kommen die Pläne für Ad-hoc-Abfragen von Anfang an dafür in Frage, aus dem Cache entfernt zu werden. Wenn es sich bei der Abfrage nicht um eine Ad-hoc-Abfrage handelt, setzt SQL Server den Kostenfaktor auf die Kosten, die beim Kompilieren der Abfrage entstehen. Diese Kosten sind Einheiten des Datenträger-E/A-Vorgangs. Wenn eine Datenseite vom Datenträger gelesen wird, betragen die Kosten einen E/A. Beim Erstellen des Plans wurden Informationen vom Datenträger gelesen, einschließlich Statistiken und dem Text der Abfrage selbst. SQL hat zusätzliche Verarbeitungsschritte ausgeführt, und diese Verarbeitungsleistung wird auf die Kosten eines E/A-Vorgangs normalisiert. Jetzt können die Kosten für das Erstellen einer Prozedur gegen die Kosten für einen E/A abgewägt werden. Die Kosten spielen bei der Cacheverwaltung eine wichtige Rolle – anhand des Kostenfaktors kann besser verwaltet werden, wieviel Cache tatsächlich für gespeicherte Prozeduren oder Abfragepläne und wieviel für den Datenträgercache verwendet wird. Nachdem die Kosten berechnet wurden, wird der Plan im Cache gespeichert.

Abbildung 8 zeigt den Ablauf zum Berechnen des Kostenfaktors und Zwischenspeichern eines Plans.

Bild08

Abbildung 8. Einfügen eines Plans in den Cache

Wenn eine andere Abfrage diesen Plan wieder verwenden kann, überprüft SQL Server erneut, um welche Art von Plan es sich handelt. Wenn es ein Ad-hoc-Plan ist, inkrementiert SQL Server die Kosten um 1. Wenn Ad-hoc-Pläne also tatsächlich wieder verwendet werden, bleiben sie, da der Kostenfaktor zunimmt, etwas länger im Cache. Wird der Plan häufig wieder verwendet, werden die Kosten bei jedem Einsatz um eine Einheit erhöht, bis sie den Erstellungskosten entsprechen. d.h., so hoch sind, wie die Kosten eingestellt werden. Wenn der Plan jedoch häufig verwendet wird, wenn derselbe oder ein anderer Benutzer genau denselben SQL-Text immer wieder verwendet, bleibt der Plan im Cache.

Wenn es sich nicht um eine Ad-hoc-Abfrage handelt, sondern um eine gespeicherte Prozedur, eine parametrisierte Abfrage oder eine automatisch parametrisierte Abfrage, werden die Kosten bei jeder Verwendung des Plans wieder auf die Erstellungskosten zurückgesetzt. Solange der Plan verwendet wird, verbleibt er im Cache. Auch wenn er über einen längeren Zeitraum nicht verwendet wird, kann er relativ lange im Cache bleiben. Dies hängt jedoch davon ab, wie teuer die ursprüngliche Erstellung des Plans war.

Abbildung 9 zeigt den Ablauf zum Abrufen eines Plans aus dem Cache und Einstellen des Kostenfaktors.

Bild09

Abbildung 9. Abrufen eines Plans aus dem Cache

Der Lazy Writer ist der Mechanismus, der das Alter der Pläne erhöht und dafür zuständig ist, Pläne bei Bedarf aus dem Cache zu entfernen. Eigentlich ist der Lazy Writer Teil des Speichermoduls. Da er aber so wichtig für die Abfrageverarbeitung ist, werden wir ihn hier behandeln. Da in SQL Server 7.0 Pläne im normalen Puffercache gespeichert werden, verwendet der Lazy Writer zum Verwalten des von den Abfrageplänen verwendeten Speichers denselben Mechanismus wie zum Verwalten von Seiten. Er durchsucht alle Pufferheader im System. Wenn gerade nur wenig Speicherplatz des Systems benötigt wird, sucht er sehr langsam. Mit zunehmendem Speicherbedarf beginnt der Lazy Writer, häufiger zu suchen. Während der Ausführung überprüft der Lazy Writer einen Pufferheader und die aktuellen Kosten für die Seite in diesem Puffer. Wenn die Kosten Null sind, bedeutet dies, dass die Seite seit dem letzten Suchvorgang des Lazy Writer nicht geändert wurde. Der Lazy Writer gibt diese Seite frei, um Speicherplatz für Seiten-E/A-Vorgänge oder andere Pläne im System freizugeben. Wenn der Puffer einen Prozedurplan enthält, ruft der Lazy Writer außerdem den SQL Manager auf, der dann eine Bereinigung durchführt. Zum Schluss wird der Puffer zur Wiederverwendung auf die freie Liste gesetzt.

Wenn die mit einem Puffer verknüpften Kosten nicht Null sind, verringert der Lazy Writer die Kosten und fährt dann mit der Überprüfung anderer Puffer fort. Die Kosten geben dann an, während wie vieler Zyklen des Lazy Writer ein Objekt ohne Wiederverwendung im Cache verbleibt, bevor es entfernt wird. Dieser Algorithmus unterscheidet nicht zwischen Plänen im Cache und Daten- oder Indexseiten im Cache. Eine Ausnahme ist das Aufrufen des SQL Manager, wenn es sich beim Objekt um eine gespeicherte Prozedur handelt. Der Lazy Writer weiß eigentlich nicht, was es bedeutet, wenn ein Objekt eine Prozedur ist, und der Algorithmus wägt auf geeignete Weise zwischen der Verwendung von Cache für einen Datenträger-E/A-Vorgang und der Verwendung von Cache für einen Prozedurplan ab.

Sie werden feststellen, dass Objekte mit hohen Kompilierungskosten u.U. sehr lange im Cache bleiben, ohne wieder verwendet zu werden. Dies ist darauf zurückzuführen, dass die ursprünglichen Kosten so hoch waren. Alle Objekte, die häufig wieder verwendet werden, bleiben lange im Cache, da die Kosten bei jeder Verwendung zurückgesetzt werden und der Lazy Writer niemals einen Kostenfaktor von Null feststellen wird.

Abbildung 10 zeigt den Ablauf der Cacheverarbeitung durch den Lazy Writer.

Bild10

Abbildung 10. Ablauf der Cacheverarbeitung durch den Lazy Writer

 

Verarbeitung der SQL-Anforderung des Clients

Jetzt werden wir noch einmal untersuchen, welche Verarbeitungsschritte stattfinden, wenn eine SQL-Anweisung abgesendet wird. Zunächst betrachten wir den Fall, in dem ein RPC-Ereignis vom Client an den Server gesendet wird. Da SQL Server ein RPC-Ereignis empfängt, weiß er, dass es sich bei dem Ereignis um eine Art parametrisierter SQL handelt – entweder um das Vorbereiten/Ausführen-Modell oder EXECUTESQL. SQL Server muss einen Cache-Schlüssel erstellen, um diesen bestimmten SQL Server-Text zu identifizieren. Wenn SQL Server eine gespeicherte Prozedur verarbeitet, muss er nicht selbst einen Schlüssel erstellen, sondern kann einfach den Namen der Prozedur verwenden. Wenn einfacher SQL-Text über einen RPC-Aufruf eingeht, wird der Cache-Schlüssel durch Hashing des SQL-Textes erstellt. Außerdem muss der Schlüssel bestimmte Statusinformationen widerspiegeln, z.B. einige der ANSI-Einstellungen. Eine Verbindung, für die alle ANSI-Einstellungen auf ON gesetzt sind, kann nicht denselben Plan verwenden wie eine Verbindung, deren ANSI-Einstellungen auf OFF gesetzt sind. Dies trifft auch dann zu, wenn sie dieselbe Abfrage übergeben – die Verarbeitung ist unterschiedlich. Wenn bei einer Verbindung z.B. der Wert von concat_null_yields_null auf ON gesetzt ist, kann sie auch dann zu vollkommen anderen Ergebnissen führen als eine Verbindung, bei der concat_null_yields_null auf OFF eingestellt ist, wenn sie exakt denselben SQL-Text ausführt. Daher muss SQL Server u.U. mehrere Kopien des Plans im Cache verwalten, eine für jede Kombination von ANSI-Einstellungen. Die aktivierten Einstellungen sind Teil eines Schlüssels, der das Kernstück bei der Suche nach Objekten mit Hilfe dieses Cachemechanismus ist. Aus diesem Grund erstellt SQL Server diesen Schlüssel und verwendet ihn zum Durchsuchen des Cache. Wenn der Plan nicht im Cache gefunden wird, kompiliert SQL Server den Plan, wie im Folgenden beschrieben, und speichert ihn mit dem Schlüssel im Cache.

SQL Server muss auch überprüfen, ob der Befehl eine Vorbereitungsoperation ist, da der Plan in diesem Fall nur kompiliert und nicht ausgeführt werden muss. Wenn es sich um eine Vorbereitungsoperation handelt, gibt SQL Server einen Handle an den Client zurück, den dieser später zum Abrufen und Ausführen des Plans verwendet. Ist es keine Vorbereitungsoperation, führt SQL Server den Plan so aus, als hätte er ihn bei der ursprünglichen Suche im Cache gefunden.

Das Vorbereiten/Ausführen-Modell kompliziert die Cacheverwaltung. Bei der Vorbereitung wird ein Handle ausgegeben, das später zum Ausführen des Plans verwendet werden kann. Die Anwendung kann dieses Handle stunden- oder tagelang aktiv halten und den Plan regelmäßig ausführen. Es ist nicht möglich, das Handle ungültig zu machen, wenn im Cache Platz für einen aktiveren Plan oder Datenseiten gemacht werden muss. Daher speichert SQL Server den Plan im Cache und speichert zusätzlich die kompaktere SQL-Anweisung aus der Vorbereitungsphase. Wenn jetzt der verfügbare Speicherplatz knapp wird, kann der Plan wie zuvor beschrieben freigegeben und gleichzeitig eine Kopie der Vorbereitungs-SQL aufbewahrt werden. Wenn der Client die vorbereitete SQL ausführen möchte und der Plan wird nicht im Cache gefunden, kann SQL Server den Text abrufen, erneut kompilieren und dann wieder im Cache speichern. Auf diese Weise können die 16 KB Seiten für den Plan wieder verwendet werden, und der langfristig an anderer Stelle belegte Speicherplatz für den SQL-Code beträgt vielleicht 100 oder 200 Bytes.

Ein anderes Szenario der Verarbeitung von Clientanweisungen liegt vor, wenn die Abfrage als SQL-Sprachereignis eingeht. Der Ablauf unterscheidet sich nur in einem Punkt: Hier versucht SQL Server eine als automatische Parametrisierung bezeichnete Technik anzuwenden. Der SQL-Text wird mit einer Vorlage für die automatische Parametrisierung abgestimmt. Die automatische Parametrisierung ist sehr kompliziert. Daher stand diese Option früher in den meisten Datenbank-Managementprodukten nicht zur Verfügung, die von den Vorteilen der gemeinsam genutzten SQL profitieren konnten. Das Problem dabei ist, dass einige (oder sogar die meisten) Abfragen sehr mangelhafte Pläne für einige der anschließend gesendeten spezifischen Werte erhalten, wenn SQL Server jede Abfrage automatisch parametrisiert. Wenn der Programmierer eine Parametermarkierung in den Code einfügt, wird vorausgesetzt, dass er den Bereich der möglichen Werte kennt und bereit ist, den von SQL Server erstellten Plan zu akzeptieren. Wenn der Programmierer jedoch einen spezifischen Wert übergibt und SQL Server entscheidet, diesen Wert als austauschbaren Parameter zu behandeln, besteht die Möglichkeit, dass der erstellte Plan zwar für einen, aber nicht für die nachfolgenden Werte funktioniert. Bei gespeicherten Prozeduren kann der Programmierer die Erstellung neuer Pläne erzwingen, indem er die Option WITH RECOMPILE in die Prozedur integriert. Bei der automatischen Parametrisierung hat der Programmierer diese Möglichkeit nicht.

SQL Server ist bezüglich der automatischen Parametrisierung sehr konservativ. Es gibt eine Vorlage von Abfragen, für die die automatische Parametrisierung sicher ist, und nur Abfragen, die mit dieser Vorlage übereinstimmen, werden automatisch parametrisiert. Angenommen, wir haben eine Abfrage, die eine WHERE-Klausel mit Gleichheitsoperator, keine Verknüpfungen und einen eindeutigen Index für die Spalte in der WHERE-Klausel enthält. SQL Server weiß, dass immer nur eine Zeile zurückgegeben wird und der Plan immer diesen eindeutigen Index verwenden soll. SQL Server zieht in diesem Fall einen Scanvorgang niemals in Betracht, und der Plan wird nie durch den jeweiligen Wert geändert. Für diesen Abfragetyp kann die automatische Parametrisierung sicher angewendet werden.

Wenn die Abfrage mit der Vorlage für die automatische Parametrisierung übereinstimmt, ersetzt SQL Server die Literale durch Parametermarkierungen (z.B. @p1, @p2) und sendet diese Abfrage wie einen sp_executesql-Aufruf an den Server. Wenn SQL Server entschieden hat, dass die automatische Parametrisierung für die Abfrage nicht sicher ist, sendet der Client den literalen SQL-Text als Ad-hoc-SQL-Anforderung an SQL Server.

Abbildung 11 zeigt den Verarbeitungsablauf, wenn ein Client eine Anforderung an SQL Server sendet.

Bild11

Abbildung 11. Verarbeitung der SQL-Anforderung des Clients

 

Kompilierung

Im Folgenden werden wir uns etwas ausführlicher mit der Kompilierung und Optimierung befassen. Während der Kompilierung analysiert SQL Server die Anweisung und erstellt eine interne Darstellung dieser Anweisung, die auch als Sequenzstruktur bezeichnet wird. Dies ist eine der wenigen Datenstrukturen, die in SQL Server 7.0 von SQL Server 6.5 übernommen wurde. Die Sequenzstruktur wird dann normalisiert. Die Hauptfunktion der Normalisierungsroutine ist die Bindung. Bei der Bindung wird überprüft, ob die Tabellen und Spalten vorhanden sind, und die Metadaten zu den Tabellen und Spalten werden geladen. Außerdem werden der Sequenzstruktur Informationen zu erforderlichen (impliziten) Konvertierungen hinzugefügt, z.B., ob die Abfrage versucht, die Ganzzahl 10 zu einem numerischen Wert zu addieren. SQL Server fügt dann eine implizite Konvertierung in die Struktur ein. Bei der Normalisierung werden außerdem Verweise auf eine Ansicht durch die Definition dieser Ansicht ersetzt. Abschließend führt die Normalisierung einige syntaxbasierte Optimierungen durch. Wenn die Anweisung eine klassische SQL-Anweisung ist, extrahiert SQL Server Informationen über diese Abfrage aus der Sequenzstruktur und erstellt eine spezielle Struktur. Diese wird als Abfragegraph bezeichnet und ist so eingerichtet, dass der Optimierer sie sehr effektiv bearbeiten kann. Der Abfragegraph wird anschließend optimiert, und ein Plan wird erstellt.

Abbildung 12 zeigt den Ablauf des Kompilierungsprozesses.

Bild12

Abbildung 12. Kompilierung

 

Optimierung

Der SQL Server-Optimierer besteht aus separaten Teilen. Der erste Teil ist ein nicht kostenbasierter Optimierer, der als Trivialplanoptimierung bezeichnet wird. Der Grundgedanke der Trivialplanoptimierung ist, dass die kostenbasierte Optimierung sehr kostspielig ist, wenn es für die SQL-Anweisung nur einen brauchbaren Plan gibt. Ein gutes Beispiel hierfür ist eine Abfrage, die aus einer INSERT-Anweisung mit einer VALUES-Klausel besteht. Es gibt nur einen möglichen Plan. Ein weiteres Beispiel ist eine SELECT-Anweisung, bei der alle Spalten in einem eindeutigen Index sind und es keinen anderen Index gibt, der diese Spalten enthält. Diese beiden Beispiele stellen Fälle dar, in denen SQL Server einfach den Plan erstellt und nicht versucht, mehrere Pläne zu untersuchen, um die optimale Lösung zu finden. Der Trivialplanoptimierer findet die ganz offensichtlichen Pläne, die in der Regel sehr kostengünstig sind. Die einfachsten Abfragen werden also in einem frühen Stadium des Prozesses ausgesondert, und der Optimierer verbringt nicht viel Zeit damit, nach einem geeigneten Plan zu suchen. Dies ist ein Vorteil, da die Anzahl potentieller Pläne in SQL Server 7.0 astronomische Ausmaße angenommen hat, als die Liste der Verarbeitungstechniken von SQL Server um Hashverknüpfungen, Zusammenführungsverknüpfungen und Indexschnittmengen erweitert wurde.

Wenn der Trivialplanoptimierer keinen Plan findet, fährt SQL Server mit dem nächsten Teil der Optimierung fort, die als Vereinfachung bezeichnet wird. Vereinfachungen sind syntaktische Transformationen der Abfrage selbst, bei denen nach kommutativen Eigenschaften und Operationen gesucht wird, die erneut angeordnet werden können. SQL Server kann Operationen ausführen, bei denen keine Kosten überprüft oder Indizes analysiert werden müssen, die jedoch zu einer effizienteren Abfrage führen können, z.B. eine Konstantenfaltung. SQL Server lädt dann die statistischen Informationen zu Indizes und Spalten und beginnt mit dem letzten Teil der Optimierung, dem kostenbasierten Optimierer.

Die kostenbasierte Optimierung besteht aus drei Phasen. Die erste der kostenbasierten Phasen, die als Transaktionsverarbeitungsphase bezeichnet wird, sucht nach Plänen für einfache Abfragen, die für Transaktionsverarbeitungssysteme typisch sind. Diese Anforderungen sind in der Regel komplexer als diejenigen, die vom Trivialplanoptimierer bearbeitet werden. Sie erfordern, dass Pläne verglichen werden, um den kostengünstigsten Plan zu finden. Wenn die Transaktionsverarbeitungsphase abgeschlossen ist, vergleicht SQL Server die Kosten des preiswertesten gefundenen Plan mit einem internen Limit. Das Limit wird verwendet, um festzustellen, ob sich eine weitere Optimierung lohnt. Wenn die Kosten des Plans geringer sind als das Limit, wäre eine weitere Optimierung kostspieliger als die Ausführung des bereits gefundenen Plans. Daher hält SQL Server die weiteren Optimierungen an und verwendet den Plan, der während der Transaktionsverarbeitungsphase gefunden wurde.

Wenn der bei der Transaktionsverarbeitungsphase gefundene Plan im Vergleich mit dem Limit dieser Phase teuer ist, fährt SQL Server mit einer zweiten Phase fort. Diese Phase wird auch als QuickPlan-Phase bezeichnet. Die QuickPlan-Phase dehnt die Suche nach einem guten Plan auf Optionen aus, die sich für wenig komplexe Abfragen eignen. QuickPlan untersucht eine Reihe möglicher Pläne und vergleicht dann die Kosten des besten Plans mit einem zweiten Limit. Wie auch bei der Transaktionsverarbeitungsphase wird die weitere Optimierung angehalten, wenn ein Plan gefunden wurde, dessen Kosten unterhalb des Limits liegen. Normalerweise wird ein Plan, der in SQL Server 6.5 für eine Abfrage verwendet wurde und auch in SQL Server 7.0 der optimale Plan ist, entweder vom Trivialplanoptimierer oder einer der beiden ersten Phasen der kostenbasierten Optimierung gefunden. Die Regeln wurden hierfür entsprechend festgelegt. Der Plan besteht möglicherweise aus der Verwendung eines Index und der Verwendung einer geschachtelten Schleifenverknüpfung.

Die letzte, als vollständige Optimierung bezeichnete Phase der Optimierung dient dazu, einen guten Plan für komplexe bis sehr komplexe Abfragen zu erstellen. Bei komplexen Abfragen wird der durch QuickPlan erzeugte Plan oft als sehr viel teurer eingestuft als die weitere Suche nach einem besseren Plan. Wenn dies der Fall ist, wird die vollständige Optimierung ausgeführt. Bei der vollständigen Optimierung stehen zwei separate Optionen zur Verfügung. Wenn die Kosten für den von der QuickPlan-Phase ermittelten Plan höher sind als der Konfigurationswert für "Kostenlimit für Parallelität" und es sich beim Server um einen Multiprozessorcomputer handelt, wird in der letzten Phase des Optimierers nach einem Plan gesucht, der parallel über mehrere Prozessoren ausgeführt werden kann. Wenn die Kosten des Plans aus der QuickPlan-Phase geringer sind als das konfigurierte "Kostenlimit für Parallelität", zieht der Optimierer nur einen seriellen Plan in Betracht. Bei der vollständigen Optimierung können sehr zahlreiche Möglichkeiten zur Anwendung kommen, was u.U. sehr zeitraubend ist, da in dieser letzten Phase ein Plan gefunden werden muss. Der Optimierer überprüft möglicherweise noch immer nicht alle denkbaren Pläne, da er die Kosten jedes potentiellen Plans mit den Kosten vergleicht, die bis zu diesem Punkt des Optimierungsprozesses bereits entstanden sind, und abschätzt, was weitere Optimierungen kosten würden. Wenn die zusätzlichen Kompilierungskosten einer weiteren Optimierung nicht kosteneffektiv sind, kann der Optimierer jederzeit entscheiden, dass es preiswerter ist, einen aktuellen Plan zu verwenden, als weiter nach einem besseren Plan zu suchen. Die Abfragetypen, die in dieser letzten Phase verarbeitet werden, sind normalerweise Abfragen, deren Pläne nur einmal verwendet werden, und bei denen die Wahrscheinlichkeit sehr gering ist, dass sich die zusätzlichen Kosten für Kompilierung und Optimierung bei nachfolgenden Auführungen bezahlt machen. Diese nachfolgenden Ausführungen werden vermutlich niemals stattfinden.

Wenn ein Plan gefunden wird, wird er vom Optimierer ausgegeben. Anschließend durchläuft SQL Server die ganzen bereits erwähnten Cachemechanismen, bevor er den Plan ausführt. Dabei ist zu beachten, dass parallele Pläne, die während der Optimierungsphase erstellt wurden, nicht unbedingt auf mehreren Prozessoren ausgeführt werden. Wenn der Computer sehr ausgelastet ist und die Ausführung einer einzigen Abfrage auf mehreren CPUs nicht unterstützen kann, wird der Plan mit nur einem Prozessor ausgeführt.

Abbildung 13 zeigt den Verarbeitungsablauf des Optimierers.

Bild13

Abbildung 13. Optimierung

 

Ausführung

Der letzte Schritt der Abfrageverarbeitung ist die Ausführung. Diesem Thema werden wir hier nur diesen kurzen Absatz widmen: Das Ausführungsmodul nimmt den vom Optimierer erstellten Plan und führt ihn aus. Bei der eigentlichen Ausführung plant das Ausführungsmodul außerdem die Threads, auf denen die Prozesse ausgeführt werden sollen, und gewährleistet die Kommunikation zwischen den Threads.

 

Zusammenfassung

Wie bereits erwähnt, sind die Interna und die Architektur von SQL Server ein sehr umfassendes Thema, und dieser Artikel beschreibt nicht annähernd alle Aspekte, die in diesem Zusammenhang von Interesse sind. Wir haben uns hier vielmehr darauf konzentriert, wie SQL Server mit dem Client kommuniziert und wie das relationale Modul von SQL Server dann die Anforderungen vom Client verarbeitet. Wir hoffen, dass Ihnen diese Informationen – wie SQL Server Ihre Abfragen verarbeitet und wie und wann diese kompiliert oder erneut kompiliert werden – dabei helfen, bessere Anwendungen zu erstellen sowie die Leistung und die fortschrittlichen Techniken von SQL Server 7.0 optimal zu nutzen.