Verarbeiten von SQL-Anweisungen

Die Verarbeitung einer einzelnen SQL-Anweisung ist das grundlegendste Verfahren nach dem SQL Server SQL-Anweisungen ausführt. Die Schritte, die zur Verarbeitung einer einzelnen SELECT-Anweisung verwendet werden, die nur auf lokale Basistabellen verweist (keine Sichten oder Remotetabellen), sollen das zugrunde liegende Verfahren veranschaulichen.

Optimieren von SELECT-Anweisungen

Eine SELECT-Anweisung ist nicht prozedural; sie gibt nicht die genauen Schritte vor, die der Datenbankserver verwenden soll, um die angeforderten Daten abzurufen. Dies bedeutet, dass der Datenbankserver die Anweisung analysieren muss, um das effizienteste Verfahren zum Extrahieren der angeforderten Daten zu ermitteln. Dieser Vorgang wird als Optimieren der SELECT-Anweisung bezeichnet. Die Komponente, die ihn durchführt, wird als Abfrageoptimierer bezeichnet. Die Eingaben für den Optimierer bestehen aus der Abfrage, dem Datenbankschema (Tabellen- und Indexdefinitionen) und den Datenbankstatistiken. Die Ausgabe des Optimierers ist ein Abfrageausführungsplan, der manchmal auch als Abfrageplan oder einfach nur als Plan bezeichnet wird. Der Inhalt eines Abfrageplans wird ausführlicher an späterer Stelle in diesem Thema beschrieben.

Die Ein- und Ausgaben des Abfrageoptimierers während der Optimierung einer einzelnen SELECT-Anweisung werden in der folgenden Abbildung gezeigt:

Abfrageoptimierung einer SELECT-Anweisung

Eine SELECT-Anweisung definiert lediglich Folgendes:

  • Das Format des Resultsets. Dieses wird meistens in der Auswahlliste angegeben. Das endgültige Format des Resultsets wird jedoch auch von anderen Klauseln, wie z. B. ORDER BY und GROUP BY, beeinflusst.

  • Die Tabellen, die die Quelldaten enthalten. Diese werden in der FROM-Klausel angegeben.

  • Die logischen Beziehungen zwischen den Tabellen, die im Rahmen der SELECT-Anweisung relevant sind. Diese werden in den Joinspezifikationen definiert, die in der WHERE-Klausel oder in einer ON-Klausel, die auf FROM folgt, auftreten können.

  • Die Bedingungen, die die Zeilen in den Quelltabellen erfüllen müssen, um die SELECT-Anweisung zu kennzeichnen. Diese werden in den WHERE- und HAVING-Klauseln angegeben.

In einem Abfrageausführungsplan wird Folgendes definiert:

  • Die Reihenfolge des Zugriffs auf die Quelltabellen.

    In der Regel gibt es viele Abfolgen, in denen der Datenbankserver auf die Basistabellen zugreifen kann, um das Resultset zu erstellen. Wenn die SELECT-Anweisung z. B. auf drei Tabellen verweist, könnte der Datenbankserver zuerst auf TableA zugreifen, dann die Daten aus TableA verwenden, um die entsprechenden Zeilen aus TableB zu extrahieren, und dann die Daten aus TableB verwenden, um Daten aus TableC zu extrahieren. Die anderen Abfolgen, in denen der Datenbankserver auf die Tabellen zugreifen kann, lauten:

    TableC, TableB, TableA oder

    TableB, TableA, TableC oder

    TableB, TableC, TableA oder

    TableC, TableA, TableB

  • Die Methoden, die verwendet werden, um Daten aus den einzelnen Tabellen zu extrahieren.

    Für den Zugriff auf die Daten in den einzelnen Tabellen gibt es in der Regel unterschiedliche Methoden. Wenn nur wenige Zeilen mit bestimmten Schlüsselwerten erforderlich sind, kann der Datenbankserver einen Index verwenden. Wenn alle Zeilen der Tabelle erforderlich sind, kann der Datenbankserver die Indizes übergehen und einen Tabellenscan ausführen. Wenn alle Zeilen einer Tabelle erforderlich sind, die Tabelle jedoch über einen Index verfügt, dessen Schlüsselspalten in einer ORDER BY-Klausel verwendet werden, kann durch die Durchführung eines Indexscans anstelle eines Tabellenscans eine andere Sortierung des Resultsets stattfinden. Wenn es sich um eine sehr kleine Tabelle handelt, können Tabellenscans die effizienteste Methode für fast alle Zugriffe auf die Tabelle darstellen.

