Der Ausführungsplan – das unbekannte Wesen Martin Hoermann ORDIX AG Paderborn – Wiesbaden – Münster – Köln – Neu-Ulm Zusammenfassung Die Antwortzeit eines SQL Befehls bei gegebenem Datenbestand und Datenbankstrukturen wird im Wesentlichen durch den Ausführungsplan bestimmt. Die Interpretation eines Ausführungsplans und die bewusste Auswahl alternativer Pläne versetzt einen Datenbankadministrator oder Programmierer in die Lage, Performanceverbesserungen im Bereich von Faktoren zu erreichen. Dieser Beitrag stellt die Ermittlung eines Ausführungsplans vor, diskutiert die wichtigsten Zugriffs- und Joinstrategien, sowie weitere Elemente des Ausführungsplans. Anschließend werden verschiedene Varianten zur Beeinflussung von Ausführungsplänen erörtert. Abschließend stellt der Beitrag verschiedene Bewertungskriterien gegenüber. Dieser Beitrag konzentriert sich auf Ausführungspläne von Selects. Einleitung Ein Hase und ein Rehbock hatten einst ein Techtelmechtel. Daraus hervorgegangen ist der bayerische gehörnte Hase. Nun verloren auch andere bayerische Gattungen mehr und mehr die Hemmungen voreinander und crisensus crisensus, oder crisensus bavaricus, erblickte das Mondlicht der Welt – der Wolpertinger. Ähnlich wie der Wolpertinger ist auch der Ausführungsplan für viele ein unbekanntes Wesen. Es wird von Plänen berichtet, welche nicht existieren. Mystifizierungen und goldene Regeln versprechen schöne Ergebnisse, auch ohne eine Analyse. Tatsächlich lässt sich ein Ausführungsplan jedoch recht leicht in seine Bestandteile zerlegen, interpretieren und optimieren. Wie die Bestandteile des Wolpertingers: Hirschgeweih, Hasenkörper, Entenfüße usw. sind auch die Teile eines Ausführungsplans den meisten doch sehr bekannt ... Abb. 1: Der Wolpertinger Ermittlung des Ausführungsplans Es gibt zahlreiche Möglichkeiten, zu einem SQL Befehl den Ausführungsplan zu ermitteln. Die einfachste Variante ist sicherlich in SQL*Plus den Befehl set autotrace on abzusetzen. Zuvor muss die Tabelle plan_table mit dem Skript utlxplan.sql angelegt werden. In Oracle 10g ist dies nicht mehr notwendig. Eine alternative Variante bietet der Befehl explain plan for. Hierbei muss die Tabelle plan_table allerdings mit SQL ausgelesen werden. Pläne zu allen SQL Befehlen einer Session lassen sich aus der Trace Datei mit tkprof ermitteln. Ausführungspläne zu SQL Befehlen aus Statspack lassen sich mit dem Skript sprepsql.sql unter Angabe des Hash Value ermitteln. Für aktuelle SQL Befehle ist der Ausführungsplan in der View v$sql_plan enthalten, das korrekte Auslesen ist allerdings nicht trivial. Ausführungsplan lesen Einen umfangreichen Ausführungsplan zu lesen und zu interpretieren, bedarf grundsätzlich einiger Übung. Natürlich ist es möglich, eine wissenschaftliche Erläuterung zum Lesen von Ausführungsplänen zu geben, aber von zu traversierenden Bäumen zu sprechen ist für den Normalverbraucher doch etwas zu abstrakt. Wie die hebräische und arabische Sprache liest sich der Ausführungsplan von rechts nach links und von oben nach unten. Jedoch lässt sich, ausgehend von der Ergebnismenge zurück zu den Daten, auch eine umgekehrte Lesart vertreten. Plan ----------------------------------SELECT STATEMENT NESTED LOOPS TABLE ACCESS BY INDEX ROWID a INDEX RANGE SCAN a_idx TABLE ACCESS BY INDEX ROWID b INDEX RANGE SCAN b_idx Schritt -----------6 5 Verbinde 2 und 4 2 1 4 3 Der Autor bevorzugt SQL Befehle in Teile zu zerlegen und für die einzelnen Teile Ausführungspläne zu optimieren. Anschließend ist zu überlegen, wie Teilkomponenten optimal miteinander zu verknüpfen sind. Diese Methode führt meistens, aber nicht zwingend zu guten Ausführungsplänen. In einigen Fällen kann es sinnvoll sein, z. B. optimierte Subselect oder Inner Selects auch als Join zu schreiben, auch wenn diese Form unter Umständen schlechter lesbar ist. Wie die Architekten sagen: Form follows function. Aufbau Ein Ausführungsplan besteht immer aus einem oder mehreren Zugriffen auf Objekte, meist Segmenten. Bei mehreren Zugriffen werden die Ergebnismengen miteinander verknüpft. Hierzu stehen verschiedene Joinstrategien zur Verfügung. Mit Mengenoperatoren lassen sich gleichartige Ergebnismengen verbinden (z. B. UNION ALL). Die nächsten beiden Kapitel beleuchten Zugriffs- und Joinstrategien intensiv. Zur weitergehenden Verarbeitung von Ergebnismengen stehen zahlreiche Varianten von Sorts über Groups bis hin zu Connect By und Filter Operationen zur Verfügung. Zugriffsstrategien Die sicherlich einfachste Zugriffsstrategie ist der Full Table Scan (FTS). Unter Verwendung von Multiblock I/O (db_file_multiblock_read_count) liest der Oracle Prozess alle Blöcke der Tabelle in den Block Buffer oder in die PGA bei parallelen FTS. Dort werden die einzelnen Zeilen bzgl. WHERE Bedingung, nachgelagerten Zugriffen usw. verarbeitet. Der FTS ist in der Regel bei großen Ergebnismengen oder in Kombination mit Hash Joins sehr effizient. Was genau „groß“ bedeutet, ist von sehr vielen Faktoren abhängig. Der FTS wird oft mit langsam assoziiert, ist jedoch eine ernstzunehmende Zugriffsstrategie. Bei großen Tabellen und kleinen Ergebnismengen ist der Full Table Scan oft ein Hinweis auf fehlende Indizes. Plan ---------------------SELECT STATEMENT TABLE ACCESS FULL A Schritt ------------------------2 1 Eine alternative Zugriffsform auf Tabellendaten ist der Zugriffsplan Table Access by Index ROWID. Bei diesem Zugriff ermittelt der Oracle Prozess über die physikalische Adresse (ROWID) den zu lesenden Block und die Nummer des Datensatzes. Der Zugriff über die ROWID ist die schnellste Möglichkeit, einen Satz aus einer Tabelle zu lesen. Die ROWID stammt in der Regel aus einem vorgelagerten Index Zugriff. Die Alternative zum Full Table Scan ist daher immer ein Index Zugriff plus den Zugriff auf die Tabelle. Die verschiedenen Formen des Index Zugriffs werden nun diskutiert. Der Index Unique Scan wird z. B. verwendet, wenn der Optimizer eine Spalte in einer WHERE Bedingung findet und auf dieser Spalte ein Unique Index liegt. In diesem Fall liest der Prozess die Index Blöcke vom Segment Header (Root Block) über die sogenannten Branch Blocks bis hinunter zum Leaf Block. Im Leaf Block findet Oracle dann den Wert mit einer ROWID oder nichts, wenn der Wert nicht existiert. Wird ausschließlich der Wert benötigt, ist der Prozess fertig, ansonsten folgt in der Regel ein Table Access über ROWID. Der Index Unique Scan ist sehr effizient für den Zugriff auf einen Datensatz. Je häufiger ein Unique Scan innerhalb eines Joins aufgerufen wird, desto eher lohnen sich alternative Zugriffsstrategien, wie z. B. der FTS. Findet der Optimizer in der WHERE Bedingung einen Index für eine Treffermenge größer eins und wählt diesen für den Ausführungsplan aus, handelt es sich um einen Index Range Scan. Der Prozess startet wie beim Unique Scan. Abhängig von der WHERE Bedingung sucht der Prozess anschließend solange nach links oder rechts im Index Baum weiter, wie er passende Werte findet. Der Index Range Scan ist effizient für wenige Datensätze. Weiterhin ist der Range Scan in Zusammenhang mit einem Order By effizient, da die Daten bereits sortiert erhoben werden. Je größer die Trefferquote und je umfangreicher die Weiterverarbeitung ist, desto eher lohnt ein Fast Full Index Scan oder ein FTS. Plan -------------------------------------SELECT STATEMENT TABLE ACCESS BY INDEX ROWID A INDEX RANGE SCAN DESCENDING A_IDX Schritt -------3 2 – Daten über Rowid 1 – Index Rückwärts Beim Index Full Scan traversiert der Oracle Prozess den Index Baum bzgl. seiner logischen Sortierung. Das Ergebnis ist eine sortierte Ergebnismenge. Der Index Full Scan ist effizient, wenn eine sortierte Ergebnismenge, z. B. bedingt durch ORDER BY, benötigt wird. Abhängig von diversen Initialisierungsparametern kann ein Fast Full Index Scan mit anschließender Sortierung oder ein Full Table Scan durchaus performanter sein. Die letzte hier diskutierte Zugriffsstrategie ist der Fast Full Index Scan. Bei dieser Strategie liest der Prozess alle Blöcke eines Index mit Multiblock I/O. Dadurch ist ein sehr schnelles Lesen garantiert. Da in der physikalischen Reihenfolge gelesen wird, ist die Ergebnismenge anschließend nicht mehr sortiert. Der Zugriff ist ausgesprochen effizient, wenn alle benötigten Daten, also WHERE und SELECT, im Index vorhanden sind. In dieser Konstellation ist der Fast Full Index Scan fast immer effizienter als ein Full Table Scan. Bei einer hohen Trefferquote kann der Zugriff wegen des Multiblock I/O auch die Effizienz eines Index Range Scans oder die eines Index Full Scans übertreffen, obwohl ggf. eine anschließende Sortierung notwendig ist. Neben den vorgestellten Zugriffsstrategien gibt es zahlreiche weitere. Hierzu gehören beispielsweise der Index Skip Scan, Index Min/Max Scans, Zugriffe auf partitionierte Segmente, auf Bitmap Indizes, auf externe Tabellen und diverse weitere. Die Kenntnis über Art und Aufbau des Objektes helfen bei der Interpretation und Beurteilung der Zugriffsstrategie weiter. Joinstrategien Die zuvor genannten Zugriffsstrategien liefern jeweils eine Ergebnismenge zurück. Bei der Verknüpfung von mehreren Tabellen oder Indizes müssen die Ergebnismengen miteinander verbunden werden. Dies geschieht mit sogenannten Join Operationen, die jeweils genau zwei Ergebnismengen miteinander verknüpfen. Es existieren drei Formen des Join. - Nested Loop - Sort Merge Join - Hash Join Beim Nested Loop wird für jeden Datensatz der ersten Ergebnismenge die gesamte zweite Ergebnismenge durchsucht. Sind beide Ergebnismengen sehr groß, so ist der Nested Loop i.d.R. die ungünstigste Joinstrategie. Sind die Ergebnismengen sehr klein oder handelt es sich hierbei um ein Cartesian Produkt (jeder mit jedem), stellt der Nested Loop oft eine effiziente Strategie dar. Ist eine Ergebnismenge sehr klein, die andere sehr groß, so ist es wichtig, die kleine gegen die große Menge zu schneiden. Plan ----------------------------------SELECT STATEMENT NESTED LOOPS TABLE ACCESS FULL a TABLE ACCESS BY INDEX ROWID b INDEX RANGE SCAN b_idx Schritt -----------5 4 Verbinde 1 und 3 1 3 2 Der Sort Merge Join setzt zwei gleich geordnete Ergebnismengen voraus. Die Sortierung muss dem Join Kriterium genügen. In diesem Fall können die beiden Ergebnismengen sehr effizient miteinander verknüpft werden. Der Sort Merge Join ist effizient bei etwa gleich großen bereits vorsortierten Ergebnismengen. Im Idealfall können zwei Index Organized Tables bzgl. ihres Primary Key extrem effizient verbunden werden, schneller geht’s nicht! Der Sort Merge Join ist weniger effizient, je aufwendiger die vorhergehende Sortierung ist. Weniger zu empfehlen ist die Strategie, wenn die Ergebnismengen in Größe stark differieren oder wenn der Join nur wenige Treffer findet. Plan ----------------------------------SELECT STATEMENT SORT GROUP BY NOSORT MERGE JOIN SORT JOIN INDEX FAST FULL SCAN A_IDX SORT JOIN TABLE ACCESS FULL B Schritt ------------7 6 – hier ist kein Sort mehr notwendig 5 – verknüpfe 2 und 4, beide sortiert 2 – aber es muss nachsortiert werden 1 – schneller Index Zugriff 4 3 Beim Hash Join wird die vermeintlich kleinere Ergebnismenge in den Speicher gelesen. Bzgl. des Join Kriteriums wird ein Hash Schlüssel berechnet, der einen Zugriff auf die Ergebnismenge mit minimalem Aufwand ermöglicht. Der Aufwand für einen Einzelzugriff ist, bedingt durch den Hash-Algorithmus, effizienter als ein Indexzugriff. Der Hash Join ist sehr effizient, wenn große Ergebnismengen mit kleinen Ergebnismengen verknüpft werden, also auf die erzeugte Hash Tabelle sehr oft zugegriffen wird. „Klein“ steht dabei in Relation zum verfügbaren Speicher für Sortierungen (PGA_AGGREGATE_TARGET bzw. sort_area_size). Plan ----------------------SELECT STATEMENT HASH JOIN TABLE ACCESS FULL A Schritt -----------------------4 3 – suche zu B Sätzen in der Hash Partition 1 – hier wird ge-hash-t TABLE ACCESS FULL B 2 Der Hash Join ist meist ineffizient, wenn aufgrund der Tabellengröße sehr viele HashPartitionen entstehen. Varianten Zu diesen vier Joinstrategien existieren die Unterformen Anti-, Outer-, Cartesian- und SemiJoin. Das Vorgehen ist im Prinzip identisch, lediglich die Auswahl welche Datensätze zusammen passen ist jeweils eine andere. Der Anti-Join ermittelt die Datensätze, zu denen keine korrespondierenden Sätze existieren, z. B. durch die NOT IN Klausel. Der Outer-Join lässt auch Zeilen einer Ergebnismenge zu, wenn keine passenden Sätze in der zweiten Ergebnismenge zu finden sind. Der Semi-Join gibt Datensätze zurück, zu welchen eine Subquery mindestens einen Treffer liefert. Der Semi-Join findet bei der Exists Klausel Anwendung. Der Cartesian-Join verbindet alle Sätze der ersten Ergebnismenge mit allen Sätzen der zweiten Ergebnismenge. Noch ein kleiner Nachtrag zum sogenannten Star-Join und zum Cluster-Join. Diese Operationen können Sie im Ausführungsplan vergeblich suchen, denn beide sind kein Join im eigentlichen Sinn. Vielmehr beschreibt der Star Join ein Konzept zur Verknüpfung von mehreren Tabellen. Im Prinzip geht es hier um eine große Tabelle, welche mit mehreren kleinen – voneinander unabhängigen – Tabellen zu verbinden sind. Der Join selbst erfolgt mit Hash, Nested Loop oder Sort Merge. Der Cluster-Join besteht aus einen Table Access (Cluster) plus einen Table Access für jede weitere Tabelle verbunden über einen Nested Loop. Keine Join Strategie im eigentlichen Sinn, aber Verknüpfungen von mehreren Ergebnismengen sind beispielsweise Bitmap Merge, Bitmap Or und Bitmap And. Bei diesen Verknüpfungen kann Oracle, im Gegensatz zu Join Operationen, auch mehr als zwei Ergebnismengen miteinander verknüpfen. Weitere Elemente des Ausführungsplans Neben den Zugriffsstrategien und den Joinstrategien gibt es zahlreiche weitere Operationen in einem Ausführungsplan. Die gängigen sind in folgender Liste aufgeführt. - Die oberste Operation ist aus der Menge SELECT, INSERT, UPDATE, DELETE und zeigt an, um welche grundsätzliche Form des Zugriffs es sich handelt. - Vollständige Sortierungen einer Ergebnismenge sind an der Operation Sort (Order By) zu erkennen, Sortierungen von Teilen einer Ergebnismenge, typisch bei der Verwendung von analytischen Funktionen, zeigen die Operation Window (Sort) an. Sortierungen für Gruppierungen arbeiten mit der Operation Sort (Group By) und für Joins mit der Operation Sort (Join). Die Verwendung der Distinct Klausel führt zur Operation Sort (Unique). - Die Auswahl bestimmter Zeilen durch eine Having Klausel ist im Ausführungsplan an der Operation Filter zu erkennen. - Mengenfunktionen wie UNION ALL, MINUS und INTERSECT sind an gleichnamigen Operationen im Ausführungsplan zu erkennen. Die Mengenfunktion UNION wird mit der Operation UNION ALL und einem anschließenden Sort (Unique) verarbeitet. - Die Operation Connect By in ihren verschiedenen Varianten verweist auf hierarchische Abfragen Es gibt zahlreiche weitere Operationen in einem Ausführungsplan. Mit den hier Vorgestellten lassen sich sicherlich weit über 90 % aller SQL Ausführungspläne analysieren. Ausführungspläne beeinflussen Es gibt zahlreiche Möglichkeiten, Ausführungspläne festzulegen, zu forcieren oder zu beeinflussen. Die nachfolgende Liste stellt, ohne einen Anspruch auf Vollständigkeit, mögliche Varianten vor. Dabei kann ein Ausführungsplan natürlich grundsätzlich nur erzwungen werden, wenn das Ergebnis mit diesem Ausführungsplan zustande kommen kann. - Stored Outlines: Ausführungspläne werden in der Datenbank gespeichert und durch Verwendung des Initialisierungsparameters use_stored_outlines verwendet. Diese Variante ist sehr aufwendig, garantiert aber den gewünschten Ausführungsplan. - Hints: Durch Hinweise im SQL Befehl können Zugriffsstrategien, Joinstrategien und ähnliches erzwungen werden. Die Verwendung von Hints stellt in der Programmierung die einfachste Form dar, bestimmte Ausführungspläne zu erzwingen. Hier ein Beispiel für einen Fast Full Index Scan: SELECT /*+ INDEX_FFS (t1 i1) */ c1 FROM t1; - Initialisierungsparameter: es gibt zahlreiche Initialisierungsparameter, welche einen mehr oder weniger signifikanten Einfluss auf die Erstellung von Ausführungsplänen haben. So können z. B. mit optimizer_index_cost_adj oder optimizer_index_caching die geschätzten Kosten beeinflusst werden. Mit star_transformation_enabled und hash_join_enabled lassen sich Joinstrategien vollständig ausschalten. Die Parameter sort_area_size und db_file_multiblock_read_count beeinflussen die tatsächlichen Kosten für bestimmte Operationen und finden dadurch natürlich auch Berücksichtigung durch den kostenbasierten Optimizer (CBO). - Durch neue Indizes oder alternative Speicherformen (IOT) eröffnen sich natürlich grundsätzlich alternative Zugriffsstrategien. - Die Wahl des Optimizer (rule, choose) und beim kostenbasierten Optimizer die Wahl des Optimierungsziels (First Rows, All Rows etc.) haben einen wichtigen Einfluss auf die Strategien, die der Optimizer zur Erstellung von Ausführungsplänen wählt. First Rows und All Rows stellen aber keine Operationen im Ausführungsplan dar! Ab Oracle 10g steht der regelbasierte Optimizer nicht mehr zur Verfügung. Bei regelbasierten Optimizern haben Statistiken einen wesentlichen Einfluss auf die Erstellung von Ausführungsplänen. Die Statistiken sollten ein möglichst gutes Abbild der tatsächlichen Datenverteilung und der Datenmengen liefern. Es ist allerdings auch möglich – und in sehr wenigen Fällen sinnvoll – durch falsche Statistiken effizientere Ausführungspläne zu erstellen. Erfolg Messen Die einfachste Beurteilung des Erfolges ist sicherlich die Antwortzeit des SQL Kommandos. Dieses Verfahren ist allerdings unter stark schwankender Datenbanklast nicht reproduzierbar. Weiterhin berücksichtigt es auch die Auswirkungen auf andere Benutzer nicht. So kann zwar ein massiv paralleler Zugriff den Chef glücklich machen, jedoch die gesamte Belegschaft einer Firma der „Eieruhr“ ausliefern. Eine andere Variante ist die Bewertung der Anzahl der Logical Reads. Eine Minimierung der Logical Reads führt fast immer auch zu kürzeren Antwortzeiten bei gleichen Rahmenbedingungen, jedoch ist dieser Zusammenhang nicht zwingend. Der Autor bevorzugt diese Variante, da die Bewertung reproduzierbar ist. Physical Reads sollten in der Regel nicht betrachtet werden, da diese stark abhängig vom aktuellen Caching der Datenbank sind. In einzelnen Fällen macht jedoch auch dies Sinn. call count ------- -----Parse 0 Execute 1 Fetch 43 ------- -----total 44 cpu elapsed disk query current ------- --------- -------- -------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 2.10 15.57 58415 60777 30 ------- --------- -------- -------- ---------2.10 15.57 58415 60777 30 rows ---------0 0 1055 ---------1055 Die oben aufgeführte Statistik aus einer tkprof Analyse zu einem Ausführungsplan zeigt recht anschaulich die vielfältigen Faktoren bei der Beurteilung von Ausführungsplänen. CPU Zeit bedingt durch logical Reads (query+current), Elapsed Time durch physical Reads (disk) und das in Relation zu der Ergebnismenge (rows) müssen sorgfältig untersucht werden. Verschiedene Ausführungspläne sollten hier – abgesehen von der Spalte rows – unterschiedliche Aufwände zeigen. Last but not Least stellen die Werte Cost, Card und Bytes im Ausführungsplan eine hilfreiche Unterstützung bei der Bewertung – insbesondere von Zwischenschritten – dar. Die Kosten einer Operation misst Oracle in der abstrakten Einheit Cost, also eine Art Oracle Dollar zur Unterscheidung von besser und schlechter. Die Anzahl Datensätze wird geschätzt und über die Kardinalität (Card) ausgegeben, ebenso die daraus resultierenden Bytes. ---------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost | ---------------------------------------------------------------------| 0 | SELECT STATEMENT | | 7 | 21 | 2 | | 1 | SORT GROUP BY NOSORT| | 7 | 21 | 2 | |* 2 | INDEX RANGE SCAN | MESSDATEN_PK | 140 | 420 | 2 | ---------------------------------------------------------------------- Das obige Beispiel zeigt recht anschaulich die Reduzierung der Zeilen (Rows) und daraus resultierend der Datenmenge (Bytes). Die Kosten bewertet der Optimizer dagegen gleich hoch. Die Analyse wurde mit dem Befehl explain plan for und die anschließende Auswertung mit select * from table(dbms_xplan.display( 'PLAN_TABLE' )) durchgeführt. Fazit Von den weit über 150 unterschiedlichen, möglichen Operationen im Ausführungsplan wurde ein repräsentativer Teil vorgestellt. Eine Systematisierung zeigt die einzelnen Gruppen von Operationen (Zugriffsstrategien, Joins usw.) und deren Zusammenspiel. „Teile und Herrsche“ ist das wichtigste Verfahren, um komplexe Ausführungspläne zu analysieren und diese anschließend optimieren zu können. Literatur Die Oracle Dokumentation liefert eine gute Basis für das Verständnis von Ausführungsplänen. Darüber hinaus finden sich unter den Adressen http://metalink.oracle.com und http://technet.oracle.com sehr gute Ergänzungen und Whitepaper. Die mit Abstand umfangreichste Auflistung von Operationen des Ausführungsplans ist unter der Adresse http://julian.dyke.users.btopenworld.com/ zu finden. Kontaktadresse: ORDIX AG Martin Hoermann Zentrale Paderborn An der Alten Ziegelei 5 Westernmauer 12 – 16 D-48157 Münster D-33098 Paderborn Telefon: +49 (0) 2 51-9 24 35-00 +49 (0) 52 51-10 63-0 E-Mail: [email protected] Internet: www.ordix.de Eine Vollmondnacht in einem bayrischen Wald. Irgendwo im Gebirge ziehen zwei müde Wanderer ihres Weges. Ein Rascheln erregt ihre Aufmerksamkeit, es kracht und zischt, plötzlich steht ein Tier vor ihnen das einem Alptraum entsprungen zu sein scheint. Sie erinnern sich an das alte bayrische Sprichwort: "Siehst du einen Wolpertinger, sieh zu dass du wegkommst ..."