Der Vorgang, in dessen Verlauf ein bestimmter Ausführungsplan aus einer Anzahl möglicher Ausführungspläne ausgewählt wird, wird Optimierung genannt. Der Abfrageoptimierer stellt eine der wichtigsten Komponenten eines SQL-Datenbanksystems dar. Der Abfrageoptimierer erzeugt zwar den zusätzlichen Aufwand, um die Abfrage analysieren und einen Plan auswählen zu können, ein Vielfaches dieses Aufwands wird jedoch normalerweise dadurch eingespart, dass der Abfrageoptimierer einen effizienten Ausführungsplan auswählt. Nehmen Sie z. B. an, zwei Bauunternehmer erhalten dieselben Konstruktionszeichnungen für ein Haus. Wenn nun das eine Unternehmen zunächst einige Tage darauf verwendet, den Bau des Hauses detailliert zu planen, das andere Unternehmen jedoch sofort und ohne weitere Planung mit dem Bau des Hauses beginnt, ist es mehr als wahrscheinlich, dass das erste Unternehmen, das sich Zeit für die Planung des Projekts nimmt, den Bau des Hauses zuerst abschließen wird.

Der Abfrageoptimierer von SQL Server arbeitet kostenorientiert. Jeder denkbare Ausführungsplan verfügt über zugeordnete Kosten hinsichtlich des Umfangs der benötigten Verarbeitungsressourcen. Der Abfrageoptimierer muss die möglichen Pläne analysieren und den Plan auswählen, der die geringsten geschätzten Kosten verursacht. Einige komplexe SELECT-Anweisungen verfügen über mehrere Tausend mögliche Ausführungspläne. In einem solchen Fall werden nicht alle denkbaren Kombinationen vom Abfrageoptimierer analysiert. Stattdessen werden komplexe Algorithmen verwendet, um einen Ausführungsplan zu ermitteln, dessen Kosten sich in vernünftigem Rahmen an die möglichen Mindestkosten annähern.

Der Abfrageoptimierer von SQL Server wählt nicht nur den Ausführungsplan aus, der die geringsten Kosten bezüglich der benötigten Ressourcen verursacht. Stattdessen wird der Plan ausgewählt, der die Ergebnisse so schnell wie möglich an den Benutzer zurückgibt und dabei Kosten für Ressourcen in vertretbarem Maß verursacht. Für die parallele Verarbeitung einer Abfrage werden in der Regel mehr Ressourcen verwendet als für die serielle Verarbeitung, die Abfrageausführung wird jedoch schneller beendet. Der SQL Server-Optimierer verwendet einen Plan mit paralleler Ausführung, um Ergebnisse zurückzugeben, wenn sich dies nicht negativ auf die Serverlast auswirkt.

Der Abfrageoptimierer stützt sich bei der Schätzung der Ressourcenkosten, die durch unterschiedliche Methoden zum Extrahieren von Informationen aus einer Tabelle oder einem Index verursacht werden, auf Verteilungsstatistiken. Die Verteilungsstatistiken werden für Spalten und Indizes erstellt. Sie kennzeichnen die Selektivität der Werte in einem bestimmten Index oder einer bestimmten Spalte. In einer Tabelle für Autos stammen z. B. viele Autos von demselben Hersteller, jedes Auto verfügt jedoch über eine eindeutige Fahrzeugnummer. Ein Index, der die Fahrzeugnummer verwendet, weist eine höhere Selektivität auf als ein Index, der den Hersteller einsetzt. Wenn die Indexstatistiken nicht auf dem aktuellen Stand sind, wählt der Abfrageoptimierer möglicherweise nicht den Plan aus, der für den aktuellen Status der Tabelle am besten geeignet ist. Weitere Informationen zur Aktualisierung von Indexstatistiken finden Sie unter Verwenden von Statistiken zum Verbessern der Abfrageleistung.

Der Abfrageoptimierer ist deshalb so wichtig, weil er es dem Datenbankserver ermöglicht, dynamische Anpassungen an geänderte Bedingungen in der Datenbank vorzunehmen, ohne dass eine Eingabe durch einen Programmierer oder Datenbankadministrator erforderlich ist. Programmierer können sich somit darauf konzentrieren, das endgültige Ergebnis der Abfrage zu beschreiben. Sie können sich darauf verlassen, dass der Abfrageoptimierer bei jeder Ausführung der Anweisung einen effizienten Ausführungsplan auf der Basis des aktuellen Status der Datenbank erstellt.

Verarbeiten einer SELECT-Anweisung

SQL Server führt zur Verarbeitung einer einzelnen SELECT-Anweisung die folgenden grundlegenden Schritte aus:

  1. Der Parser scannt die SELECT-Anweisung und spaltet sie in ihre logischen Einheiten auf, wie z. B. Schlüsselwörter, Ausdrücke, Operatoren und Bezeichner.

  2. Eine Abfragestruktur, manchmal auch Sequenzstruktur genannt, wird erstellt, die die logischen Schritte beschreibt, die für die Transformation der Quelldaten in das für das Resultset benötigte Format erforderlich sind.

  3. Der Abfrageoptimierer analysiert verschiedene Arten des Zugriffs auf die Quelltabellen. Anschließend wählt er die Reihenfolge der Schritte aus, mit denen die Ergebnisse am schnellsten mithilfe möglichst weniger Ressourcen zurückgegeben werden. Die Abfragestruktur wird aktualisiert, um diese genaue Reihenfolge von Schritten aufzuzeichnen. Die endgültige, optimierte Version der Abfragestruktur wird als Ausführungsplan bezeichnet.

  4. Das relationale Modul beginnt mit der Ausführung des Ausführungsplans. Während der Verarbeitung von Schritten, für die Daten aus den Basistabellen erforderlich sind, fordert das relationale Modul an, dass das Speichermodul die Daten aus den Rowsets übergibt, die durch das relationale Modul angefordert wurden.

  5. Das relationale Modul transformiert die Daten, die von dem Speichermodul zurückgegeben werden, in das für das Resultset definierte Format und gibt das Resultset an den Client zurück.

Verarbeiten anderer Anweisungen

Die zuvor beschriebenen grundlegenden Schritte für die Verarbeitung einer SELECT-Anweisung gelten ebenfalls für andere SQL-Anweisungen, wie z. B. INSERT, UPDATE und DELETE. Die UPDATE- und DELETE-Anweisungen müssen sich auf die Gruppe von Zeilen beziehen, die geändert bzw. gelöscht werden soll. Der Vorgang zum Identifizieren dieser Zeilen ist der gleiche Vorgang, der zum Identifizieren der Quellzeilen verwendet wird, die einen Beitrag zum Resultset einer SELECT-Anweisung leisten. Sowohl UPDATE- als auch INSERT-Anweisungen können eingebettete SELECT-Anweisungen enthalten, die die Datenwerte bereitstellen, die aktualisiert oder eingefügt werden sollen.

Sogar DDL-Anweisungen (Data Definition Language, Datendefinitionssprache), wie z. B. CREATE PROCEDURE oder ALTER TABLE, werden letztendlich in eine Folge relationaler Operationen aufgelöst, die für die Systemkatalogtabellen und manchmal (wie bei ALTER TABLE ADD COLUMN) auch für die Datentabellen ausgeführt werden.