T ECHNISCHE U NIVERSITÄT I LMENAU Fakultät für Informatik und Automatisierung Institut für Praktische Informatik und Medieninformatik Fachgebiet Datenbanken und Informationssysteme D IPLOMARBEIT Anfragengetriebenes Index-Tuning in PostgreSQL Inventarisierungsnummer 2004-09-30/101/IN99/2254 eingereicht im: von: Betreuer: Septemer 2004 Mario Stiffel geboren am 25. Februar 1979 in Eisenach Prof. Sattler 2 Erklärung Ich versichere, dass ich die vorliegende Diplomarbeit selbständig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt habe. Die Arbeit hat in gleicher oder ähnlicher Form noch keinem Prüfungsamt vorgelegen. Ilmenau, den 13. September 2004 ............................................ 2004-09-30/101/IN99/2254 INHALTSVERZEICHNIS 3 Inhaltsverzeichnis Thesen 5 1 Einleitung 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Ziel der Diplomarbeit . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Index-Set Bestimmung . . . . . . . . . . . . . . . . . . . . . . . . . 6 6 8 8 2 Existierende Systeme 2.1 Oracle Virtual Index Wizard . . . . . . . . 2.2 Microsoft SQL Server Index Selection Tool 2.3 IBM DB2 Advisor . . . . . . . . . . . . . 2.4 QUIET . . . . . . . . . . . . . . . . . . . 2.5 Zusammenfassung . . . . . . . . . . . . . 3 PostgreSQL 3.1 Indizes . . . . . . . . . . . . . . . . 3.1.1 Indizes allgemein . . . . . . 3.1.2 Index-Scan . . . . . . . . . 3.1.3 Index-Typen in PostgreSQL 3.2 Anfrageplanung . . . . . . . . . . . 3.3 TPC-H Datenbank . . . . . . . . . 3.4 Der Weg einer Anfrage . . . . . . . 3.5 List-Package . . . . . . . . . . . . 3.6 Trees . . . . . . . . . . . . . . . . . 3.6.1 Nodes . . . . . . . . . . . . 3.6.2 Query-Tree . . . . . . . . . 3.6.3 Plan-Tree . . . . . . . . . . 3.7 CommandCounter . . . . . . . . . . 3.8 Disk-Pages . . . . . . . . . . . . . 3.9 System Catalog . . . . . . . . . . . 3.9.1 Access Methods . . . . . . 3.9.2 Operatoren . . . . . . . . . 3.9.3 Operator Class . . . . . . . 2004-09-30/101/IN99/2254 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 10 10 12 14 16 . . . . . . . . . . . . . . . . . . 18 18 18 21 21 22 23 25 27 29 29 33 41 46 48 50 50 51 52 INHALTSVERZEICHNIS 4 5 4 3.10 Run-time Configuration . . . . . . . . . . . . . . . . . . . . . . . . . 53 Index-Advisor 4.1 Grundlegender Ablauf . . . . . . . . . . 4.2 Index-Kandidaten-Liste . . . . . . . . . . 4.3 Regelwerk . . . . . . . . . . . . . . . . . 4.4 Virtuelle Indizes . . . . . . . . . . . . . . 4.5 Systemtabelle pg_indexadvisor . . . . . . 4.6 Ablauf . . . . . . . . . . . . . . . . . . . 4.6.1 Input . . . . . . . . . . . . . . . 4.6.2 Ausführung ohne virtuelle Indizes 4.6.3 Index-Kandidaten . . . . . . . . . 4.6.4 Anlegen der virtuellen Indizes . . 4.6.5 Ausführung mit virtuellen Indizes 4.6.6 Entfernen der virtuellen Indizes . 4.6.7 Speicherung des Ergebnisses . . . 4.7 Implementierung . . . . . . . . . . . . . 4.8 Advisor-Configuration . . . . . . . . . . 4.9 Auswertung . . . . . . . . . . . . . . . . 4.10 Einsatz . . . . . . . . . . . . . . . . . . . 4.11 Fazit . . . . . . . . . . . . . . . . . . . . 57 58 59 61 63 68 69 70 71 71 73 73 74 75 76 77 78 82 82 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Abbildungsverzeichnis 86 Literaturverzeichnis 87 2004-09-30/101/IN99/2254 INHALTSVERZEICHNIS 5 Thesen • Eines der wichtigsten Qualitätsmerkmale eines Datenbanksystems ist die Performance. • Mittels Indizes kann die benötigte Zeit zur Verarbeitung von Anfragen drastisch reduziert werden. • Die Struktur einer Datenbank, ihre Inhalte und deren Nutzung ändert sich im Laufe der Zeit. Dies macht eine kontinuierliche Anpassung der Indizes notwendig. • Große Datenbanken können nicht manuell optimiert werden. Ein Beispiel dafür ist die Datenbank der Software „SAP R/3”. • Die Ermittlung eines Index-Set für einen gegebenen Workload hat einen exponentiellen Zeitbedarf. • Die Nutzenabschätzung eines Index ist mit virtuellen Indizes möglich. Sie liefern mit geringem Aufwand eine gute Schätzung der Realität. • PostgreSQL ist eine gute Basis für den Index-Advisor. Das Datenbanksystem ermöglicht die vollständige Integration des Advisors durch Open-Source. • Der Index-Advisor benötigt drei wesentliche Schritte zur Ermittlung der IndexKandidaten. Der erste ist die Anfrageplanung ohne virtuelle Indizes. Der zweite ist die Anfragenplanung mit virtuellen Indizes und dritter ist der Vergleich der Ergebnisse. • Die Empfehlungen des Index-Advisors sind gut. Sie stimmen mit der Verwendung von realen Indizes überein. • Der zusätzliche Overhead des Index-Advisors ist minimal und wird durch seinen Mehrwert gerechtfertigt. Ilmenau, den 13. September 2004 ............................................ 2004-09-30/101/IN99/2254 KAPITEL 1. EINLEITUNG 6 Kapitel 1 Einleitung 1.1 Motivation Indizes sind neben Parametern eines der wichtigsten Mittel zur Optimierung einer Datenbank. Mithilfe von Indizes können Anfragen an ein Datenbanksystem effizienter abgearbeitet werden. Dies ermöglicht es, mit einem geringeren Hardwarebedarf das gleiche Ergebnis zu erzielen oder bei gleicher Hardware mehr Anfragen zu bearbeiten. Des Weiteren sinkt die Reaktionszeit der Datenbank, was in vielen Fällen ein unschätzbarer Vorteil sein kann. Die Vorteile der Indizes machen ihre Verwendung zu einem wichtigen Punkt bei der Datenbankadministration. Die Verwendung von Indizes bringt aber auch Nachteile mit sich. Der Offensichtlichste dabei ist der zusätzliche Speicherverbrauch eines jeden Index. Dabei gehen wertvolle Speicherressourcen verloren, ohne das mehr Informationen in der Datenbank abgelegt sind. Wie viel Speicher genau dabei verloren geht, hängt von der konkreten Tabelle und des Typs der Spalte ab. Das dies aber ein beträchtlicher Teil sein kann, ist in Abbildung 1.1 zu sehen. Die dargestellten Beispiele stammen aus der Datenbank des TPC-H Benchmarks (siehe Kapitel 3.3). Abbildung 1.1: Beispiele der Größenverhältnisse zwischen Tabelle und Index 2004-09-30/101/IN99/2254 KAPITEL 1. EINLEITUNG 7 Ein weiterer, nicht zu unterschätzender, Nachteil von Indizes ist der zusätzliche Aufwand für das Datenbanksystem selbst. Dieser Mehraufwand entsteht bei jeder Veränderung an einer Tabelle, welche Indizes enthält. Ursache dafür ist, dass die Modifikationen an der Tabelle ebenfalls in die Indizes übertragen werden müssen. Der dritte Nachteil betrifft die Ausführungsplanung der Anfragen. Bei dieser Planung müssen existierende Indizes berücksichtigt werden, nur so können sie der Datenbank einen Vorteil bringen. Je mehr Indizes angelegt sind, desto mehr mögliche Pläne gibt es, was sich in längeren Planungszeiten niederschlägt. Damit die Vorteile der Indizes ihre Nachteile überschatten, sollte das Anlegen von Indizes sparsam und wohl durchdacht erfolgen. Ein Vorgehen wie das Anlegen eines Index für jede Spalte in der Datenbank ist somit schon zum Scheitern verurteilt und würde der Datenbank nicht den gewünschten Geschwindigkeitsgewinn bringen. Indizes dürfen nur auf Spalten angelegt werden, bei denen dies auch angebracht ist. Das wichtigste Entscheidungskriterium für das Anlegen von Indizes ist die Zugriffshäufigkeit. Mit diesem Wert wird ausgesagt, wie oft eine Spalte innerhalb aller Anfragen auftaucht und mit einer Bedingung verknüpft ist. Sie ist also „aktiver Bestandteil” einer Anfrage. In heutigen Datenbanksystemen kommt die Aufgabe der Indexverwaltung dem Datenbankadministrator zu. Diese nicht automatisierte Indexverwaltung kann als „manuelle Planung” bezeichnet werden kann. Die Verwaltung beinhaltet die Planung, das Anlegen und die Pflege aller Indizes. Mit einem genauen Wissen über die Struktur der Datenbank und der Anfragen kann der Administrator geeignete Indizes auswählen und anlegen. Alle angelegten Indizes werden dabei als Index-Set bezeichnet. Dem Administrator wird es dabei aber nur schwer möglich sein, den zusätzlichen Speicherverbrauch und Mehraufwand der Indizes in seine Planung mit einzubeziehen. Je mehr Tabellen und Spalten eine Datenbank hat, desto schwieriger wird die Index-Planung „von Hand”. Ein Beispiel dafür ist die Datenbank der Software „SAP R/3”. Sie umfasst über 13.000 Relationen [17]. Eine solche Menge von Tabellen ist nur schwer zu überblicken und eine manuelle Index-Optimierung unmöglich. Kommt zu der steigenden Datenbankkomplexität noch die Unwissenheit über mögliche Anfragen an die Datenbank hinzu, ist eine „manuelle Planung” praktisch nicht mehr durchführbar. Die Annahme der Unwissenheit über die Anfragen ist dabei nicht so abwägig, wie es scheint. Sowohl die Datenbank, als auch die Benutzer können sich im Laufe der Zeit ändern. Daraus resultieren völlig neue Zusammenstellungen von Anfragen und damit Zugriffshäufigkeiten auf die Spalten der Datenbank. Auch eine alternierende Datenbanknutzung ist denkbar. So könnte die Nutzung am Tag durch Auslese-Anfragen geprägt sein und die Nacht durch Update-Anfragen, um die Daten auf den neuesten Stand zu bringen. Bei diesem Problem ist der Grund für „anfragengetriebes Index-Tuning” zu finden. Ziel dieses Verfahrens ist die automatische Bestimmung von nützlichen Indizes, das Anlegen dieser und die Pflege des Index-Set. Das Verfahren arbeitet dabei parallel zur Datenbank und passt die Indizes kontinuierlich an die Zugriffshäufigkeiten an. Damit kann das anfragengetriebene Index-Tuning als „automatische Planung” der Indizes 2004-09-30/101/IN99/2254 KAPITEL 1. EINLEITUNG 8 verstanden werden. Erreicht werden soll, dass zu jeder Zeit ein optimales Index-Set vorliegt. Des Weiteren ist es möglich, bei der Auswahl von Indizes Parameter vorzugeben, welche eingehalten werden müssen. So kann der von den Indizes verwendete Speicher begrenzt werden, als auch das notwendige Verhältnis von Vorteilen zu Nachteilen beschrieben werden. Aus der „automatischen Planung” resultiert eine Entlastung des Datenbankadministrators und Performancesteigerung des Datenbanksystems. Dabei ist Index-Tuning ein Teilstück eines Self-Tuning-Datenbanksystems. Dieses verfügt u.a. über eine automatische Memory-, Disk- und Konfigurations-Optimierung. So kann sich das Datenbanksystem an die Anforderungen kontinuierlich anpassen. 1.2 Ziel der Diplomarbeit Ein bestehendes Datenbanksystem soll um einen so genannten „Indexadvisor” erweitert werden. Der „Indexadvisor” (im folgenden auch als „Advisor” bezeichnet) stellt einen wichtigen Baustein des anfragengetriebenen Index-Tuning dar. Seine Aufgabe besteht in der Analyse jeder Anfrage, die an die Datenbank gestellt wird. Dabei soll der „Advisor” nützliche Indizes für die Anfrage ermitteln. Außer der Aussage, welche Indizes nützlich sind, muss der mögliche Performancegewinn abgeschätzt werden. Diese Informationen können dann als Entscheidungsgrundlage für die Erstellung von Indizes genutzt werden. Als Datenbanksystem wurde PostgreSQL gewählt. In dieses wird der „Indexadvisor” implementiert und zwischen dem Rewriter und dem Optimieren eingegliedert. PostgreSQL ist ein Open-Source-Projekt, d.h. der Quelltext der Datenbank ist frei verfügbar. Dies macht die Einbindung des „Advisors” erst möglich. Zu den weiteren Vorteilen des Datenbanksystems gehört seine weite Verbreitung und damit seine große Akzeptanz, als auch der sehr gute Optimierer, welcher eine große Bedeutung für den „Advisor” hat. 1.3 Index-Set Bestimmung Die Bestimmung eines optimalen Index-Sets für eine Anfrage ist sehr aufwendig. Das Laufzeitverhalten eines solchen Algorithmus ist exponentiell in Abhängigkeit zu der Anzahl der Attribute. Dies resultiert aus den stark ansteigenden Kombinationsmöglichkeiten zur Bildung des Index-Sets. Die Bestimmung eines optimalen Index-Sets ist also ein NP-vollständiges Problem. Zum Beweis kann es leicht in das „Rucksack-Problem” überführt werden. Durch die schlechte Skalierbarkeit dieser Probleme können nicht beliebig große Datenmengen verarbeitet werden. Für die Bestimmung eines Index-Set, bedeutet dies: Ab einer bestimmten Anzahl von Attributen ist die Berechnung zu aufwendig. Dies gilt insbesondere, wenn die Berechnung parallel zur Anfrageverarbeitung erfolgt. Um auch bei aufwändigen Anfragen mit vielen Attributen ein Index-Set bestimmen 2004-09-30/101/IN99/2254 KAPITEL 1. EINLEITUNG 9 zu können, werden unterschiedliche Lösungsansätze benutzt. Diese werden im Kapitel 2 genauer vorgestellt. Allen gemeinsam ist, dass sie nicht alle möglichen Kombinationen berücksichtigen. Damit können sie auch nicht garantieren, dass ein optimales Index-Set bestimmt wird. Dennoch liefern sie gute Ergebnisse und bieten eine gute Skalierbarkeit. 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 10 Kapitel 2 Existierende Systeme 2.1 Oracle Virtual Index Wizard Zur der Datenbank „Oracle” ist eine Sammlung von Zusatzprogrammen verfügbar, welche zum „Oracle Tuning Pack” zusammengefasst sind. Ein Programm des „Tuning Packs” ist der „Virtual Index Wizard”. Der Wizard soll bei der Auswahl von nützlichen Indizes für eine Anfrage helfen. Für diese Aufgabe benötigt er lediglich die Anfrage, den Namen der Tabelle und der Spalte, welche auf ihre Eignung für einen Index überprüft werden soll. Mittels dieser Informationen kann der Wizard seine Aufgabe durchführen. Er benutzt dabei den Optimierer der Datenbank und einen so genannten „virtuellen Index”. Dieser Index-Typ simuliert einen „realen Index” und ermöglicht die Abschätzung des möglichen Nutzen des Index. Das Ergebnis des „Virtual Index Wizard” ist eine Gegenüberstellung der Ausführung der Anfrage mit und ohne einem Index, wie in Abbildung 2.1 zu sehen ist. Somit wird es dem Benutzer ermöglicht, schnell und einfach mögliche Geschwindigkeitsvorteile zu ermitteln und gegebenenfalls Indizes anzulegen [4, 5]. 2.2 Microsoft SQL Server Index Selection Tool Seit der Version 7.0 verfügt der „Microsoft SQL Server” über das „Index Selection Tool”. Die Aufgabe des Tools ist die Ermittlung von nützlichen Indizes für eine Gruppe von Anfragen, welche auch als „Workload” bezeichnet wird. Das Ergebnis ist ein Index-Set, welches eine Menge von nützlichen Indizes für den „Workload” darstellt. Bei der Ermittlung des Index-Set können Grenzen vorgegeben werden, welche vom „Index Selection Tool” eingehalten werden. Solche Grenzen sind zum Beispiel die maximale Anzahl der Indizes oder der Speicherverbrauch des Index-Set. Eine Besonderheit des Tools ist die Berücksichtigung von Multi-Column-Indizes. Diese Indizes beschränken sich nicht auf eine Spalte, sondern indizieren mehrere Spalten gleichzeitig. Ihre Ermittlung ist wesentlich rechenintensiver als die Bestimmung von Single-Column-Indizes. Um die benötigte Rechenzeit in Grenzen zu halten, 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 11 Abbildung 2.1: Oracle - „Virtual Index Wizard” ([4]) werden zur Ermittlung der Multi-Column-Indizes nicht alle möglichen Spaltenkombinationen berücksichtigt, sondern Iterationen genutzt. Die erste Iteration beschränkt sich auf Single-Column-Indizes. Die zweite Iteration dient zur Ermittlung von TwoColumn-Indizes. Diese berücksichtigt aber nur Spalten, welche als Single-ColumnIndizes in Frage kommen. Nach dem selben Muster laufen die weiteren Iterationen ab. Durch das iterierte Vorgehen wird die Entscheidungskomplexität sehr stark verringert, und Multi-Column-Indizes können mit vertretbarem Rechenaufwand ermittelt werden. Die Ermittlung der nützlichen Indizes ist ein NP-vollständiges Problem. Dadurch steigt der benötigte Rechenaufwand mit der Anzahl der Attribute exponentiell an. Um das Problem abzuschwächen, werden die bereits beschriebenen Iterationen eingesetzt. Des Weiteren erfolgt die Ermittlung des Index-Set in zwei Schritten. Als Erstes wird für jede Anfrage des Workload ein eigenes Index-Set bestimmt. Erst im zweiten Schritt folgt die Ermittlung des Index-Set für den gesamten Workload. Dabei werden nur die Indizes berücksichtigt, welche zuvor auch in einem Anfragen-Index-Set enthalten waren. Für die Bestimmung des Nutzen eines jeden Index wird der Optimierer des „SQL Server” genutzt. Dazu wurde der Optimierer um What-if-Indizes erweitert. Diese speziellen Indizes existieren nicht physisch, sondern nur virtuell, d.h. sie werden dem Optimierer vorgetäuscht. Die Verwendung des Optimierers zur Entscheidung über den Nutzen eines Index hat den Vorteil, dass Konsistenz zwischen der Arbeit des „Index Selection Tool” und der normalen Anfrageverarbeitung besteht. In beiden Fällen kommt das gleiche Kostenmodell zum Einsatz. So ist es ausgeschlossen, dass ein Index, der vom „Index Selection Tool” empfohlen wird, bei der normalen Anfrageverarbeitung nicht zur Anwendung kommt. Das „Index Selection Tool” besteht aus insgesamt fünf Funktionsblöcken, welche 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 12 Abbildung 2.2: Architektur des „Index Selection Tool” ([3] / S.2) in der Abbildung 2.2 grau dargestellt sind. Der Input des Tools ist der „Workload”, also eine Zusammenstellung von Anfragen. Der erste Funktionsblock ist mit „Candidate index selection” bezeichnet. Er reduziert die Anzahl der Spalten, welche für einen Index in Frage kommen. Dies geschieht durch die Entfernung von Spalten, deren Indizes keinen oder nur geringen Gewinn versprechen. Der zweite Funktionsblock mit dem Titel „Configuration Enumeration” wählt aus allen möglichen Index-Kombinationen diejenige aus, welche die niedrigsten Kosten hat. Dabei werden gleichzeitig die vorgegebenen Parameter beachtet. Der mit „Multi-Column Index Generation” bezeichnete Funktionsabschnitt bildet Multi-Column-Indizes, welche beim nächsten Iterationsschritt des „Index Selection Tool” in Betracht gezogen werden sollen. Die zwei verbleibenden Funktionsblöcke benötigen eine Verbindung zum „SQL Server”. Sie führen die Nutzen- und Kostenermittlung der Indizes durch. Der „What-if Index Creation”Funktionsblock ist für das Erstellen der „What-if-Indizes” verantwortlich. Anhand der „What-if-Indizes” werden die Kosten eines für jeden Index und damit die Gesamtkosten eines Index-Set bestimmt. Die Tabelle mit dem Kosten wird im „Cost Evaluation” Funktionsblock verwaltet. Diese Tabelle wird zur Bestimmung des Index-Set mit den geringsten Kosten benötigt. Die Iterationen des „Index Selection Tool” sind über eine gepunktete Linie angedeutet. Sind alle Iterationen durchlaufen, gibt das Tool ein Index-Set als Empfehlung für den Workload aus. Dieses Ergebnis wird mit „Final Indexes” bezeichnet [3]. 2.3 IBM DB2 Advisor In dem Datenbanksystem DB2 von „IBM” wurde in der Version 6.1 der „IBM DB2 Advisor” eingeführt. Dieser ermittelt für einen Workload ein geeignetes Index-Set. 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 13 Auch der „IBM DB2 Advisor” nutzt für seine Berechnungen den Optimierer des Datenbanksystems. Dies ermöglicht den Verzicht auf ein weiteres Kostenmodell im Advisor und wahrt die Konsistenz zwischen der Ermittlung des Index-Set und dessen Nutzung durch die Datenbank. Der prinzipielle Aufbau des Advisors ist in Abbildung 2.3 dargestellt. Dabei sind die grauen Funktionsblöcke die eigentlichen Funktionen des „IBM DB2 Advisor”. Andere Blöcke stellen Funktionen und Einheiten des Datenbanksystems dar und werden zur Verdeutlichung der Arbeitsweise des Advisors benötigt. Abbildung 2.3: Architektur des „DB2 Advisors” ([1] / S.2) Die favorisierte Benutzungsart des Advisors ist über den „Index SmartGuide”. Dieser ist zusammen mit dem Datenbanksystem im System-Speicher zu finden. Der „Index SmartGuide” ist ein grafisches Benutzerinterface und ermöglicht dem Anwender eine komfortable Nutzung des Advisors. Die zweite mögliche Nutzungssart des Advisors ist über das Kommando-Zeilen-Programm „Db2Advis”. Hinter diesem Programm verbirgt sich der eigentliche Advisor. Wird der „Index SmartGuide” genutzt, so steuert dieser für den Anwender den „Db2Advis”. Als Input benötigt der Advisor ein Set von SQL-Anfragen, also den Workload, und eine Statistik welche die Häufigkeit der einzelnen Anfragen beschreibt. Bei der Nutzung des „Index SmartGuide” wird die Statistik automatisch gebildet. Dazu wird der SQL-Cache des Datenbanksystems nach den Anfragen des Workloads durchsucht und die ausgelesene Häufigkeit zur Bildung der Statistik genutzt. Im SQL-Cache befinden sich alle Anfragen, die vor kurzer Zeit ausgeführt wurden. Zusätzlich zu dem Workload und der Statistik können dem Advisor Parameter übergeben werden. Diese legen zum Beispiel fest, wie lange der Advisor für die Berechnung arbeiten darf oder welche Größe das Index-Set nicht überschreiten soll. 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 14 Die Arbeit des Advisors besteht darin, für jede Anfrage des Workloads den Optimierer aufzurufen. Dieser ermittelt eine Index-Empfehlung für die Anfrage und legt das Ergebnis in den „Advise Tables” ab. Von dort werden die Werte vom Advisor ausgelesen und zur Bildung des Index-Set genutzt. Die „Advise Tables” dienen damit als Kommunikationsmittel zwischen dem Advisor und dem Optimierer. Das Ergebnis des „DB2 Advisors” ist ein Index-Set, welches die Empfehlung für den gegebenen Workload ist. Auch der „DB2 Advisor” muss mit dem exponentiellen Wachstum des Rechenaufwands zurecht kommen. Dies löst er jedoch anders als das „Index Selection Tool” (siehe Kapitel 2.2). Der „DB2 Advisor” benutzt einen randomisierten Algorithmus. Dieser erstellt aus einer Menge von möglicherweise nützlichen Indizes ein zufälliges Index-Set. Danach ermittelt er den Geschwindigkeitsvorteil des Index-Set und vergleicht diesen mit dem aktuell besten Index-Set. Ist das Index-Set vorteilhafter, so überschreibt es das aktuell beste, ansonsten wird es verworfen. Dieser Vorgang wiederholt sich solange bis eine vorgegebene Zeit vergangen ist. Durch den randomisierten Algorithmus ist die benötigte Zeit immer konstant und unabhängig von dem Umfang der Datenbank und des Workloads [1]. 2.4 QUIET Das QUIET-System ist eine universitäre Entwicklung und ein Ansatz für anfragengetriebenes Index-Tuning. Dabei steht der Name QUIET für „Query-driven Index Tuning”. Das System nutzt den „DB2 Advisor” (siehe Kapitel 2.3), um das Index-Tuning durchzuführen. Die Architektur des QUIET-Systems ist in Abbildung 2.4 zu sehen. Um das QUIET-System in die Anfrageverarbeitung zu integrieren, wurde es als Middleware zwischen dem Datenbank-Frontend und dem DB2-Datenbanksystem umgesetzt. So müssen alle Anfragen an das Datenbanksystem zuerst das QUIET-System durchlaufen. Die Anfragen werden vom Frontend über das „JDBC Interface” (Java Database Connectivity) an die Datenbank gegeben. Dort wird die Anfrage vom QUIETSystem abgefangen. Nachdem QUIET seine Arbeit beendet hat, gibt es die Anfrage über das „JDBC Interface” weiter an die Datenbank. Der erste Teil des QUIET-Systems ist der „Statistic Gatherer”. Er wird bei jeder Anfrage aufgerufen. Seine Aufgabe ist die kontinuierliche Bildung einer IndexStatistik. Diese gibt Auskunft, wie hoch der Profit von möglichen Indizes ist. Für diese Aufgabe verfügt der „Statistic Gatherer” über ein eigenes Kostenmodell. Dieses berücksichtigt die einmaligen Kosten bei der Erstellung eines Index, die kontinuierlichen Kosten seiner Pflege und den Gewinn bei jeder Anfrage. Zur Ermittlung von nützlichen Indizes wird der Optimierer des DB2-Datenbanksystems verwendet, welcher bereits in Kapitel 2.3 vorgestellt wurde. Die Empfehlungen des Optimierers muss der „Statistic Gatherer” aus den „Explain Tables” auslesen. Hat der „Statistic Gatherer” seine Arbeit beendet, legt er die Index-Empfehlungen in der Datenbank „Index Statistics” ab. Teil zwei des QUIET-Systems ist der „Configuration Manager”. Er verwaltet den 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 15 Abbildung 2.4: Architektur des QUIET-Systems ([2] / S. 3) „Index-Pool” der Datenbank. Im „Index-Pool” befinden sich alle vom QUIET angelegten Indizes, dabei unterliegt der Pool einer Speichergrenze, welche nicht überschritten wird. Der „Configuration Manager” arbeitet ausschließlich mit den Ergebnissen des „Statistic Gatherer”, welches die Einträge in der Datenbank „Index Statistics” sind. Anhand dieser Einträge erstellt der „Configuration Manager” neue Indizes oder entfernt unnütze aus dem „Index-Pool”. Diese Aufgabe erledigt er mittels festgelegter Strategien, welche für eine maximale Auslastung des „Index-Pools” und ein optimales Index-Set sorgen sollen. Eine Einsicht in die Arbeit des Systems erlaubt der dritte Teil des QUIET-Systems. Der „Index Configuration Monitor” bietet einen Einblick in die Statistik, zeigt alle angelegten Indizes im „Index-Pool”, den Profit eines jeden Index und die Auslastung des „Index-Pool”. In Abbildung 2.5 ist ein Screenshot des „Configuration Monitor” abgebildet. Der „Query Generator” gehört nicht im eigentlichen Sinne zum QUIET-System. Er dient ausschließlich zum Testen. Dazu generiert er Anfragen für das System und ermöglicht das Testen unter frei wählbaren Bedingungen [2]. Durch die QUIET-Middleware ist aus dem eigentlichen „DB2 Advisor” ein anfragengetriebenes Index-Tuning-System entstanden. Dieses ist in der Lage, sich verändernden Workloads anzupassen um jederzeit eine optimale Leistung der Datenbank zu garantieren. Jedoch hat das QUIET-System auch Nachteile. Der erste ist die fehlende Integration in die Datenbank. Die Middleware kann umgangen werden und würde das System ungewollt nutzlos machen. Hinzu kommen Performance-nachteile durch die zusätzliche Middleware und der Nutzung des „DB2 Advisors”, welcher für diese Aufgabe nicht entwickelt wurde. Ein weiterer Nachteil ist die Abhängigkeit vom „DB2 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 16 Abbildung 2.5: „Index Configuration Monitor” des QUIET-Systems ([2] / S. 4) Advisor”. Sollte sich in der Weiterentwicklung der DB2-Datenbank die Ansteuerung oder der Funktionsumfang des Advisors ändern, so kann dies direkte Auswirkungen auf das QUIET-System haben. Eine bessere Lösung als die Nutzung einer Middleware ist die Integration des QUIET in das Datenbanksystem selbst. Für kommerzielle Datenbanken, wie im Fall von DB2, ist der Quellcode aber nicht frei verfügbar. Dadurch ist eine Integration nicht möglich. 2.5 Zusammenfassung Der „Oracle - Virtual Index Wizard”, das „Microsoft - Index Selection Tool” und der „IBM - DB2 Advisor” stellen gute Ansätze für die automatische Bestimmung von nützlichen Indizes dar. Ihr Funktionsumfang ist für anfragengetriebenes Index Tuning aber viel zu gering. Im Besonderen fehlt ihnen: 1. Das Anlegen von Indizes Die vorgestellten Programme erstellen nur Empfehlungen. Das Anlegen der Indizes muss weiterhin manuell vom Datenbankadministrator erfolgen. 2. Das Löschen von Indizes Wenn sich der Workload ändert, können bereits angelegte Indizes unnütz werden und verlangsamen die Datenbank. Solche Indizes müssen manuell gefunden und entfernt werden, da sie von den Tools völlig unberücksichtigt bleiben. 3. Die „online-Arbeit” Alle Programme werden unabhängig von der Datenbank gestartet, d.h., sie werden nicht in die laufende Anfrageverarbeitung einbezogen. Dies wird als „offline-Arbeit” bezeichnet. Zur Realisierung eines anfragengetriebenen Systems ist allerdings die „online-Arbeit” notwendig. Das QUIET-System löst diese Nachteile, stellt aber trotzdem keine vollwertige Lösung dar. Die Kritikpunkte an diesem System sind: 2004-09-30/101/IN99/2254 KAPITEL 2. EXISTIERENDE SYSTEME 17 1. Hoher Overhead Die Realisierung als Middleware ist eine unzureichende Lösung. Größter Nachteil ist die schlechte Performance. So kann der durch das QUIET-System erzeugte Overhead bei bis zu einer Sekunde pro Anfrage liegen 2. Keine Kombinierbarkeit Nachteil der Nutzung des DB2-Datenbanksystems ist, dass keine Kombination mit anderen Optimierungen eines Datenbanksystems möglich ist. Solche Optimierungen sind meist für Open-Source-Datenbanken verfügbar und lassen sich durch den fehlenden Quellcode von DB2 nicht nutzen. 3. Abhängigkeit von „DB2 Advisor” Wie in Kapitel 2.3 bereits beschrieben ist das QUIET-System direkt vom „DB2 Advisor” abhängig. Eine Änderung seines Interface kann große Auswirkungen haben. Diese Probleme des QUIET haben zu dem Ziel geführt, das System auf eine andere Datenbank zu portieren. Allerdings hat keine verfügbare Datenbank die notwendigen Voraussetzungen dafür. Diese sind ein Optimierer mit virtuellen Indizes und ein Tool, welches Index-Empfehlungen ermitteln kann. Deshalb wurde eine Erweiterung der Datenbank PostgreSQL um die benötigten Funktionen erwogen. Die Abbildung 2.6 gibt eine Übersicht aller vorgestellten Systeme, mit ihren spezifischen Eigenschaften. Anlegen von Indizes Entfernen von Indizes Arbeitsart Systemintegration Virtual Index Wizard nein nein offline ja Index Selection Tool nein nein offline ja DB2 Advisor nein nein offline ja QUIET ja ja online nein Abbildung 2.6: Übersicht der vorgestellten Systeme 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 18 Kapitel 3 PostgreSQL PostgreSQL ist das ausgewählte Datenbanksystem für die Implementierung des IndexAdvisors. Die Open-Source-Verfügbarkeit und weite Verbreitung von PostgreSQL machen es zur idealen Plattform. Das Datenbanksystem basiert auf einer Entwicklung von der „University of California”. PostgreSQL ist eine relationale Datenbank mit objektorientierten Erweiterungen. Sie unterstützt die SQL-Standards „SQL92” und „SQL99”. Zu ihrer reichhaltigen Ausstattung gehören unter anderem Transaktionen, Fremdschlüssel und Views. Hinzu kommt die Möglichkeit der Erweiterung der Datenbank durch den Anwender. So können u.a. neue Datentypen, Funktionen oder IndexMethoden hinzugefügt werden [8]. In den folgenden Unterkapiteln werden wichtige Punkte des PostgreSQLDatenbank-Systems vorgestellt. Das Verständnis dieser Punkte ist wichtig für die spätere Erklärung des Aufbaus und der Arbeitsweise des Index-Advisors. Des Weiteren werden in den entsprechenden Unterkapiteln auch allgemeine Sachverhalte beschrieben, insoweit sie für das Verständnis hilfreich sind. 3.1 3.1.1 Indizes Indizes allgemein Die Indizes sind optionale Elemente einer Datenbank, d.h. die Anfrageergebnisse sind mit und ohne Indizes identisch. Der einzige Unterschied liegt in der Zeit, die vergeht, bis das Ergebnis einer Anfrage vorliegt. Mit den richtigen Indizes kann die Verarbeitungszeit einer Anfrage nur einen Bruchteil von der Zeit ohne Indizes betragen. Indizes stellen einen alternativen Zugriffspfad auf die Tupel einer Relation dar. Zu diesem Zweck enthält der Index die kompletten Werte eines oder mehrerer Attribute der Relation. Die Attributwerte dienen zur schnellen Beantwortung einer Suche. Aus diesem Grund werden die Werte in einer dafür geeigneten Speicherstruktur abgelegt. Dies kann zum Beispiel ein Binärbaum sein. Zusätzlich zu den Attributwerten verfügt der Index über Verweise zu den Tupeln 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 19 der indizierten Relation. Anhand der Verweise und Attributwerte können schnell die gesuchten Tupel aus der Relation bestimmt werden. Dabei müssen lediglich die Werte des Index durchsucht werden und nicht die gesamte Relation. Hinzu kommt die schnellere Suche durch die optimierte Speicherstruktur des Index. Ein Index kann allerdings nur dann zur Anwendung kommen, wenn mindestens eines seiner indizierten Attribute in der Anfrage vorkommt und mit einer Bedingung verknüpft ist. Die Abbildung 3.1 zeigt ein Beispiel einer Index-Nutzung. Die Anfrage ist links durch die SQL-Worte „SELECT - FROM - WHERE” dargestellt. Zur Beantwortung der Anfrage wird nicht sofort die zugehörige Relation benutzt, welche rechts dargestellt ist. Ein komplettes Durchsuchen der Relation würde bedeuten, dass die gesamte Relation von der Festplatte gelesen und jedes Tupel mit den Bedingungen der Anfrage verglichen werden muss. Dies kann einen sehr hohen Aufwand darstellen und viele Ressourcen verschwenden, besonders wenn die gesuchten Tupel nur ein kleiner Teil der eigentlichen Relation sind. Aus diesem Grund wird im Beispiel ein Index genutzt. Er ist in der Mitte der Abbildung dargestellt und als Binär-Baum aufgebaut. Diese Struktur ermöglicht ein schnelles Auffinden der gesuchten Werte. Sind die gesuchten Attributwerte gefunden, können anhand der Verweise im Index die entsprechenden Tupel der Relation ausgelesen werden. Abbildung 3.1: Beispiel einer Index-Nutzung Die Gründe der Performancesteigerung eines Datenbanksystems durch Indizes sind: 1. Direkter Tupelzugriff Mittels eines Index können die gesuchten Tupel einer Relation direkt bestimmt werden. Ein komplettes Durchsuchen der Relation ist nicht notwendig. Dies ist insbesondere wichtig, da bei den meisten Anfragen nur ein kleiner Teil der Tupel einer Relation von Interesse ist. Nur selten werden alle Tupel benötigt. Es profitieren nicht nur Select-Anfragen” von Indizes, auch bei Update-, Insert- und Delete-Anfragen müssen Tupel innerhalb einer Relation gesucht werden. 2. Sortierter Tupelzugriff Bei vielen Anfragen ist es notwendig, dass die Tupel einer Relation sortiert vor2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 20 liegen. Der Grund dafür kann datenbankintern oder durch die Anfrage vorgegeben sein. Ein datenbankinterner Grund ist z.B. die Art, wie zwei Tabellen verbunden werden sollen. Einige dieser Verbundoperationen, man spricht dabei von „Joins”, setzen die Sortierung der Relationen voraus. Der Sortierungsgrund von Seiten der Anfrage ist der Order-by-Teil der Anfrage. Wird eine Sortierung benötigt, so kann anstatt einer sehr aufwendigen Sortierung der gesamten Relation, der Index genutzt werden. Über ihn wird die Relation direkt sortiert ausgelesen. 3. „Index-Only” Zugriffe Der Index enthält die kompletten Informationen der indizierten Attribute. Sind diese Attribute die einzigen, welche in einer Anfrage vorkommen, so muss kein Tupel der Relation ausgelesen werden. Die Informationen im Index reichen vollkommen, um die Anfrage zu beantworten. 4. Realisierung von Unique-Attributen Unique-Attribute sind Attribute, bei denen garantiert wird, dass jeder mögliche Wert nur genau einmal innerhalb der Attribut-Spalte vorkommt. Solche UniqueAttribute werden zum Beispiel zur eindeutigen Identifikation von Tupel genutzt. Beim Einfügen eines neuen oder der Modifikation eines Tupels, muss bei einem Unique-Attribut geprüft werden, ob sein Wert nicht schon existiert. Dies entspricht einem Durchsuchen der gesamten Relation, was sehr lange dauern kann und Ressourcen verschwendet. Ein effizienterer Weg ist die Indizierung des entsprechenden Attributs. Dabei ist der Index für die Einhaltung der UniqueBedingung verantwortlich. Dem Index ist es viel schneller möglich zu entscheiden, ob es den Wert innerhalb der Attribut-Spalte bereits gibt. Neben diesen Vorteilen gibt es auch Nachteile von Indizes. Diese können die gewonnene Performance des Datenbanksystems wieder verringern oder sogar negieren. Die Nachteile sind im einzelnen: 1. Pflege der Indizes Indizes enthalten teilweise die kompletten Werte der indizierten Relation. Aus diesem Grund müssen die Indizes aktualisiert werden, sobald sich der Inhalt der Relation ändert. Solch eine Aktualisierung ist bei jedem Update, Delete oder Insert notwendig. Innerhalb des Index kann dies aber mehr als nur die simple Änderung eines Attributwertes bedeuten. Bei einem solchen Index-Update kann es möglich sein, dass die Speicherstruktur des Index umgeordnet werden muss. Nur so kann immer ein effizientes Suchen innerhalb des Index gewährleistet werden. Je mehr Indizes in einer Datenbank sind, desto höher kann die zusätzliche Arbeit bei der Änderung einer Relation werden. 2. Größe der Datenbank Die Attributwerte im Index sind redundant, d.h., sie vergrößern die Datenbank ohne einen Mehrgewinn an Informationen für den Benutzer. Damit erhöht die Indizierung eines Attributs den Speicherverbrauch des Attributs um mehr als das 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 21 Doppelte. So kann ein sehr verschwenderischer Umgang mit Indizes die Datenbankgröße stark ansteigen lassen, was unter der Voraussetzung von begrenzten Speicherressourcen nicht unbedenklich ist. 3.1.2 Index-Scan Das Durchsuchen eines Elementes der Datenbank wird als „Scan” bezeichnet. Wird ein Index durchsucht, so ist dies ein Index-Scan. Dabei müssen nicht alle Werte des Index gelesen werden. Durch die spezielle Speicherstruktur des Index, können die gewünschten Informationen gezielt ausgelesen werden. Im Gegensatz dazu liegen die Tupel in einer Relation unstrukturiert vor. Das Suchen in einer Relation geht nur durch das Einlesen der Relation vom Anfang bis zum Ende. Aus diesem Grund spricht man bei einem Durchsuchen einer Relation von einem Sequentiellen-Scan. 3.1.3 Index-Typen in PostgreSQL In PostgreSQL sind mehrere Typen von Indizes verfügbar. Der gewünschte Typ kann beim Anlegen eines Index spezifiziert werden. Der Unterschied zwischen den einzelnen Typen liegt in der verwendeten Speicherstruktur. Daraus ergeben sich unterschiedliche Eigenschaften, Fähigkeiten und Anwendungsgebiete. Folgende drei Index-Typen gibt es: 1. „B-Tree” Indizes Dieser Index-Typ ist der Default-Index-Typ, d.h., wenn beim Erstellen eines Index nicht explizit ein Typ angegeben wird, so wird ein B-Tree-Index angelegt. Als Speicherstruktur verwendet dieser Typ einen Binär-Baum. Dieser Baum ermöglicht es, die häufigsten Anfragebedingungen effizient zu beantworten. Diese sind Bereichs- und Vergleichsbedingungen. Bereichsbedingungen werden in SQL mittels des „BETWEEN” Schlüsselwortes formuliert. Ein Beispiel ist „price BETWEEN 5 AND 50”. Zu den Vergleichsbedingungen gehören u.a. die Operatoren „kleiner als” oder „größer als”. Alle unterstützten Operatoren sind „<”, „<=”, „=” , „>=” und „>”. Außer den Bereichs- und Vergleichsbedingungen unterstützt der B-Tree-Index „LIKE-”, „ILIKE”, „~” und „~*”-Bedingungen. So kann er zum Beispiel genutzt werden, um nach dem Anfang eines Wortes zu suchen, welches mit dem „LIKE” Schlüsselwort möglich ist. Der B-Tree-Index ermöglicht auch das sortierte Auslesen von Tupel. Dabei werden mehrere Sortierungsarten unterstützt. 2. „R-Tree” Indizes Der R-Tree-Index-Typ hat nur ein sehr spezielles Anwendungsgebiet. Er wird für Anfragen mit räumlichen Koordinaten genutzt. Diese sind zum Beispiel „Box-Koordinaten”. Dabei kann ermittelt werden, ob sich die Koordinaten überlappen, gegenseitig enthalten oder identisch sind. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 22 3. „Hash” Indizes Ein Hash-Index kann nur zur Beantwortung der Frage nach Gleichheit („=”) benutzt werden. Dies schränkt sein Anwendungsgebiet zwar stark ein, aber besonders für den Verbund von Relationen wird nur diese Bedingung benötigt. Hinzu kommt die hohe Effizienz des Hash-Index, da die Frage nach Gleichheit meist direkt beantwortet werden kann. Trotzdem wird der Hash-Index-Typ in PostgreSQL nur selten verwendet, denn laut der PostgreSQL-Dokumentation hat der Hash-Index, gegenüber dem B-Tree-Index, weder in der Geschwindigkeit, noch in der Größe des Index einen Vorteil [9]. Der Einsatz der unterschiedlichen Index-Typen unterliegt dem Benutzer. Er kann für den jeweiligen Einsatzzweck den richtigen Typ wählen. Dies dürfte in den meisten Fällen der B-Tree-Index sein, was seine Stellung als Default-Index-Typ rechtfertigt. Auch während der Verarbeitung einer Anfrage werden Indizes erstellt. Dies geschieht durch das Datenbanksystem und hat den Zweck, die Abarbeitung effizienter zu gestalten. Diese Vorgänge sind für den Benutzer transparent und geschehen nur temporär im Speicher der Datenbank. Die dabei verwendeten Index-Typen wählt das Datenbanksystem selbst aus. 3.2 Anfrageplanung Der Umfang einer Anfrage kann sehr einfach sein und nur eine Tabelle mit wenigen Spalten betreffen. Es sind aber auch hochkomplexe Anfragen mit vielen Tabellen, Spalten und Unteranfragen möglich. Bei jeder Anfrage muss das Datenbanksystem zuerst entscheiden, in welcher Reihenfolge die Anfrage verarbeitet wird. Dieser Vorgang wird als „Anfrageplanung” bezeichnet. Grund für diese Planung ist, dass nicht alle Teile der Anfrage gleichzeitig ermittelt werden können. Hängt z.B. eine Bedingung von dem Ergebnis einer Unteranfrage ab, so muss zuerst die Unteranfrage ausgeführt werden. Neben solchen Abhängigkeiten gibt es auch die Möglichkeit, dass die Reihenfolge von Operationen vertauscht werden kann, ohne das sich das Ergebnis verändert. Des Weiteren ist es möglich, dass mehrere Wege existieren, um die benötigten Tupel zu ermitteln. Dies kann z.B. ein Sequentieller-Scan und ein Index-Scan der Relation sein. Die Möglichkeiten, dasselbe Ergebnis zu erziehlen, aber mit unterschiedlichen Mitteln und Schritten, erhöhen sich mit der Komplexität der Anfrage. Eine konkrete Verarbeitungsreihenfolge aus allen möglichen wird als „Plan” bezeichnet. Ziel der Anfrageplanung ist es, den „Plan” zu bestimmen, der die niedrigsten Gesamtkosten aufweist. Die Ermittlung dieses „Plans” ist umso aufwendiger, je komplexer die Anfrage ist. Zur Reduzierung der Gesamtkosten werden zum Beispiel Operationen, die die Menge der Tupel stark einschränken, zuerst ausgeführt. Solche Operationen können Vergleichsbedingungen wie „kleiner als” sein. Erfüllen bei dieser Operation viele 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 23 Tupel die Bedingung nicht, müssen diese in den weiteren Schritten nicht sortiert oder mit anderen Tupeln verbunden werden. Das Ziel, den günstigsten „Plan” zufinden, ist dabei sehr aufwendig, denn der Zeitbedarf der Ermittlung steigt exponentiell mit der Anfragengröße an. In PostgreSQL wird die Anfrageplanung vom „Optimierer/Planner” durchgeführt. Dieser erstellt für jede Anfrage einen „Plan”. Der generierte „Plan” ist ein Baum, welcher vom „Executor” abgearbeitet wird. Dabei beginnt dieser bei den Blättern. An jedem Knoten müssen die Tupel der Zweige verbunden werden. Auf diese Weise entsteht die Ergebnismenge bis zur Wurzel. Soweit es dem „Optimierer/Planner” möglich ist, erstellt er jeden möglichen „Plan” und wählt den günstigsten aus. Durch das exponentielle Wachstum der Zeit für die Anfrageplanung ist dies nicht bei allen Anfragen möglich. Für das Wachstum ist insbesondere die Anzahl der „Joins” verantwortlich. Ist die benötigte Zeit für die Anfrageplaung zu hoch, wird der „Genetic Query Optimizer” eingesetzt. Sein Zeitbedarf wächst nicht exponentiell, aber der von ihm ermittelte „Plan” muss nicht der „Plan” mit den geringsten Gesamtkosten sein [10]. 3.3 TPC-H Datenbank In dem Kapitel 3 werden interne Abläufe und Datenstrukturen von PostgreSQL vorgestellt. Diese sind anhand eines Beispiels besser verständlich, als mit aufwendigen Umschreibungen. Für die Beispiele wird ein Teil der Datenbank des „TPC-H Benchmarks” verwendet. Der eigentliche Einsatzzweck dieser Datenbank ist die Funktion als Benchmark. Dabei soll der „TCP-H Benchmark” als Entscheidungshilfe für BusinessDatenbanksysteme helfen. Die Datenbank verfügt über eine große Anzahl an Daten, üblicherweise 1GB, und stellt mit den hoch komplexen Anfragen hohe Anforderungen an das Datenbanksystem. Die Resultate des Benchmarks sollen als Vergleichsmittel zwischen verschiedenen Datenbanksystemen dienen. Dabei sollen die ausgewählten Anfragen die Relevanz des Ergebnisses für den Einsatz in einem Unternehmen sicherstellen [16]. Das vollständige Schema der „TPC-H Datenbank” ist in Abbildung 3.2 dargestellt. Die Anzahl der Relationen und Attribute ist allerdings viel zu groß, um sie alle in den Beispielen zu nutzen. Deswegen beschränken sich die Beispiele auf zwei Relationen und insgesamt sechs Attribute, welche in der Abbildung 3.2 grau dargestellt sind. Die verwendeten Relationen sind: • Relation „orders” Diese Relation enthält, wie aus dem Namen zu schließen ist, Bestellungen. Dabei sind die Attribute „orderkey”, „orderdate” und „comment” für die Beispiele interessant. Das Attribut „orderkey” enthält den Identifikator der Bestellung. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 24 Abbildung 3.2: Schema der TPC-H Datenbank ([16] / S.12) Des Weiteren dient dieser auch als Fremdschlüssel in anderen Relationen. Das Datum der Bestellung wird in „orderdate” gespeichert. Ein Kommentar zu der Bestellung kann in dem Attribut „comment” abgelegt werden. In der gesamten „TPC-H Datenbank” erhalten alle Attribute vor ihren Namen den Anfangsbuchstaben der Relation. Dies macht die Attributnamen eindeutig in der gesamten Datenbank. Demzufolge lauten die korrekten Attributnamen „o_orderkey”, „o_orderdate” und „o_comment”. • Relation „lineitem” Die Zuordnung von Waren zu einer Bestellung geschieht über diese Relation. Dabei dient das Attribut „orderkey” als Bezug zur Relation „orders”, also zu der Bestellung selbst. Das Attribut „partkey” ist ein Fremdschlüssel und steht für die Ware. Die Menge der bestellten Ware wird in „quantity” gespeichert. Auch vor diese Attribute muss der Anfangsbuchstabe der Relation gesetzt werden. Die 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 25 Namen lauten also „l_orderkey”, „l_partkey” und „l_quantity”. Der gewählte Datenbankausschnitt ist im Vergleich zur gesamten Datenbank recht klein. Die ausgewählten Relationen und Attribute reichen aber völlig aus und machen die Beispiele überschaubarer. Die Nutzung von allen Attributen der Relation „lineitem” (16 Stück) und der Relation „orders” (9 Stück) würde weder zum Verständnis, noch zur Übersichtlichkeit beitragen. 3.4 Der Weg einer Anfrage Innerhalb des PostgreSQL-Datenbanksystems durchläuft eine Anfrage mehrere Verarbeitungsstufen bis das Ergebnis vorliegt. Man spricht dabei auch von einer Verarbeitungs-Pipeline. Dabei hat jede der Verarbeitungsabschnitte eine festgelegte Aufgabe. Einen Gesamtüberblick über die Verarbeitungsstufen und ihre Reihenfolge gibt die Abbildung 3.3. Die wichtigsten Verarbeitungsschritte werden im Folgenden beschrieben. Sie sind in der Abbildung innerhalb des größten grauen Rechteckes zu finden. • „Parser” Jede Anfrage erreicht das PostgreSQL-Datenbanksystem als String. Diesen String überprüft der Parser auf seine syntaktische Korrektheit. Findet er keine Fehler, erzeugt er anhand des Strings einen Baum, den so genannten QueryTree. Dieser repräsentiert die textuelle Anfrage, hat aber selbst eine baumartige Speicherstruktur. Die Wurzel des Baumes gibt darüber Auskunft, um was für eine Anfrage es sich handelt. Ein Beispiel dafür ist ein Select-Statement, also eine Anfrage, die mit dem „SELECT” Schlüsselwort beginnt und aus der Datenbank nur Daten ausliest. Von der Wurzel aus verzweigt sich der Baum. Die einzelnen Zweige enthalten weitere Informationen zu der Anfrage. Zum Beispiel existiert je ein Zweig, um die Informationen der „Where”- und „Group by”-Klausel zu speichern. Der entstandene Query-Tree ist die Grundlage für alle weiteren Verarbeitungsschritte. • „Transformation” Dieser Abschnitt wird in der Abbildung 3.3 als „Traffic Cop” bezeichnet. In dieser Verarbeitungsstufe werden die Namen der Relationen und Attribute in ihre Identifikatoren umgewandelt. Nur mit diesen kann das Datenbanksystem umgehen. Des Weiteren findet eine Trennung der Anfragen statt. Bei der Trennung werden komplexe Anfragen, wie z.B. Select-, Update- oder Delete-Anfragen an die „normale” Verarbeitungspipeline weitergegeben. Einfache Anfragen, wie z.B. Create-, Analyze- oder Drop-Anfragen hingegen werden von den sogenannten „Utility Commands” ausgeführt. Der Grund der Trennung liegt in dem notwendigen Aufwand für die Anfragen. Für einfache Anfragen ist es nicht notwendig, 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 26 Abbildung 3.3: Verlauf der Anfrageverarbeitung [7] eine Optimierung durchzuführen, auch unterliegen sie nicht „Views”. Diese Aufgaben werden im Weiteren noch genauer beschrieben. • „Rewrite” Das PostgreSQL-Datenbanksystem ermöglicht die Verwendung von so genannten „Views”. Mittels dieser können Relationen und ihre Attribute zu „neuen Relationen” zusammengestellt werden, man spricht dabei von „Views”. Die „Views” existieren nicht in der Datenbank, sondern werden für den Benutzer nur simuliert. Aktionen an diesen Relationen müssen auf die „physischen Relationen” umgelenkt werden. Diese Aufgabe unterliegt dem „Rewrite System”. Falls die Anfrage auf einem „View” basiert, schreibt das „Rewrite System” die Anfrage um. Im Ergebnis des Verarbeitungsschrittes existieren nur noch „physi- 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 27 sche Relationen” im Query-Tree. • „Optimizer” Die Aufgabe des Optimierers wurde bereits ausführlich im Kapitel 3.2 beschrieben. Die Arbeit dieses Abschnittes der Pipeline besteht im Wesentlichen in der Erstellung von Ausführungsplänen und der Auswahl des Planes mit den geringsten Gesamtkosten. Aus diesem Grund ist der Verarbeitungsschritt in der Abbildung 3.3 mittels der zwei Blöcke „Generate Paths” und „Choose Path & Generate Plan” dargestellt. Das Ergebnis ist ein neuer Baum, der Plan-Tree. Dieser ersetzt den Query-Tree und repräsentiert nicht mehr die Anfrage. Die Struktur des Plan-Tree stellt die Reihenfolge dar, in welcher der „Executor” die Anfrage abarbeiten soll. • „Executor” Dieser Verarbeitungsschritt ist der letzte der Pipeline. Anhand des Plan-Trees führt er die Verarbeitung der Anfrage durch und bildet die Ergebnismenge. Dazu durchläuft er den Plan-Tree rekursiv, beginnend von der Wurzel. Mit dem Auslesen von Tupel beginnt der „Executor” aber erst, wenn er bei einem Blatt angelangt ist. Zum Schluss wird die Ergebnismenge vom Executor an den Aufrufenden zurückgegeben [6, 7]. 3.5 List-Package Listen sind unabdingbare Datenstrukturen in fast jedem Programm, so auch im PostgreSQL-Datenbanksystem. Dort werden sie u.a. zur Speicherung von Parametern, einer Menge von Tupeln oder als Teil einer Baumstruktur eingesetzt. Zur Implementierung von PostgreSQL wird die Hochsprache „C” genutzt. Sie stellt keine vollwertigen Listen zur Verfügung. In „C” sind nur Arrays mit fester Länge und einheitlichem Typ möglich. Aus diesem Grund existiert eine List-Package in PostgreSQL. Diese „Package” ermöglicht das Erstellen und die Verwendung von einfachverketteten Listen. Dafür stellt sie vielfältige Funktionen bereit. Diese ermöglichen z.B. das Suchen innerhalb einer Liste, das Verbinden und Vergleichen von Listen und das Entfernen von Elementen aus einer Liste. Die Listen werden mittels Zeiger zwischen ihren Elementen aufgebaut. Dadurch ist die Länge einer Liste nur durch den verfügbaren Speicher begrenzt. Die List-Package stellt zwei Listentypen bereit. Der erste Typ verfügt nur über einen Zeiger auf den Listenanfang, den so genannten „Head”. Eine solche Liste ist in der Abbildung 3.4 dargestellt. Operationen am Ende der Liste können nicht direkt ausgeführt werden. Dazu muss zuerst die komplette Liste durchlaufen werden. Aus diesem Grund gibt es den zweiten Listentyp, der in PostgreSQL auch als „FastList” bezeichnet wird. Dieser Typ hat zwei Zeiger auf die Liste, einen auf den „Head” und einen auf das Ende der Liste, dem so genannten „Tail”. Der Zeiger auf den „Tail” macht besonders bei der Arbeit mit sehr langen Listen Sinn. Bei solchen Listen kann das Durchlaufen der 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 28 kompletten Liste viel Zeit in Anspruch nehmen, was mit dem Tail-Zeiger vermieden wird. Abbildung 3.4: List-Package Beispiel Wie bereits kurz erwähnt, ist in der Abbildung 3.4 eine Beispiel-Liste vom ersten Typ, also eine „normale” Liste, dargestellt. Die Variable „dataList” speichert den Zeiger auf den Listenanfang. Dieser Zeiger verweist auf das erste Element der Liste, dabei sind die Listenelemente immer Listen-Nodes. Diese Nodes erlauben die Speicherung eines Zeiger-, Integer- und Oid-Wertes. Die Nutzung der drei Werte ist beliebig, es können auch alle drei Felder gleichzeitig benutzt werden. In dem Integer- und OidFeld können Zahlenwerte abgelegt werden. Dabei ist Integer selbsterklärend und Oid steht für Object-id, was ein in PostgreSQL häufig verwendeter Typ ist. Er wird zur Identifikation von Relationen, Tupeln oder Operationen vergeben. In dem Zeiger-Feld kann ein beliebiger Zeiger abgelegt werden. Diese Möglichkeit dient dazu, in der Liste beliebige Informationen abzulegen. Dies wurde auch in dem Beispiel in Abbildung 3.4 genutzt. Die mit „Data1”, „Data2” und „Data3” bezeichneten Blöcke stellen die in der Liste abgelegten Informationen dar. Jeder der List-Nodes verfügt über ein Feld mit dem Namen „next”. Dieses Feld enthält einen Zeiger auf das nächste Element der Liste. Ist das Listenende erreicht, wird dies mit einem Null-Zeiger symbolisiert. Dieser ist in der Abbildung 3.4 mit einem „x” verdeutlicht. Die Deklaration des List-Node-Typs ist in Abbildung 3.5 zu sehen. Sie besteht aus dem „type-”, „union-” und „next-Feld”. Der „type” dient zur Identifikation des ListNodes und weist bei allen List-Nodes den selben Wert auf, dieser ist „T_LIST”. Das „union-Feld” stellt die möglichen „Speicherplätze” bereit, welche bereits beschrieben wurden. Der Verweis auf das nächste Element der Liste ist im „next-Feld” enthalten. Die List-Package ermöglicht das einfache Anlegen und Arbeiten mit Listen. Durch die einheitliche Struktur können die Listen auch als Parameter von Funktionen genutzt werden. Bei der Nutzung des Integer- oder Oid-Feldes stehen des Weiteren eine Fülle von unterstützenden Funktionen bereit. Mit diesen können u.a. Werte in der Liste gesucht, ausgelesen und entfernt werden. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 29 typedef struct List { NodeTag type; union { void *ptr_value; int int_value; Oid oid_value; } elem; struct List *next; } List; Abbildung 3.5: Definition des List-Nodes 3.6 Trees Bei der Verarbeitung einer Anfrage durch das PostgreSQL-Datenbanksystem (siehe Kapitel 3.4) werden mehrere Bäume erzeugt. Die Bäume, auch als Trees bezeichnet, repräsentieren die Anfrage und dienen zur Speicherung von Verarbeitungsergebnissen. Sie sind die zentralen Speicherstrukturen bei der Verarbeitung. Auch der IndexAdvisor nutzt die Trees. Aus diesem Grund ist eine genaue Kenntnis der Bäume wichtig für das Verständnis der Arbeitsweise des Index-Advisors. In den folgenden Unterkapiteln werden die in PostgreSQL vorkommenden Bäume vorgestellt. Dabei werden zuerst die Nodes beschrieben, aus denen sich jeder Baum zusammensetzt. 3.6.1 Nodes Die Nodes (zu deutsch „Knoten”) sind die eigentlichen Informationsträger eines jeden Baumes in PostgreSQL. Der Aufbau eines Nodes ist in der Abbildung 3.9 beispielhaft dargestellt. Ein Node besteht im Allgemeinen nur aus einer Sammlung von Variablen. Je nach der Aufgabe des Nodes schwankt die Anzahl und der Typ der Variablen. Im Verlauf der Anfrageverarbeitung werden die Werte der Variablen gesetzt, ausgelesen und modifiziert. Dies geschieht u.a. vom „Parser”, „Optimizer” und „Executor”, welche bereits im Kapitel 3.4 vorgestellt wurden. Die Modifikation der Variablen dient der Speicherung und Weitergabe von Verarbeitungsergebnissen. Eine besondere Bedeutung kommt den Variablen vom Typ „NodeTag” und „Pointer” zu. Im „NodeTag”, welcher nur einmal pro Node vorkommen darf, wird der Typ des Nodes festgelegt. Vor jeder Arbeit mit einem Node, muss der „NodeTag” ausgelesen werden, denn dieser gibt Auskunft über die Struktur des Nodes. Das bedeutet anhand des „NodeTag” kann der Typ eines jeden Nodes ermittelt werden. Variablen vom 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 30 Typ „Pointer” dienen u.a. zur Bildung einer Baumstruktur. Dazu können die „Pointer” eines Nodes auf andere Nodes zeigen. Dies ermöglicht den Aufbau beliebig großer und komplexer Baumstrukturen. Aufgrund der vielfältigen Einsatzmöglichkeiten der Nodes gibt es viele verschiedene Typen. Diese sind in PostgreSQL in Kategorien eingeteilt. Dabei sind die Kategorien nach dem Einsatzzweck des Nodetyps benannt. Diese sind: • „Primenodes” Diese Nodes können in jedem Baum vorkommen. Damit bilden die Primenodes eine Art Basis. Auch werden viele Nodes von den Primenodes abgeleitet. Eine Ableitung ist dabei mit einer Vererbung in der objektorientierten Programmierung vergleichbar. Bei einer Ableitung erhält der neue Node alle Variablen eines Primenodes und zusätzlich können weitere hinzugefügt werden. Ein Beispiel für einen Primenode ist der Executable-Expression-Node oder kurz Expr-Node. Seine Definition ist in Abbildung 3.6 dargestellt. Viele Nodes werden vom Expr-Node abgeleitet. Die Definition des Expr-Nodes besteht nur aus dem „NodeTag”. Dieser hat keine Bedeutung zur Speicherung von Verarbeitungsinformationen. Erst die abgeleiteten Nodes von diesem Typ können für die Anfrageverarbeitung genutzt werden. Trotzdem hat der Expr-Node seine Berechtigung. Durch ihn ist es jederzeit möglich, alle abgeleiteten Nodes mit zusätzlichen Variablen zu versehen. typedef struct Expr { NodeTag type; } Expr; Abbildung 3.6: Definition des Expr-Nodes • „Parsenodes” Nodes dieser Kategorie werden vom Parser zum Aufbau des Query-Tree benötigt. Der Parser bildet aus der Anfrage, welche in Textform vorliegt, den QueryTree. Für diesen Zweck werden Nodes benötigt, welche die Abschnitte, Schlüsselwörter und Funktionen des SQL-Standards repräsentieren. So gibt es z.B. einen Node für eine Insert-Anfrage, den InsertStmt-Node und einen Node für die Modifizerung einer Tabelle, den AlterTableStmt-Node. • „Plannodes” Der Optimierer erstellt anhand des gerade beschriebenen Query-Trees, den PlanTree. In dem Plan-Tree kommen die Plannodes zur Anwendung. Diese Kategorie von Nodes ist notwendig, da die Struktur und der Inhalt des Plan-Tree völlig 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 31 verschieden zum Query-Tree ist. Jeder der Plannodes ist vom Node „Plan” abgeleitet. Dessen Definition ist in Abbildung 3.7 dargestellt. Dabei sind nur die wichtigsten Variablen aufgeführt. Diese sind der bereits erklärte „NodeTag”, die Kosten „startup_cost” und „total_cost” sowie die Größe der Ergebnismenge, mit „plan_rows” und „plan_width” bezeichnet. Die zwei Kostenvariablen sind eine Schätzung des Optimierers und dienen zur Auswahl des günstigsten „Plans”. Die Variable „startup_cost” bezeichnet dabei die Kosten, die minimal benötigt werden, um die Anfrage zu beantworten. In Abhängigkeit von der Menge der Tupel, welche für die Anfrage ausgelesen werden müssen, können die Kosten bis zu dem Wert der Variablen „total_cost” steigen. Eine Schätzung über die Tupelanzahl ist die Variable „plan_rows”. Dabei gibt der Wert „plan_width” die ungefähre Anzahl an Bytes an, die jedes ErgebnisTupel haben wird. Beispiele für Plannodes sind der IndexScan-Node und der MergeJoin-Node. Der IndexScan-Node liefert alle Angaben zur Benutzung eines Index, um Tupel aus einer Relation auszulesen. Der MergeJoin-Node beschreibt, wie Tupel aus verschiedenen Relationen miteinander verbunden werden sollen. Die Informationen dieser Nodes werden von „Executor” ausgewertet und entsprechend genutzt. typedef struct Plan { NodeTag type; Cost startup_cost; Cost total_cost; double plan_rows; int plan_width; ... } Plan; Abbildung 3.7: Definition des Plan-Nodes • „Execnodes” Wie sich anhand des Namen vermuten lässt, werden die Execnodes vom „Executor” eingesetzt. Dieser benötigt die Nodes zur Speicherung von Statusinformationen. Der „Executor” arbeitet den Plan-Tree der Reihe nach ab. Dabei entstehen Daten, für deren Aufnahme die Nodes im Plan-Tree nicht vorgesehen sind. Um die Informationen trotzdem ablegen zu können, baut der „Executor” einen weiteren Tree auf, dies ist der State-Tree. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 32 Der State-Tree besteht aus den Execnodes. Dabei existiert für viele Node-Typen der Plannodes ein korrespondierender Node-Typ in den Execnodes. Beim Aufbau des State-Trees legt der Executor Verweise zwischen den meisten Nodes des Plan-Tree und den zugehörigen Nodes des State-Tree an. Informationen die während der Verarbeitung eines Nodes des Plan-Tree anfallen, werden vom Executor in den korrespondierenden Node im State-Tree abgelegt. Dieser Zusammenhang ist in Abbildung 3.8 dargestellt. Dabei symbolisieren die gepunkteten Pfeile die Verweise zwischen den zwei Trees. Beispiele für Execnodes sind der SubPlanState-Node und der LimitState-Node. Der SubPlanState-Node korrespondiert mit dem SubqueryScan-Node und enthält die Informationen zu einer Unteranfrage. Der zum LimitState-Node gehörige Plannode ist der Limit-Node. Dieser ist für die Begrenzung der Ergebnismenge verantwortlich, was dem SQL-Schlüsselwort „LIMIT” entspricht. Abbildung 3.8: Zusammenhang zwischen State-Tree und Plan-Tree An einem Beispiel soll veranschaulicht werden, wie ein Node aufgebaut ist. Dazu wird der SortBy-Node genauer betrachtet. Seine Definition ist in Abbildung 3.9 zu sehen. Der Node ist ein Parsenode und wird im Query-Tree verwendet. Der „Parser” nutzt diesen Node-Typ bei der Umsetzung der „Order by-Klausel” einer Anfrage. Die Variablen des SortBy-Nodes bedeuten im einzelnen: • „type” Dies ist der „NodeTag”. Er dient zur Identifikation des SortBy-Nodes und muss immer den Wert „T_SortBy” haben. • „sortby_kind” Der Wert dieser Variable legt die Sortierungsart fest. Die dabei möglichen Werte werden über Konstanten festgelegt. Es gibt je eine Konstante für aufsteigende, 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 33 typedef struct SortBy { NodeTag type; int sortby_kind; List *useOp; Node *node; } SortBy Abbildung 3.9: Definition des SortBy-Nodes absteigende und spezielle Sortierung. Dabei ist mit der „speziellen Sortierung” eine vom Anwender angelegte Sortierart gemeint. • „useOp” Dieser Zeiger verweist auf den Operator, der zur Sortierung genutzt werden soll. Ein Operator dient bei der Sortierung zur Ermittlung der Beziehung zweier Werte. Das Ergebnis des Operators ist die Aussage, welcher der zwei Werte größer ist. Eine ausführlichere Beschreibung der Operatoren ist in Kapitel 3.9.2 zu finden. • „node” Die Variable „node” ist ebenfalls ein Zeiger. Sie verweist auf die Informationen zum Attribut, nach welchem die Sortierung erfolgen soll. Die Menge der verschiedenen Node-Typen ist sehr groß. Dies zeigt, wie komplex die Verarbeitungspipeline des PostgreSQL-Datenbanksystems ist. Durch die Einteilung der Nodes in die Kategorien, bleibt jede Kategorie für sich dennnoch überschaubar. Der eigentliche Verwendungszweck der Nodes wird in den folgenden Unterkapiteln noch näher beschrieben. 3.6.2 Query-Tree Jede Anfrage an das PostgreSQL-Datenbanksystem wird als Text gestellt. Diese Form der Anfrage ist ungeeignet für die weitere Verarbeitung. Aus diesem Grund wird der Text vom „Parser” in den Query-Tree umgesetzt. Dieser repräsentiert die Anfrage vollständig und wird bei den weiteren Verarbeitungsschritten genutzt. Der Tree besteht selbst aus Nodes, welche bereits im Kapitel 3.6.1 vorgestellt wurden. Die Nodes verfügen über Variablen, die vom Typ „Pointer” sein können. Anhand dieser Zeiger lassen sich beliebige Strukturen mit den Nodes aufbauen, u.a. auch Bäume. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 34 SELECT l_orderkey,count(l_partkey) FROM orders,lineitem WHERE o_orderkey = l_orderkey AND o_orderdate < ’1998-06-20’ GROUP BY l_orderkey HAVING count(l_partkey) > 5; Abbildung 3.10: Beispiel-Anfrage Das beste Verständnis der Query-Trees ist anhand eines Beispiels möglich. Im Abbildung 3.10 ist der SQL-Text einer Anfrage dargestellt, welche als Beispiel genutzt werden soll. Die von der Anfage verwendete Datenbank wurde im Kapitel 3.3 näher beschrieben. Das Ergebnis der Anfrage ist die Bestellnummer und Anzahl der Warentypen einer Bestellung. Dabei sind nicht alle Bestellungen der Datenbank interessant. Es werden nur diejenigen ausgelesen, bei denen mehr als fünf Warentypen bestellt wurden und die vor dem 20. Juni 1998 eingegangen sind. Des Weiteren wird nach dem Attribut „l_orderkey” gruppiert, um die Aggregations-Funktion „count” anwenden zu können. Ein Teil dieser Ergebnismenge ist in Abbildung 3.11 dargestellt. Dabei entspricht die Darstellung der Anzeige des PostgreSQL-Terminals. l_orderkey | count ------------+------1 | 6 3 | 6 7 | 7 32 | 6 35 | 6 39 | 6 67 | 6 68 | 7 69 | 6 70 | 6 71 | 6 Abbildung 3.11: Teilmenge des Ergebnisses der Beispiel-Anfrage Der vom „Parser” anhand dieser Anfrage erstellte Query-Tree ist in Abbildung 3.12 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 35 dargestellt. Dabei wurde der Baum aus Darstellungsgründen nach rechts gedreht. Somit befindet sich die Wurzel des Baumes auf der linken und die Blätter auf der rechten Seite. Die Wurzel, Knoten und Blätter des Baumes sind Nodes, welche durch Rechtecke symbolisiert werden. Der Typ des Nodes ist der oberste Eintrag jedes Rechteckes. Die weiteren Einträge sind die Variablen des Nodes, wobei sich jede Variable aus ihrem Namen und dem Wert zusammensetzt. Einige Variablen wurden bei jedem Node weggelassen, denn eine ausführliche Liste der Variablen würde die Abbildung unübersichtlich machen und den Platz einer Seite überschreiten. Außerdem sind nicht alle Nodes des Baumes abgebildet. Der Grund dafür ist derselbe wie bei den Variablen. Dennoch ist der Query-Tree, soweit es zum Verständis beiträgt, vollständig. Variablen vom Typ „Pointer” sind gesondert dargestellt. Ihr Wert steht nicht nach einem Gleichheitszeichen, sondern erscheint in einem separatem Quadrat. Ist der Wert des Zeigers gesetzt, so befindet sich in dem Quadrat ein schwarzer Kreis, welcher der Ausgangspunkt für den Zeiger ist. Der Wert des Zeigers wird durch dem von dort ausgehenden Pfeil symbolisiert. Ist der Wert des Zeigers nicht gesetzt, dies entspricht dem Wert „Null”, so ist in dem Quadrat ein „x” abgebildet. Einige der Nodes sind in einer Liste eingebettet, wie sie in Kapitel 3.5 beschrieben wurden. Aus Gründen der Übersichtlichkeit wurde auf die Darstellung der Listenstruktur verzichtet. Stattdessen haben Nodes, die sich in einer Liste befinden, eine zusätzliche Variable mit dem Namen „next”. Die kursive Schreibweise soll verdeutlichen, dass diese Variable nicht im eigentlichen Sinne zu dem Node gehört. Die Variable „next” ist vom Typ „Pointer” und verweist jeweils auf das nächste Element der Liste. Der Query-Tree in Abbildung 3.12 besteht aus den folgenden Abschnitten: • „Query-Node” Dieser Node befindet sich am linken Rand der Abbildung und ist damit die Wurzel des Baumes. Zur besseren Übersicht ist der Query-Node in Abbildung 3.13 alleine dargestellt. Seine oberste Variable mit dem Namen „commandType” legt den Typ der Anfrage fest. Möglich sind Select-, Insert-, Update-, Delete- und Utility-Anfragen. Die ersten vier Typen sind selbsterklärend. Der fünfte Typ, die Utility-Anfragen, wurden bereits in Kapitel 3.4 erwähnt. Zu diesem Typ gehören alle Anfragen, die keine aufwendige Planung und Ausführung durch das Datenbanksystem benötigen. Solche Anfragen sind u.a. das Anlegen von Tabellen oder die Bereinigung der Datenbank. Im Beispiel hat die Variable „commandType” den Wert 1. Dieser steht für eine Select-Anfrage. Alle anderen Variablen des Query-Node sind Zeiger. Diese verweisen auf Nodes und Listen, welche Detailinformationen zur Anfrage enthalten. • „Rangetable” Der Rangetable enthält alle in der Anfrage verwendeten Tupel-Quellen. Solch eine Quelle kann eine Relation der Datenbank und das Ergebnis einer Unteranfrage, Funktion oder „Joins” sein. Der Rangetable ist eine Liste, auf welche 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 36 Abbildung 3.12: Query-Tree der Beispiel-Anfrage die Variable „rtable” des Query-Nodes verweist. In der Liste existiert für jede verwendete Tupel-Quelle ein Eintrag. Die Daten jedes Eintrages werden in einem separaten RangeTableEntry-Node gespeichert. Dieser hat zwei Variablen. Die erste mit dem Namen „relkind”, gibt die Art der Tupel-Quelle an. Mit der zweiten Variablen wird die datenbankinterne „Id” der Relation angegeben. Aus diesem Grund hat sie den Namen „relid”. Der Wert dieser Variablen ist nur von Bedeutung, wenn die Tupel-Quelle auch eine Relation ist. Der Rangetable der Beispiel-Anfrage ist in Abbildung 3.14 dargestellt. Vom „Parser” wurden zwei Einträge im Rangetable angelegt. Einer entspricht 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 37 Abbildung 3.13: Query-Tree Ausschnitt - Query-Node Abbildung 3.14: Query-Tree Ausschnitt - Rangetable dabei der Tabelle „orders” und einer der Tabelle „lineitem”. Bei beiden RangeTableEntry-Nodes hat die Variable „relkind” den Wert 0, welcher die Tupel-Quellen als Relationen auszeichnet. Dies bedeutet gleichzeitig, dass die „Id” jeder Relationen in der Variablen „relid” abgelegt wurde. Der komplette Rangetable entspricht damit der Umsetzung des Textes „...FROM orders, lineitem...” aus der Beispiel-Anfrage. • „Jointree” Zwei Teile der Anfrage sind im „Jointree” untergebracht. Der erste Teil gibt an, in welcher Reihenfolge die verwendeten Relationen verbunden werden sollen. Wird die Reihenfolge in der Anfrage nicht festgelegt, so ist sie dem Datenbanksystem überlassen. Dieser Fall trifft auch für die Beispiel-Anfrage zu. Der zweite Teil des „Jointrees” sind die Selektionsbedingungen der Anfrage. Diese werden in der Anfrage nach dem „WHERE”-Schlüsselwort angegeben. Der komplette „Jointree” der Beispiel-Anfrage ist in Abbildung 3.15 dargestellt. Dabei ist die Wurzel des „Jointrees” der FromExpr-Node. Auf ihn zeigt die Variable „jointree” des Query-Nodes. Die erste Variable des FromExpr-Nodes hat den Namen „fromlist”. Sie enthält die gerade beschriebene Reihenfolge, in welcher die Relationen verbunden werden sollen. Im Beispiel erfolgt dies durch eine Liste mit RangeTblRef-Nodes. Jeder dieser Nodes hat nur eine Variable. Der Wert dieser Variablen gibt eine Position im Rangetable an. D.h., über diese Variable wird ein Verweis in den Rangetable erzeugt. Auf diese Weise wird festgelegt, welche Tupel-Quellen miteinander verbunden werden sollen. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 38 Abbildung 3.15: Query-Tree Ausschnitt - Jointree Das zweite Attribut des FromExpr-Node verweist auf die Selektionsbedingungen. Es trägt den Namen „quals”. Der Baum der Selektionsbedingungen beginnt mit einem BoolExpr-Node. Dieser gibt die Verknüpfungsart der Bedingungen an. Die Art ist in der Variablen „boolop” enthalten und im Beispiel mit dem Wert „and” belegt. Das bedeutet, dass die einzelnen Bedingungen mit einem logischen „Und” verknüpft werden. Die zu verknüpfenden Bedingungen sind in einer Liste abgelegt, auf welche die Variable „args” verweist. Jeder Eintrag dieser Liste ist ein OpExpr-Node. Dieser legt den Operator fest, der für die Auswertung der Bedingung genutzt werden soll. Die Variable „opno” gibt die „Id” des Operators an. Im ersten OpExpr-Node der Liste ist der Wert der „Id” 96. Hinter dieser „Id” verbirgt sich der Gleichheits-Operator für Integer-Werte. Dies ist der Vergleich „o_orderkey = l_orderkey” der Beispiel-Anfrage. Weitere Details zu den Operatoren sind im Kapitel 3.9 zu finden. Die Variable „args” des OpExpr-Nodes verweist auf weitere Informationen zu der Bedingung. Diese können Konstanten und die Namen der Attribute sein, die zur Bedingung gehören. Der zweite OpExpr-Node hat die „opno” 664. Damit steht er für den Datumsvergleich, also dem Ausdruck „o_orderdate < ’1998-06-20”’ der Beispiel-Anfrage. • „Targetlist” Die gewünschten Attribute der Ergebnis-Tupel werden in der Targetlist festgelegt. Sie entspricht damit dem Select-Teil der Anfrage, also dem Text „SELECT l_orderkey, count(l_partkey)” aus der Beispiel-Anfrage. Auf die Targetlist verweist die Variable „targetList” des Query-Nodes. Die Targetlist der BeispielAnfrage ist in Abbildung 3.16 dargestellt. Jede Targetlist besteht aus einem oder mehreren TargetEntry-Nodes. Dabei steht jeder Node für ein Attribut des SelectTeils. Die Variable „expr” des TargetEntry-Nodes verweist auf die Informationen zu dem entsprechenden Attribut. Mittels der Variable „next” wird auf den nächs2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 39 Abbildung 3.16: Query-Tree Ausschnitt - Targetlist ten Eintrag der Liste verwiesen. Für die Beispiel-Anfrage existieren zwei Einträge in der Targetlist. Dabei ist der zweite der einfachere von beiden und wird deswegen zuerst beschrieben. Dieses Attribut der Targetlist wird mittels eines Var-Nodes dargestellt. Dabei ist die Aufgabe des Var-Nodes lediglich ein Verweis in den Rangetable, welcher bereits beschrieben wurde. Die Variable „varno” des Var-Nodes gibt die Nummer des Eintrages im Rangetable und damit die Tupel-Quelle an. Die zweite Variable „varattno” bezieht sich dann auf diesen Eintrag und benennt die Nummer des Attributes, welches ausgelesen werden soll. Im Beispiel wird das erste Attribut des zweiten Rangetable-Eintrages angegeben. Damit beschreibt dieser Eintrag die Relation „orders” und von dieser das Attribut „l_orderkey”. Der erste Eintrag der Targetlist aus der Beispiel-Anfrage sieht etwas umfangreicher aus. Auch hier ist am Ende der Verweise, ganz rechts, wieder ein Var-Node zu finden. Dieser verweist auf denselben Rangetable-Eintrag, allerdings auf das zweite Attribut. Damit wird das Attribut „l_partkey” der Relation „orders” beschrieben. Vor dem Var-Node befindet sich noch ein AggRef-Node. Dieser beschreibt die Aggregations-Funktion, welche über diesem Attribut erfolgen soll. Die Variable „aggfnoid” gibt dabei die zu verwendende Aggregations-Funktion an, welche im Beispiel „count” ist und mit der „Id” „2147” beschrieben wird. Damit entspricht dieser Eintrag der Targetlist dem Text „count(l_partkey)” aus der Anfrage. • „GroupClause” Die Variable „groupClause” des Query-Nodes verweist auf die „GroupClause”. Diese enthält alle Informationen zu verwendeten Gruppierungen der Anfrage, welche mittels des SQL-Schlüsselwortes „GROUP BY” deklariert wurden. Dabei wird für jede Spalte, nach der gruppiert wird, ein eigener GroupClause-Node angelegt. Dieser umfasst zwei Variablen. Die erste hat den Namen „tleSortGroupRef” und verweist auf die Targetlist. Der Wert der Variablen gibt die Nummer des Targetlist-Eintrages an, nach dem die Gruppierung erfolgt. Die Variable „sortop” legt den Operator fest, welcher für die Durchführung der Gruppierung verwendet werden soll. Die „GroupClause” der Beispiel-Anfrage ist in der Abbildung 3.17 dargestellt. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 40 Abbildung 3.17: Query-Tree Ausschnitt - GroupClause Abbildung 3.18: Query-Tree Ausschnitt - HavingQual Sie besteht aus einem einzigen GroupClause-Node. Der Wert 2 der Variablen „tleSortGroupRef” verweist auf den zweiten Eintrag der Targetlist, welches das Attribut „l_orderkey” ist. Die Variable „sortop” hat den Wert 97, welches die „Id” des Operators ist, der für die Gruppierung verwendet wird. • „HavingQual” Die letzte noch nicht beschriebene Variable des Query-Nodes hat den Namen „havingQual”. Sie zeigt analog zu ihrem Namen auf die „HavingQual” der Anfrage. Diese gibt alle Bedingungen, die hinter dem „SQL-Having-Teil” stehen, an. Die „HavingQual” der Beispiel-Anfrage ist in Abbildung 3.18 dargestellt. Ihre Wurzel ist ein OpExpr-Node, dessen Aufgabe der Vergleich zweier Werte ist. Die Art des Vergleiches wird mittels der Variablen „opno” festgelegt. Ihr Wert 419 steht für „größergleich”. Die Variable „args” zeigt auf eine Liste, welche die zu vergleichenden Operanden enthält. Der erste Operand ist ein AggRefNode zusammen mit einem Var-Node. Diese Kombination wurde bereits bei der Targetlist beschrieben. Im Fall der „HavingQual” stehen diese zwei Nodes für den Text „count(l_partkey)” der Beispiel-Anfrage. Der nächste Node der Liste ist ein Const-Node. Dieser steht für einen konstanten Wert, mit dem verglichen wird. Die Variable „constvalue” enthält dabei den konkreten Wert, welcher 5 ist. Zusammen ergibt sich der Ausdruck „HAVING count(l_partkey) > 5”, welcher durch die „HavingQual” dargestellt wird. Durch die beschriebenen Teile des Query-Trees wird die gesamte Beispiel-Anfrage aus Abbildung 3.10 repräsentiert. Der Query-Tree kann nun genutzt werden, um die Anfrage zu verarbeiten. Trotz der einfach erscheinenden Anfrage ist ein komplexer Query-Tree entstanden. Dabei stellt die Abbildung 3.12 nicht mal den vollständigen 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 41 Baum dar. Dies lässt erahnen, wie verzweigt ein Query-Tree bei hoch komplexen Anfragen werden kann. 3.6.3 Plan-Tree Nach der Transformation der Anfrage in einen Query-Tree wird dieser Baum im PostgreSQL-System weiterverarbeitet. Handelt es sich bei der Anfrage um eine komplexe Anfrage, wird der Query-Tree an den „Planner” übergeben. Die Aufgabe des „Planners” ist die Erstellung des Plan-Trees. Dieser Baum wird für die weitere Verarbeitung der Anfrage benötigt. Der Aufbau und die Funktion eines Plan-Trees soll in diesem Kapitel vorgestellt werden. Dazu wird das gleiche Beispiel wie beim Query-Tree verwendet. Dieses ist in Kapitel 3.6.2 beschrieben. Der „Planner” hat als einzigen Input den Query-Tree. Aus diesem erstellt er den Plan-Tree. Dieser Baum beschreibt die Arbeitsschritte des Datenbanksystems, die notwendig sind, um die Anfrage zu beantworten. Wie der Query-Tree setzt sich auch der Plan-Tree aus Nodes zusammen. Allerdings stammen die verwendeten Nodes aus einer anderen Kategorie (siehe Kapitel 3.6.1). Der „Planner” nutzt Nodes aus der Kategorie Plannodes. Deren Besonderheit sind die Variablen „startup_cost”, „total_cost”, „lefttree” und „righttree”. Die beiden ersten Variablen dienen der Speicherung von errechneten Kosten. Diese Kosten sind eine Schätzung des „Planners” und dienen als Maß für den notwendigen Aufwand, um die Ergebnismenge eines Teilbaumes zu bilden. Die Variablen „lefttree” und „righttree” sind vom Typ „Pointer”. Sie verweisen auf weitere Nodes und ermöglichen so den Aufbau des Plan-Trees. Der Plan-Tree des Beispiels ist in Abbildung 3.19 dargestellt. Er besteht im Einzelnen aus: • „Agg-Node” Der Agg-Node ist die Wurzel des gesamten Plan-Trees. Bei der späteren Verarbeitung durch den „Executor” bedeutet dies, er wird als letzter Node verarbeitet. Der Grund dafür liegt in der rekursiven Verarbeitung des „Executors”. Die Aufgabe des Agg-Nodes ist die Repräsentation einer Aggregation. Dies entspricht dem Text „count(l_partkey)” der Beispiel-Anfrage. Der Agg-Node des Beispiel-Plan-Trees ist in Abbildung 3.20 dargestellt. Die ersten zwei Variablen des Nodes stellen die bereits beschriebene Kostenschätzung für den gesamten Baum dar. Dabei gibt „startup_cost” die Kosten an, welche bei der Verarbeitung mindestens benötigt werden, auch wenn die Ergebnismenge leer ist. Mit „total_cost” werden die Kosten bezeichnet, welche entstehen, wenn alle Tupel der beteiligten Relationen in der Ergebnismenge sind. Die Variable „aggstrategy” legt den verwendeten Gruppierungstyp fest. Dabei ist eine „reine Aggregation” oder „gruppierte Aggregation” möglich. Bei der „gruppierten Aggregation” wird die Aggregation auf die jeweiligen Gruppen angewendet. Liegen die Input-Tupel 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 42 Abbildung 3.19: Plan-Tree der Beispiel-Anfrage des Agg-Nodes nicht sortiert nach dem Gruppierungs-Attribut vor, wird zur Bildung der Gruppen ein Hash benutzt. Ansonsten reicht die Sortierung aus. Der im Beispiel verwendete Wert 1 der Variablen „aggstrategy” steht für „gruppierte Aggregation” mit bereits sortierten Input-Tupeln. Wie bereits beschrieben, wird über die Variablen „lefttree” und „righttree” der Plan-Tree aufgebaut. Diese beiden Variablen zeigen auf weitere Nodes, welche die Input-Tupel für den Node erstellen. Im Fall des Agg-Nodes ist nur eine Menge von Input-Tupel” zulässig. Aus diesem Grund ist die Variable „righttree” nicht gesetzt. Lediglich die Variable „lefttree” verweist auf einen Node. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 43 Abbildung 3.20: Plan-Tree Ausschnitt - Agg-Node Die Variablen des Agg-Nodes reichen nicht aus, um die Funktionalität des Nodes vollständig zu beschreiben. So enthält die Variable „targetlist” einen Zeiger auf einen „Unter-Baum”. Die Nodes des „Unter-Baumes” sind in der Abbildung 3.20 grau dargestellt. Die Targetlist im Plan-Tree hat die gleiche Funktion wie schon im Query-Tree. Diese ist die Beschreibung der Attribute, welche die Ergebnismenge des Nodes bilden. Allerdings ist die Bedeutung der Var-Nodes im Plan-Tree anders. Sie sind zwar auch Referenzen auf Attribute, allerdings verweisen sie nicht auf einen Rangetable wie im Query-Tree. Die Variable „varno” hat bei beiden Var-Nodes den Wert 0, da es nur eine Tupel-Quelle gibt und dies ist der „lefttree”. Die Variable „varattno” enthält die Nummer eines Attributes innerhalb dieser Tupel-Quelle. Eine detaillierte Beschreibung aller Nodes der Targetlist ist in Kapitel 3.6.2 zu finden. • „MergeJoin-Node” Dieser Node steht für einen „MergeJoin”. Dabei werden die Tupel zweier Ergebnismengen zu einer verbunden. Die Beschreibung des „MergeJoin-Algorithmus” würde an dieser Stelle zu weit führen und ist in der Fachliteratur nachzulesen. Voraussetzung für einen „MergeJoin” ist, dass die zu vereinigenden TupelMengen sortiert sind und zwar jeweils nach dem Attribut, das zum „Join” ver2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 44 wendet wird. Dies sind im Beispiel die Attribute „o_orderkey” und „l_orderkey”. Aus den sortierten Input-Tupel und dem Algorithmus des „MergeJoin” resultiert, dass die Ergebnismenge des MergeJoin-Node ebenfalls wieder sortiert ist und zwar nach den beiden Join-Attributen. Damit wird auch im Beispiel die Voraussetzung des Agg-Nodes geschaffen, welche bereits beschrieben wurde. Der MergeJoin-Node ist zusammen mit seiner Targetlist und seinen „Mer- Abbildung 3.21: Plan-Tree Ausschnitt - MergeJoin-Node geclauses” in der Abbildung 3.21 dargestellt. Die ersten zwei Variablen des MergeJoin-Nodes sind die vom „Planner” geschätzten Kosten für den Teilbaum. Mit „lefttree” und „righttree” wird auf die Input-Tupel verwiesen und die Variable „targetlist” zeigt auf die Targetlist des MergeJoin-Nodes. Alle diese Variablen wurden bereits ausführlich beschrieben. Neu an der Targetlist sind lediglich die recht hohen Werte der Variablen „varno” in den Var-Nodes. Deren Bedeutung wird beim Sort-Node noch beschrieben. Durch die Variable „mergeclauses” wird angegeben, wie die zwei Ergebnismengen verknüpft werden. Dafür zeigt diese Variable auf einen Baum, welcher in der Abbildung 3.21 grau dargestellt ist. Oberster Node dieses Baumes ist ein OpExpr-Node. Dieser Node-Typ wurde bereits beim Query-Tree beschrieben. Der OpExpr-Node repräsentiert in diesem Fall die Verknüpfungsart „Gleichheit”. Dabei werden die Attribute, welche durch die Var-Nodes beschrieben werden, auf ihre „Gleichheit” überprüft. Die Var-Nodes verweisen, wie schon beim Agg-Node, auf die Ergebnismengen des „lefttrees” und „righttrees”. Hat die Variable „varno” einen Wert von 65001, steht dieser für den „lefttree”. Das bedeutet im Beispiel, dass der erste Var-Node auf das erste Attribut aller Tupel aus dem „lefttree” verweist. Der zweite Var-Node, mit einem Wert von 65000 der Varia2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 45 blen „varno” verweist auf den „righttree”. Dabei wird ebenfalls das erste Attribut angegeben. • „Sort-Node” Eine Grundvoraussetzung des „MergeJoins” ist die Sortierung der zwei TupelMengen, die verbunden werden sollen. Aus diesem Grund beginnt jeder der Teilbäume des MergeJoin-Nodes („lefttree” und „righttree”) mit einem Sort-Node. Da beide Teilbäume fast identisch sind, wird im Weiteren nur der „lefttree” des MergeJoin-Nodes beschrieben. Der Sort-Node ist in der Abbildung 3.22 dargestellt. Er hat, wie auch schon der Abbildung 3.22: Plan-Tree Ausschnitt - Sort-Node Agg-Node, nur eine Input-Menge von Tupeln. Auf diese verweist die Variable „lefttree”. Die „targetlist” des Nodes erfüllt ebenfalls den bereits beschriebenen Zweck. Aus diesem Grund wurde sie in der Abbildung nur durch drei Punkte angedeutet. Die obersten zwei Variablen des Nodes geben die geschätzten Kosten des Teilbaumes an. Die Variable „sortColIdx” bezieht sich auf alle Input-Tupel. Ihr Wert nennt die Nummer des Attributes, nach dem die Sortierung erfolgen soll. Im Beispiel nach dem ersten Attribut. Dabei wird zur Sortierung der Operator verwendet, der mittels der Variable „sortOperators” angegeben wurde. Der Wert 97 steht für einen Kleiner-Vergleich mit Integer-Werten. Ausführlichere Informationen zu den Operatoren in PostgreSQL sind im Kapitel 3.9.2 zu finden. • „SeqScan-Node” Dieser Node ist als Blatt des Plan-Trees zu verstehen. Demnach wird ihn der „Executor” als erstes verarbeiten. Der SeqScan-Node ist in Abbildung 3.23 dargestellt. Er steht für das vollständige Durchsuchen einer Tabelle. Dies wird als „sequentieller Scan” bezeichnet. Damit ist der Node eine mögliche Ausgangsquelle von Tupeln. Auch dieser Node hat die Variablen „startup_cost”, „total_cost”, „lefttree”, „righttree” und „targetlist”, welche bereits beschrieben wurden. Die verbleibende Variable „qual” gibt die Bedingungen an, nach denen die Tabelle durchsucht wird. Nur die Tupel, die alle Bedingungen erfüllen, sind auch 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 46 Abbildung 3.23: Plan-Tree Ausschnitt - SeqScan-Node in der Ergebnismenge des SeqScan-Nodes. Die Bedingungen werden mittels eines Baumes ausgedrückt, welcher in der Abbildung 3.23 grau dargestellt ist. Oberster Node ist ein OpExpr-Node. Er steht für den Kleiner-Vergleich von „Text”. Der erste zu vergleichende Wert ist ein FuncExpr-Node. Über seine Variable „funcid” wird die Funktion angegeben, welche er ausführen soll. Der Wert von 749 steht für die Umwandlung eines Datums in Text. Das Attribut für die Umwandlung wird durch den Var-Node angegeben, auf den die Variable „args” des FuncExpr-Node verweist. Im Beispiel entspricht dies dem Attribut „o_orderdate”. Der zweite Wert für den Vergleich des OpExpr-Nodes ist der Const-Node. Er enthält nur einen Text. Damit entspricht der ganze Teilbaum dem Ausdruck „o_orderdate < ’1998-06-20”’ der Beispiel-Anfrage. Anhand dieses Plan-Trees ist nun der „Executor” in der Lage, die Ergebnismenge der Anfrage zu bilden. Wie anhand des Beispiels erkennbar war, ist die Struktur des PlanTrees völlig verschieden zum Query-Tree, auch wenn beide die gleiche Anfrage repräsentieren. 3.7 CommandCounter Der „CommandCounter” ist eine wichtige Variable für die Transaktionen. Er wird für jede Transaktion separat angelegt und zu Beginn der Transaktion auf Null gesetzt. Mittels der Funktion „CommandCounterIncrement” kann er systemintern um jeweils einen Zählerschritt erhöht werden. Normalerweise sind von der Transaktion gemachte Änderungen nicht vor ihrem Abschluss sichtbar. Dies macht Änderungen in der Form „x = x + 1” erst möglich. Trotzdem kann es sein, dass Datenbanksystem-Vorgänge eine Einsicht in ihre Änderungen benötigen. Ist dies der Fall, hilft die Funktion „CommandCounterIncrement”. Jede Erhöhung des „CommandCounters” macht die bisherigen Änderungen für die 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 47 Transaktion sichtbar, verbirgt aber kommende. Die Funktion „CommandCounterIncrement” wird in verschiedenen Teilen des PostgreSQL-Datenbanksystems eingesetzt, so auch auch im Index-Advisor. Zum besseren Verständnis wird die Nutzung des „CommandCounters” an einem Beispiel erklärt. Die Transaktion des Beispiels hat die Aufgabe, an einer Relation Änderungen vorzunehmen und danach einen Durchschnittswert zu bilden. Der Ablauf dieser Transaktion ist in Abbildung 3.24 dargestellt. Dabei beginnt die Verarbeitung der Transaktion ganz links. Abbildung 3.24: CommandCounter-Beispiel Die erste Aktion der Transaktion ist die Durchführung eines SQL-Updates. Dabei erhöht die Transaktion bei einem Tupel den Wert des Attributes „att2” um drei. Diese Funktion entspricht damit der Gleichung „x = x + 3”. Diese Änderung bleibt aber für die Transaktion selbst unsichtbar und ist deswegen in der Abbildung 3.24 nur grau unterlegt. Im zweiten Schritt führt die Transaktion ein SQL-Insert durch. Dabei wird ein neues Tupel mit den Werten „rtf” und 52 in die Relation eingefügt. Auch dieses Tupel ist für die Transaktion zunächst nicht sichtbar und deswegen nur grau angedeutet. Der dritte Schritt der Transaktion ist die Bildung eines Mittelwertes über das Attribut „att2”. Die Mittelwertbildung ist jederzeit möglich, allerdings würden die gerade erfolgten Änderungen nicht mit einbezogen werden. Aus diesem Grund muss vorher ein Aufruf von „CommandCounterIncrement” erfolgen. Danach sind der neue Attributwert (90) und das neue Tupel („rtf”,52) für die Transaktion sichtbar. Mittels eines SQL-Select kann nun der korrekte Mittelwert ausgelesen werden, und weitere Transaktionsabschnitte können folgen. Durch den „CommandCounter” kann jede Transaktion selbst entscheiden, ob sie ihre Änderungen sehen möchte oder nicht. Dies erhöht die Möglichkeiten von Transaktionen enorm, macht aber gleichzeitig eine Gliederung in Abschnitte notwendig. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 3.8 48 Disk-Pages Zu jeder Relation in einer PostgreSQL-Datenbank existiert eine eigene Datei. In dieser werden die Tupel der Relation abgelegt. Die Datei wird vom PostgreSQLDatenbanksystem in sogenannte Disk-Pages aufgeteilt. Diese haben eine feste Größe und Struktur. Ihre Aufgabe besteht in der Speicherung von Tupeln. Dabei bieten sie folgende Vorteile: • einfaches Löschen von Tupeln Beim Entfernen eines Tupels aus der Relation und damit aus der zugehörigen Datei, muss nicht die gesamte Datei umgeordnet werden. Die entstandene Lücke wird von der Disk-Page verwaltet und kann für neue Tupel verwendet werden. • gezieltes Auslesen einer Tupelmenge Beim Durchsuchen einer Relation muss nicht die ganze Datei der Relation eingelesen werden. Vielmehr ließt das PostgreSQL-Systems Disk-Page für Disk-Page ein. Wird die gesuchte Information gefunden, kann die Suche sofort beendet werden. Auch ist das Auslesen einer beliebigen Disk-Page möglich. Dies wird angewendet, wenn die Nummer der Disk-Page bereits bekannt ist, zum Beispiel durch einen Index. • einfache Umsortierung von Tupeln Die Disk-Page-Struktur macht es möglich, die Tupel einer Disk-Page zu sortieren, ohne die Tupel-Daten verschieben zu müssen. Bei der Sortierung werden lediglich Zeiger geändert. Diese haben ein viel kleineres Speichervolumen, als die Tupel selbst. Somit kann die Sortierung sehr ressourcen-schonend erfolgen. Die Struktur einer Disk-Page ist in Abbildung 3.25 dargestellt. Jede Disk-Page beginnt mit einem „Pageheader”, dessen Inhalt und Größe fest vorgeschrieben sind. Im „Pageheader” sind Informationen wie die Version der Diskpage oder Offset-Werte innerhalb der Page gespeichert. Weitere Disk-Page-Informationen, für die kein Platz mehr im „Pageheader” ist, werden im „Special Space” abgelegt. Dabei wird die Größe des „Special Spaces” im „Pageheader” festgelegt und ist dadurch dynamisch. Genutzt wird der „Special Space” zum Beispiel von „Indizes”, welche dort Zeiger auf andere DiskPages ablegen. Durch diese Zeiger bauen sie ihre Speicherstrukturen, wie zum Beispiel Bäume, auf. Der „Pageheader” und „Special Space” sind Speicherplatz, der bei jeder DiskPage nicht von Tupel genutzt werden kann. Aus diesem Grund werden diese Daten als „Overhead” bezeichnet und sind in der Abbildung 3.25 weiß dargestellt. An den „Pageheader” schließen sich die „Itempointer” an. Diese wurden in der Abbildung mit „Itemp1”, „Itemp2” und „Itemp3” bezeichnet. Für jedes Tupel in der Disk-Page gibt es einen „Itempointer”. Dieser verweist auf die Speicherposition der Tupel-Daten. Die Verwendung von Zeiger ist notwendig, denn die Größe der Tupel 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 49 ist nicht vorgeschrieben. So kann es sein, dass eine Disk-Page 1000 oder nur 10 Tupel enthält. Eine feste Speicherplatz-Aufteilung für jedes Tupel der Disk-Page ist so nicht möglich. Die „Itempointer” verweisen jeweils auf den Anfang eines Tupels und ermöglichen so beliebige Tupel-Größen. Abbildung 3.25: Struktur einer Disk-Page Ein weiterer Vorteil der „Itempointer” wurde bereits beschrieben. Dies ist die einfache Sortierung von Tupeln. Durch die Änderung der Reihenfolge der „Itempointer” kann eine Disk-Page sortiert werden, ohne dass die eigentlichen Tupel-Daten verschoben werden. Da ein „Itempointer” für jedes Tupel benötigt wird, er selbst aber keine Informationen des Tupels speichert, gehört er zum Tupel-Overhead. Innerhalb einer Disk-Page werden neue „Itempointer” immer nach dem „Pageheader” oder dem letzten „Itempointer” angelegt. In der Abbildung 3.25 bedeutet dies, die „Itempointer” „wachsen” von links nach rechts und von oben nach unten. Die Daten der Tupel hingegen werden genau umgekehrt angelegt. Sie beginnen vor dem „Special Spache” und „wachsen” von rechts nach links und von unten nach oben. Damit „wachsen” die „Itempointer” und Tupel-Daten aufeinander zu. Dies ermöglicht es, viele „Itempointer” mit jeweis kleinen Tupel-Daten, als auch wenige „Itempointer” mit jeweils umfangreichen Tupel-Daten anzulegen. Die Daten der Tupel sind in Abbildung 3.25 mit „Tupel1”, „Tupel2” und „Tupel3” bezeichnet. Ihre Nummerierung korreliert mit denen der „Itempointer”. Zwischen den Tupel-Daten und den „Itempointern” liegt der ungenutzte Bereich der Disk-Page. Erst wenn dieser so gering ist, dass er nicht mehr einen „Itempointer” und die dazugehörigen Tupel-Daten speichern kann, ist die Disk-Page vollständig gefüllt. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 3.9 50 System Catalog Außer den in den Datenbanken gespeicherten Informationen, benötigt das Datenbanksystem zur Anfrageverarbeitung weitere Informationen. Diese werden als Meta-Daten bezeichnet und sind z.B. Informationen über die Tabellen und deren Spalten. Die MetaDaten geben des Weiteren Aufschluss über die angelegten Datenbanken und wie auf diese zugegriffen werden kann. Die Speicherung der Meta-Daten erfolgt in PostgreSQL im sogenannten SystemCatalog. Bei Veränderungen an einer Datenbank wird der System-Catalog automatisch mit angepasst. Dies ist z.B. der Fall beim Anlegen oder Modifizieren einer Tabelle. Des Weiteren besteht die Möglichkeit, direkt auf den System-Catalog zuzugreifen und Änderungen an ihm durchzuführen. Auf diese Weise ist es möglich, PostgreSQL auf einem einfachen Weg um neue Fähigkeiten zu erweitern [11]. In den folgenden Unterkapiteln werden einige Teile des System-Catalogs vorgestellt. Dies sind Teile, welche für den Index-Advisor eine besondere Bedeutung haben und von ihm genutzt werden. 3.9.1 Access Methods Die Access-Methods enthalten alle möglichen Index-Zugriffs-Methoden auf eine Relation, welche das Datenbanksystem beherrscht. Wie bereits erwähnt können die Funktionen des Datenbanksystems erweitert werden. Neue Index-Zugriffs-Methoden müssen in diesen Teil des System-Catalogs eingetragen werden, erst dann können sie genutzt werden. Die Access-Methods werden in der Tabelle mit dem Namen „pg_am” abgelegt. Beispiele für Zugriffs-Methoden sind „R-Tree” oder „B-Tree”. Diese Index-Typen wurden in Kapitel 3.1.3 bereits näher beschrieben. Für jeden Eintrag in „pg_am”, also für jede Index-Zugriffs-Methode, werden die folgenden Informationen gespeichert: • Name Dies ist der Name der Index-Zugriffsmethode. • Zugriffsstrategien Mittels der „Zugriffsstrategien” wird festgelegt, zur Auswertung welcher Operationen ein Index in der Lage ist. Dies können zum Beispiel Vergleichsoperationen wie „größer als” („>”), „gleich („=”) oder „kleiner gleich” („<=”) sein. • Sortierungsstrategien Nicht jeder Index unterstützt einen sortierten Zugriff auf eine Relation. Ob ein Index dies unterstützt und welche Sortierungen dies sind, ist in diesem Eintrag vermerkt. Eine mögliche Sortierung ist zum Beispiel „aufsteigend”. • Indexfunktionen Das Datenbanksystem benötigt für die Nutzung eines Index und damit einer 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 51 Access-Method verschiedene Funktionen. So wird z.B. eine Funktion für den Aufbau des Index benötigt. Die Namen der Funktionen werden in diesen Einträgen abgelegt. • weitere Informationen Zu den bereits beschriebenen Informationen werden noch weitere zu jeder Access-Method abgelegt. Diese sind aber im Rahmen des Index-Advisors nicht von Belang und können in der PostgreSQL-Dokumentation nachgelesen werden [12]. 3.9.2 Operatoren Die Tabelle „pg_operator” enthält die Operatoren des PostgreSQL-Systems. Die Operatoren werden bei jeder Verarbeitung von Attributen benötigt. Das Prinzip der Operatoren ist in Abbildung 3.26 dargestellt. Der Input des Operators sind ein oder zwei Werte, welche mit „Input A” und „Input B” bezeichnet wurden. Auf diese Werte wird der Operator angewendet. Das Resultat ist der mit „Ergebnis” bezeichnete Wert. Beispiele für Operatoren sind die Addition („+”), Subtraktion („-”) und der Vergleich („=”). Abbildung 3.26: Prinzip der PostgreSQL-Operatoren Die Operatoren unterscheiden sich nicht nur durch die Berechnung, die sie durchführen, sondern auch durch die unterstützten Typen. Mit Typen ist die Art der Werte „Input A”, „Input B” und „Ergebnis” gemeint. Solche Typen können z.B. Integer, Float oder String sein. Auch ist es möglich, dass ein Operator überladen ist. Das bedeutet, dieser Operator unterstützt mehrere Typen. Ein Operator wird immer dann genutzt, wenn Attribute verarbeitet werden müssen. Dies kann z.B. bei einer mathematischen Berechnung, der Suche nach einem Wert oder der Negation eines Wertes sein. Der Operator erledigt diese Aufgabe, und das Datenbanksystem arbeitet mit seinem Ergebnis weiter. Zu jedem Operator werden die folgenden Informationen gespeichert: 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 52 • Name Der Name des Operators. • Operator-Art Im Text der Anfrage werden Operatoren direkt verwendet, z.B. durch die Benutzung eines Gleichheitszeichens. Dabei unterscheidet PostgreSQL zwischen drei Operatoren-Arten. Die erste ist „infix”. Bei dieser Art von Operatoren steht der Operator zwischen den zwei Input-Werten, was der Fall bei dem Gleichheitszeichen ist. Die zwei weiteren Arten sind „prefix-” und „postfix-Operatoren”. Beide haben nur einen Input-Wert. Dabei stehen „prefix-Operatoren” vor dem Input-Wert und die „postfix-Operatoren” nach dem Input-Wert. • Typ des Ergebnis Der Typ des Ergebnisses hängt von dem verwendeten Operator ab. Während bei einer Addition i.A. der Ergebnis-Typ mit dem Input-Typ übereinstimmt, ist das Ergebnis bei einem Vergleich meist vom Typ „Boolean”. • Funktion Hinter der „Funktion” verbirgt sich die Art der Berechnung, welche der Operator realisiert. • weitere Informationen Zu den bereits beschriebenen Informationen werden noch weitere zu jedem Operator abgelegt. Diese sind aber im Rahmen des Index-Advisors nicht von Belang und können in der PostgreSQL-Dokumentation nachgelesen werden [13]. 3.9.3 Operator Class Eine Operator-Class gehört zu einer Access-Method und einem Attribut-Typ. Die Operator-Class legt das Verhalten eines Index bei dem angegebenen Attribut-Typ fest. Dies ist an einem Beispiel am verständlichsten. Eine komplexe Zahl besteht aus einem Real- und Imaginär-Teil. Bei PostgreSQL kann ein Attribut als komplexe Zahl definiert sein. Wird dieses Attribut indiziert, so wird eine Operator-Class für komplexe Zahlen benötigt. Die Operator-Class ist dann u.a. für die Auswertung und Sortierung der Attribut-Werte zuständig. Dazu enthält die Operator-Class die Informationen, welche Operatoren und Funktionen für die einzelnen Aufgaben genutzt werden sollen. Für eine Kombination von Attribut-Typ und Access-Method können auch mehrere Operator-Classes definiert sein. Der Unterschied zwischen ihnen liegt dann in dem jeweiligen Verhalten der Operator-Class. Bei einem komplexen Typ z.B. kann zum Vergleich nur der Real- oder Imaginär-Teil des Wertes benutzt werden. Wird beim Anlegen eines Index keine Operator-Class angegeben, so nutzt das Datenbanksystem die „Default Class”. Die Daten jeder Operator-Class werden in der Tabelle „pg_opclass” abgelegt. Jeder Eintrag enthält die folgenden Informationen: 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 53 • Name Der Name der Operator-Class. • Typ Dies ist der Attribut-Typ, den die Operator-Class unterstützt. • Access-Method Dieser Eintrag legt fest, welche Access-Method die Operator-Class unterstützt. • Default Wenn es mehrere Operator-Class für eine Kombination von Attribut-Typ und Access-Method gibt, so muss eine als „Default” deklariert werden. Dies geschieht mit diesem Eintrag. • weitere Informationen Zu den bereits beschriebenen Informationen werden noch weitere zu jeder Operator-Class abgelegt. Diese sind aber im Rahmen des Index-Advisors nicht von Belang und können in der PostgreSQL-Dokumentation nachgelesen werden [14]. 3.10 Run-time Configuration Wie fast jede Software verfügt auch PostgreSQL über eine umfangreiche Konfiguration. Diese wird als „Run-time Configuration” bezeichnet. Sie legt vielfältige Einstellungen des Systems fest. Einige davon sind Konstanten, die Anzeige von DebuggingInformationen oder interne Funktionen wie die Arbeitsweise des „Planners”. Die Parameter der „Run-time Configuration” können vom Typ „boolean”, „integer”, „float point” oder „string” sein. Jeder der Parameter hat einen Default-Wert, welcher zur „normalen” Arbeit des Datenbanksystems genügt. Die Parameter können aber auch angepasst werden, um ein spezielles Verhalten zu erreichen. Zur Änderung der Parameter stehen mehrere Möglichkeiten zur Verfügung. Die erste Möglichkeit ist die Änderung der Datei „postgresql.conf” im Datenverzeichnis. Diese Datei wird bei jedem Start des Datenbanksystems eingelesen und die Parameter entsprechend gesetzt. Ein Ausschnitt der Datei „postgresql.conf” ist in Abbildung 3.27 dargestellt. Die abgebildeten Parameter sind „enable_indexadvisor” und „enable_indexadvisor_terminaloutput”. Sie gehören zum Index-Advisor. Eine Beschreibung der Parameter ist in Kapitel 4.8 zu finden. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL # # # # # # # # 54 ----------------------------PostgreSQL configuration file ----------------------------This file consists of lines of the form: name = value #------------------------------# INDEXADVISOR #------------------------------enable_indexadvisor = true enable_indexadvisor_terminaloutput = false Abbildung 3.27: Ausschnitt der Datei „postgresql.conf” Die nächste Möglichkeit der Parameteränderung ist die Nutzung von „Command line option”. Dabei wird der Datenbank der Wert von einem oder mehreren Parametern übergeben. Dies kann nur zum Start des Datenbanksystems erfolgen und konkurriert zu den Werten in der „postgresql.conf”. Die Regelung dabei ist, dass die Werte der „Command line” die höhere Priorität haben. Ein Beispiel der „Command line”-Nutzung ist in Abbildung 3.28 dargestellt. Dabei wird der Parameter „enable_indexadvisor” auf „false” gesetzt. Abbildung 3.28: Parameter als „Command line option” Die dritte Möglichkeit der Festlegung von Parameterwerten ist die Bindung an 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 55 einen User oder eine Datenbank. Dies geschieht mittels des SQL-Kommandos „ALTER USER” bzw. „ALTER DATABASE”. So kann das Verhalten des Datenbanksystems von Datenbank zu Datenbank und in Abhängigkeit des Benutzers unterschiedlich sein. Die mit einem Nutzer oder einer Datenbank verbundenen Werte überschreiben alle bisher genannten Arten. In Abbildung 3.29 ist das Setzen des Parameters „enable_indexadvisor” auf „false” dargestellt. Dabei wird dieser Wert dem Nutzer mit dem Namen „benutzer” zugewiesen. Abbildung 3.29: Parameter mit Benutzer verbinden Vierte und letzte Art der Parameteränderung ist die Nutzung des „SET”Kommandos. Dieses kann jederzeit in einem Terminal benutzt werden. Es erlaubt die beliebige Manipulation der Parameter. Ein mit „SET” angegebener Wert überschreibt alle anderen Einstellungen. Die Nutzung des „SET”-Kommandos ist in Abbildung 3.30 dargestellt. Auch in diesem Beispiel wird wieder der Parameter „enable_indexadvisor” auf „false” gesetzt. Das Datenbanksystem bestätigt dies mit der Rückmeldung „SET”. 2004-09-30/101/IN99/2254 KAPITEL 3. POSTGRESQL 56 Abbildung 3.30: Parameter mit „SET” setzen Die Auswahl, auf welche Art ein Parameter geändert wird, unterliegt dem Benutzer. Dabei kann nicht bei jedem Parameter jede Möglichkeit angewendet werden. Dies hängt von dem konkreten Parameter und seiner Bedeutung ab. Mittels des Kommandos „SHOW” kann jederzeit der aktuelle Wert eines Parameters ermittelt werden. Die Nutzung des Befehls „SHOW” ist in Abbildung 3.31 dargestellt. Dabei wird der Wert des Parameters „enable_indexadvisor” abgefragt. Die Antwort des Datenbanksystems zeigt, dass der Wert des Parameters „off” ist, welches dem Wert „false” entspricht [15]. Abbildung 3.31: Parameter mit „SHOW” anzeigen 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 57 Kapitel 4 Index-Advisor Die Aufgabe des Index-Advisors ist die Ermittlung von nützlichen Indizes. Dies soll für jede Anfrage erfolgen, welche an das Datenbanksystem gestellt wird. Das bedeutet, der Index-Advisor arbeitet synchron mit dem Datenbanksystem und ist ein Teil der Verarbeitungs-Pipeline. Somit beeinflusst er die Zeit, welche benötigt wird, um eine Anfrage zu beantworten. Nur wenn der „Advisor” nicht zu viel Zeit für seine Aufgabe beansprucht, kann er dem Datenbanksystem mehr Performance bringen. Ansonsten würde der Geschwindigkeitsvorteil durch neu angelegte Indizes, von dem Mehraufwand für den „Advisor” wieder verbraucht werden und das Datenbanksystem sogar langsamer werden. Für eine Anfrage gibt es meist nur eine kleine Anzahl von Indizes, die für sie von Nutzen sind. Selbst wenn die Anfrage viele Attribute enthält, können Indizes nur bei wenigen Attributen angewendet werden. Der Grund dafür wird bei der schrittweisen Betrachtung der Anfrageverarbeitung klar. Die Verarbeitung beginnt immer mit den Tabellen der Datenbank. Aus den Tabellen werden die Tupel ausgelesen, welche für die Anfrage zutreffen. Danach befinden sich die Tupel in temporären Tabellen, wo sie weiterverarbeitet werden. Dieser Ablauf ist in Abbildung 4.1 dargestellt. Je nach Komplexität der Anfrage kann die Verarbeitung der temporären Tabellen viel länger sein als das Auslesen der „normalen” Tabellen. Mögliche Operationen dabei sind z.B. „Joins”, Sortierungen und Selektionen. Bei allen Verarbeitungsschritten können Indizes nur beim Auslesen der „normalen” Tabellen genutzt werden. Denn nur für diese Tabellen wurden Indizes angelegt. Temporäre Tabellen existieren ausschließlich während der Verarbeitung und können höchstens vom Datenbanksystem indiziert werden. Dies ist der Grund, weshalb der Index-Advisor ein Regelwerk benutzt. Dieses reduziert die Anzahl der betrachteten Attribute, indem die Attribute entfernt werden, auf denen kein Index genutzt werden kann. Diese Reduzierung erhöht die Performance des Index-Advisors ohne das Ergebnis zu verändern. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 58 Abbildung 4.1: genutzte Tabellen bei der Anfrageverarbeitung 4.1 Grundlegender Ablauf Der Index-Advisor wird bei jeder komplexen Anfrage aufgerufen. Sein „Input” ist die Anfrage selbst, in Form des Query-Trees. Dieser wurde bereits im Kapitel 3.6.2 beschrieben. Für die Anfrage soll der „Advisor” nützliche Indizes bestimmen und den möglichen Performance-Gewinn dieser Indizes abschätzen. Die dabei durchlaufenen Arbeitsschritte werden im Folgenden kurz beschrieben und sind in der Abbildung 4.2 grafisch dargestellt. Für seine Arbeit nutzt der Index-Advisor den „Planner” des PostgreSQLDatenbanksystems. Der „Planner” wird dabei zweimal aufgerufen. Beim ersten Aufruf ist die Datenbank unverändert. Der erstellte Plan-Tree (siehe Kapitel 3.6.3) wird lediglich als Vergleich benötigt und ist in der Abbildung 4.2 mit „Plan-Tree1” bezeichnet. Im nächsten Schritt ermittelt der Index-Advisor die Index-Kandidaten. Dies ist eine Liste der Attribute, die in der Anfrage genutzt werden und noch nicht indiziert sind. Zur Ermittlung nutzt der „Advisor” den Query-Tree, welcher alle Informationen der Anfrage enthält. Stehen die Index-Kandidaten fest, wird für jedes der Attribute in der Liste ein Index angelegt. Diese Indizes sind allerdings nur „virtuell”, d.h. sie täuschen einen Index vor. Das Anlegen eines „realen” Index würde zu lange dauern und dadurch den Performance-Gewinn des Index-Advisors verbauchen. Nach dem Anlegen der „virtuellen Indizes” erfolgt der zweite Aufruf des „Planners”. Der generierte Plan-Tree ist in der Abbildung 4.2 mit „Plan-Tree2” bezeichnet. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 59 Abbildung 4.2: Arbeitsschritte des Index-Advisors Nach der zweiten Ausführung des „Planners” werden die „virtuellen Indizes” nicht mehr benötigt und wieder aus der Datenbank entfernt. Die weitere Aufgabe des „Advisors” ist, den Plan-Tree nach „virtuellen Indizes” zu durchsuchen. Enthält dieser „virtuelle Indizes”, so bringen diese einen Geschwindigkeitsvorteil für die Anfrage. Der mögliche Performance-Gewinn ist durch die Differenz der zwei Plan-Trees zu berechnen. Zum Schluss speichert der „Advisor” sein Ergebnis in die Datenbank. Einen detailierteren Einblick in die einzelnen Arbeitsschritte des „Advisors” geben die folgenden Unterkapitel. 4.2 Index-Kandidaten-Liste Während der Arbeit des Index-Advisors stellt die Index-Kandidaten-Liste die wichtigste Datenstruktur dar. Sie wird bei vielen Arbeitsschritten ausgelesen oder modifiziert. Somit enthält die Liste den aktuellen Zustand des Index-Advisors. Dies sind im einzelnen Informationen über: • gefundene Index-Kandidaten Dies sind die Attribute der Anfrage. Insbesondere die Attribute, welche noch nicht indiziert sind und dem Regelwerk entsprechen (siehe Kapitel 4.3). • angelegte Index-Kandidaten Dies sind die Attribute, für die ein „virtueller Index” in der Datenbank angelegt wurde. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 60 • genutzte Index-Kandidaten Dies sind die Attribute, welche vom „Planner” als „virtuelle Indizes” genutzt wurden. typedef struct IndexCandidate { Index varno; AttrNumber varattno; Index varlevelsup; Oid vartype; bool converted; Oid reloid; char *colname; Oid idxoid; bool idxused; } IndexCandidate; Abbildung 4.3: Definition des IndexCandidates Die Index-Kandidaten-Liste wird mittels der PostgreSQL-List-package aufgebaut. Diese wurde in Kapitel 3.5 vorgestellt. Die Listenelemente sind vom Typ „IndexCandidate”, welcher eigens dafür implementiert wurde. Sein Aufbau ist in der Abbildung 4.3 dargestellt. Dabei bedeuten die Variablen im einzelnen: • varno Diese Variable verweist auf den Rangetable des Query-Trees und gibt damit eine Relation an. Der Wert der Variablen wird vom „Advisor” nur vorübergehend benötigt und in einem späteren Arbeitsschritt in eine „RelationsId” umgewandelt. • varattno Die Nummer des Attributes zu der Relation von „varno” wird in dieser Variable gespeichert. Auch dieser Wert ist nur ein Zwischenergebnis. Der endgültige Wert ist der Name des Attributes, welcher anhand von „varno” und „varattno” ermittelt werden kann. • varlevelsup Die Werte von „varno” und „varattno” beziehen sich auf einen Rangetable. Von diesem kann es in einem Query-Tree mehrere geben. Zum Beipsiel kann eine Unteranfrage einen eigenen Rangetable haben. Auf welchen Rangetable sich das Attribut bezieht, wird mit „varlevelsup” festgelegt. • vartype Der Typ des Attributes ist in „vartype” abgelegt. Beispiele für Typen sind 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 61 „string”, „float point” und „integer”. Der Typ des Attributes wird vom „Advisor” beim Anlegen der „virtuellen Indizes” benötigt. Nur mit der Kenntnis des Attribut-Types kann die zugehörige Operator-Class (siehe Kapitel 3.9.3) des Indexes ermittelt werden. Das Anlegen des Indexes ist ohne sie nicht möglich. • converted Nachdem die Umwandlung von den Rangetableverweisen („varno” und „varattno”) erfolgt ist, wird diese Variable auf „true” gesetzt. Sie ist damit der Indikator, ob die Umwandlung bereits durchgeführt wurde oder nicht. • reloid Dies ist die „RelationsId”, welche anhand des Wertes von „varno” und dem Rangetable ermittelt wird. Nur die „RelationsId” ist in der Datenbank eindeutig und kann vom Index-Advisor genutzt werden. • colname Hinter „colname” verbirgt sich der Name des Attributes. Dieser wird auch aus dem Rangetable ausgelesen. Dazu werden die Werte der Variablen „varno” und „varattno” benutzt. Die Kombination von „colname” und „reloid” „adressiert” ein Attribut der Datenbank eindeutig. Des Weiteren ist es dem Index-Advisor möglich, anhand der Variable „reloid” zusammen mit „colname”, doppelte Attribute in der Index-Kandidaten-Liste zu finden und danach zu entfernen. • idxoid Beim Erstellen eines „virtuellen Index” wird diesem vom Datenbanksystem eine eindeutige „Id” zugeordnet. Die „Id” wird für alle Manipulationen an dem Index benötigt. Nach dem Anlegen des Indexes speichert der „Advisor” die zugehörige „Id” in die Variable „idxoid”. • idxused Diese Variable stellt das Ergebnis des Index-Advisors dar. Nur wenn der „virtuelle Index” des Attributes vom „Planner” genutzt wurde, hat „idxused” den Wert „true”. Die Variable dient damit zur Erstellung der Advisor-Empfehlung. Die meisten Variablen werden nicht in allen Arbeitsschritten des Index-Advisors benötigt. Viel mehr haben sie nur eine Bedeutung für einen definierten Einsatzbereich oder werden im Laufe der Verarbeitung durch andere ersetzt. 4.3 Regelwerk Die Arbeitszeit des Index-Advisors sollte so kurz wie möglich sein. Je mehr Zeit der „Advisor” benötigt, desto größer wird die Reaktionszeit des Datenbanksystems. Dies 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 62 fällt besonders bei sehr „kleinen” Anfragen auf, für deren Beantwortung nicht viel Zeit benötigt wird. Deshalb ist das Ziel des Regelwerks die Reduzierung der Arbeitszeit des IndexAdvisors. Dies wird mit der Verminderung der Anzahl der Index-Kandidaten erreicht. Dabei soll die Qualität der Empfehlung des „Advisors” gleich bleiben. Eine Verringerung der Index-Kandidaten-Anzahl führt zu weniger Aufwand in allen Arbeitsschritten des „Advisors”. Solch eine Reduzierung ist möglich, da ein Index nur bei einem kleinen Teil der Attribute der Anfrage einen Vorteil bringt. Dieser Aspekt wurde bereits in der Einführung zu Kapitel 4 näher beschrieben. Die Reduzierung der Index-Kandidaten bringt insbesondere einen Vorteil bei folgenden Arbeitsschritten: • Anfrageplanung Wie in Kapitel 4.1 beschrieben wurde, nutzt der Index-Advisor den „Planner” zweimal. Dabei steigt die vom „Planner” benötigte Zeit mit der Anzahl möglicher Ausführungspläne an. Eine große Anzahl „virtueller Indizes” erhöht die Anzahl dieser Pläne stark. Aus diesem Grund sollten die Anzahl der „virtuellen Indizes” und damit die Anzahl der Index-Kandidaten gering gehalten werden. • Anlegen von „virtuellen Indizes” Die Zeit zur Erstellung eines „virtuellen Index” ist nur ein Bruchteil der Zeit die für einen „realen” Index benötigt wird. Trotzdem fällt sie in der Gesamtzeit des Index-Advisors ins Gewicht. Eine Reduzierung der „Kandidaten” führt auch zu einer Verminderung der „virtuellen Indizes”. Das Regelwerk des Index-Advisors besteht aus drei Regeln. Diese werden der Reihenfolge nach abgearbeitet. Erfüllt mindestens ein Attribut eine Regel, so werden die verbleibenden Regeln nicht mehr überprüft. Die Regeln lauten: Ein „virtueller Index” wird für alle Attribute erstellt die... 1. ...sich in der „Where-Klausel” befinden und mit einem der Operatoren „<”, „>”, „<=”, „>=”, „=” oder „LIKE” verknüpft sind. 2. ...sich in der „Group-by-Klausel” befinden. 3. ...sich in der „Order-by-Klausel” befinden. Das Regelwerk ist sehr einfach gehalten. Dies ermöglicht eine schnelle und einfache Überprüfung der Regeln. Des Weiteren steigt der Aufwand mit genaueren Regeln enorm an. Die Schwierigkeit dabei ist, dass die Regeln nicht das Ergebnis des „Advisors” verändern dürfen. Welche Indizes der „Advisor” empfiehlt, hängt direkt von der Entscheidung des „Planners” ab (siehe Kapitel 4.1). Die Regeln müssten sich also sehr am „Planner” orientieren und seine Entscheidungen „vorhersagen”. Dies ist sehr aufwendig und kann bereits durch eine neue PostgreSQL-Version nicht mehr stimmen. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 63 Aus diesem Grund wurden sehr einfache Regeln gewählt. Sie schränken die IndexKandidaten-Anzahl stark ein, ohne dabei sehr vom „Planner” abhängig zu sein. Anhand eines Beispiels soll der Einsatz des Regelwerkes verdeutlicht werden. Die dafür genutzte Anfrage ist in Abbildung 4.4 dargestellt. Zur Ermittlung der IndexKandidaten wird als erstes Regel 1 überprüft. Diese erfüllt keines der Attribute, da es in der Anfrage keine „Where-Klausel” gibt. Es folgt die Überprüfung der zweiten Regel. Mit dieser stimmt das Attribut „l_orderkey” überein und wird als Index-Kandidat aufgenommen. Die dritte Regel kommt nicht zur Anwendung, da bereits eine Regel erfüllt wurde. Damit ist der einzige Index-Kandidat der Anfrage das Attribut „l_orderkey”. SELECT l_orderkey,l_quantity FROM lineitem GROUP BY l_orderkey; Abbildung 4.4: Beispiel-Anfrage2 4.4 Virtuelle Indizes Die Entscheidung, welche Indizes für eine Anfrage von Nutzen sind, werden vom „Planner” getroffen. Das PostgreSQL-Datenbanksystems verfügt aber nur über „reale Indizes”. Das bedeutet: Für die Entscheidung des „Planners” müsste für jedes Attribut der Anfrage ein „realer” Index angelegt werden. Je nach der Komplexität einer Datenbank, kann dies nur Sekunden aber auch Stunden dauern. Solch ein Aufwand ist im Rahmen des Index-Advisors nicht vertretbar und würde die Datenbank nicht performanter machen. Die Lösung ist die Einführung eines neuen Index-Types, den sogenannten „virtuellen Indizes”. Ihre Erstellung dauert nur einen Bruchteil einer Sekunde, unabhängig von der Größe der Datenbank. Der „Planner” macht bei seiner Arbeit keinen Unterschied zwischen den Index-Typen. Somit sind „virtuelle Indizes” ein vollwertiger Ersatz für „reale Indizes” bei der Planungsphase. „Virtuelle Indizes” gewinnen ihren Geschwindigkeitsvorteil aus der Tatsache, dass ihre Index-Datei nicht gefüllt ist. Sie existieren nur im System-Catalog (siehe Kapitel 3.9) von PostgreSQL und werden ausschließlich für den Index-Advisor eingesetzt. Zur Erstellung eines „virtuellen Index” muss zwischen dem SQL-Text „CREATE INDEX” das Schlüsselwort „VIRTUAL” eingefügt werden. Der allgemeine Syntax lautet damit wie folgt: CREATE VIRTUAL INDEX name ON tabelle (spalte); Wie bereits beschrieben, wird ein „virtueller Index” vom „Planner” in seine Planung mit einbezogen. Problematisch daran ist die Tatsache, dass der „Planner” für seine 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 64 Arbeit die Statistiken des Index auswertet. Diese sind die zwei Werte Tupelanzahl und Disk-Pages. Die Tupelanzahl beschreibt, wieviele Tupel der Index indiziert hat. Dieser Wert entspricht bei den „virtuellen Indizes” der Anzahl der Tupel der zugehörigen Relation und ist somit einfach zu ermitteln. Die Disk-Pages (siehe Kapitel 3.8) hingegen sind nicht einfach von der zugehörigen Relation abzuleiten. Mittels der Disk-Pages wird die Größe des Index beschrieben. Die Anzahl der Disk-Pages kann zwar berechnet werden, allerdings bestehen dabei folgende Probleme: 1. Füllung der Disk-Pages Die einzelnen Disk-Pages können unterschiedlich gut gefüllt werden. Dies hängt von der Größe der gespeicherten Tupel ab. Eine zuverlässige Vorhersage der Füllung pro Disk-Page ist nur sehr schwer möglich. 2. Größe des indizierten Attributes Zur Berechnung wird die Größeninformation des Attributes benötigt auf welches sich der Index bezieht. Dabei haben nicht alle Attribute eine bekannte Größe. Diese Tatsache wird im Weiteren noch genauer beschrieben. Von den beiden Punkten kann der erste über einen Durchschnittswert gut kompensiert werden. Die Berechnung ist zwar nicht exakt, reicht im Rahmen des Index-Advisors aber aus. Der zweite Punkt, die Größe des indizierten Attributes, ist abhängig von dem Typ des Attributes. Dazu lassen sich die Typen in vier Kategorien einteilen. Diese sind in der Abbildung 4.5 dargestellt. Diese Kategorien sind im Einzelnen: 1. Fix Die Attribute haben immer die gleiche Größe. Diese ist vom System fest vorgegeben und nicht veränderbar. Attribut-Typen dieser Kategorie sind z.B. „integer” und „date”. 2. Fix zur Laufzeit Attribute dieser Kategorie haben eine festlegbare Größe. Die Wahl der Größe ist aber für alle Tupel gleich und kann nur über spezielle Schlüsselworte wie „ALTER” geändert werden. Ein Attribut-Typ dieser Kategorie ist „char”. 3. Variabel zur Laufzeit Die Größe dieser Attribute ist nur durch ein Maximalwert begrenzt. Die konkrete Größe kann von Tupel zu Tupel unterschiedlich sein und richtet sich nach den Daten, die das Tupel speichert. Ein Attribut-Typ dieser Kategorie ist „varchar”. 4. Unbegrenzt Diese Attribute haben keinerlei Größenangaben. Der von ihnen verwendete Speicher hängt ausschließlich von den gespeicherten Daten ab. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 65 Abbildung 4.5: Spaltentypen und ihre Größe In Abhängigkeit der Kategorie, zu der ein Attribut gehört, schwankt die Qualität der Schätzung der Disk-Pages. Für die Schätzung muss zunächst die Anzahl der Tupel pro Disk-Page ermittelt werden. Die dazu benutzen Formeln lauten wie folgt: • Kategorie 1 und 2 Attribute aus beiden Kategorien können identisch behandelt werden. Bei beiden ist die Größe zur Laufzeit bekannt und kann vom „Advisor” leicht ermittelt werden. Die Berechung der Tupel pro Disk-Page erfolgt dabei nach der in Abbildung 4.6 dargestellten Formel. Dabei wird eine Abschätzung erreicht, die sehr gut ist und zu 90-100% einem „realen” Index entspricht. T uplesP erP age = P ageSize − P ageOverhead T upleSize + T upleOverhead Abbildung 4.6: Formel - Kategorie 1 und 2 • Kategorie 3 Die Größe der Attribute dieser Kategorie ist nur durch einen Maximalwert begrenzt. Aus diesem Grund sind die Formeln für die Schätzung der Tupel pro Disk-Pages etwas anders. Alle Formeln sind in Abbildung 4.7 ersichtlich. Die Qualität der Abschätzung kann als durchschnittlich eingestuft werden. Sie hängt sehr stark von den Daten der Tupel ab. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 66 V arT upleSize = T upleSize ∗ T upleF ill T uplesP erP age = P ageSize − P ageOverhead V arT upleSize + T upleOverhead Abbildung 4.7: Formeln - Kategorie 3 • Kategorie 4 Eine Abschätzung der Tupel pro Disk-Pages für die Attribute dieser Kategorie ist sehr schwer. Die Abschätzung wird umso ungenauer, je mehr Attribute der Kategorie 3 und 4 in einer Relation sind. Um trotz der unbekannten Größe eine Abschätzung machen zu können, wird für die Berechnung die Anzahl der Disk-Pages der indizierten Relation benutzt. Zusammen mit der Anzahl der Attribute der Kategorie 4 ergeben sich die in Abbildung 4.8 dargestellten Formeln. Eine Aussage über die erreichte Qualität der Abschätzung ist nicht möglich. Sie hängt völlig von den Daten der Tupeln ab. RelationSize = RelationDiskP ages ∗ P ageSize RelationT upleF ixSize = SumT upleF ixSize ∗ RelationT uples V arT upleSize = RelationSize − RelationT upleF ixSize ÷ RelationT uples CountT upleV arSize T uplesP erP age = P ageSize − P ageOverhead V arT upleSize + T upleOverhead Abbildung 4.8: Formeln - Kategorie 4 Die in den Formeln verwendeten Variablen bedeuten im Einzelnen: • PageSize Die Größe einer „Disk-Page” in Byte. Dies ist ein vom Datenbanksystem vorgegebener Wert. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 67 • PageOverhead Der Overhead pro „Disk-Page” in Byte. Er setzt sich aus der Größe des „Pageheaders” und des „Special Spaces” zusammen. • TupleSize Die Größe eines Tupels in Byte, wobei das Attrbribut zu der Kategorie 1 oder 2 gehört. • VarTupleSize Die Größe eines Tupels in Byte, wobei das Attrbribut zu der Kategorie 3 oder 4 gehört. • TupleOverhead Der Overhead pro Tupel in Byte. Dieser besteht aus dem „Tupelheader”, „Indexheader” und „Itempointer” pro Tupel. • RelationTuples Die Anzahl der Tupel in der indizierten Relation. • TupleFill Der Faktor, der angibt, wie gut das Attribut durchschnittlich gefüllt ist. Dabei wurde 0,75 verwendet. • RelationDiskPages Die Anzahl der „Disk-Pages” der indizierten Relation. • SumTupleFixSize Die Summe der Größe aller Attribute der Kategorien 1, 2, 3 in der indizierten Relation. • CountTupleVarSize Die Anzahl der Attribute der Kategorie 4 in der indizierten Relation. • TuplesPerPage Die errechnete Anzahl der Tupel pro Disk-Page. • RelationSize Die Größe einer Relation in Bytes. • RelationTupleFixSize Die Summe der Größe aller Attribute der Kategorie 1 und 2. Mit der Schätzung der Tupel pro Disk-Page kann die Größe des Index berechnet werden. Die dazu genutzte Formel ist für alle Kategorien gleich und in Abbildung 4.9 dargestellt. Sie berechnet die Index-Größe in Disk-Pages. Die verwendeten Variablen werden im Folgenden beschrieben: 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 68 DiskP ages = RelationT uples ÷ P ageF ill T uplesP erP age Abbildung 4.9: Formel - Index-Größe • PageFill Der Faktor, mit dem durchschnittlich eine Indexseite gefüllt wird. Er beträgt 0,92. • RelationTuples Die Anzahl der Tupel in der indizierten Relation. • TuplesPerPage Die errechnete Anzahl der Tupel pro Disk-Page. Die Qualität der Abschätzung der Index-Größe hängt stark von der Kategorie des Attributes ab. Die Kategorien 1 und 2 liefern dabei die besten Ergebnisse. Die Abschätzung für Kategorie 3 und 4 liefern nur ungenaue Ergebnisse, sind aber in Anbetracht der schnellen Berechnung akzeptabel. Vorteilhaft ist, dass Attribute der Kategorie 1 und 2 am häufigsten in Datenbanken vorkommen und in Anfragen genutzt werden. Dies bedeutet, sie werden auch am meisten indiziert und stellen die Hauptaufgabe für den Index-Advisor dar. 4.5 Systemtabelle pg_indexadvisor Wie bereits im Kapitel 3.9 beschrieben, verfügt PostgreSQL über einen SystemCatalog. In diesem werden die Meta-Daten des Datenbanksystems abgelegt. Für den Index-Advisor wurde der System-Catalog um eine Relation erweitert. Entsprechend ihrer Zugehörigkeit, trägt die Relation den Namen „pg_indexadvisor”. Als Systemtabelle wird sie vom Datenbanksystem automatisch angelegt. Aufgabe der Relation ist die Speicherung der Ergebnisse des Index-Advisors. Dies sind die ermittelten nützlichen Index-Kandidaten. Für diesen Zweck hat die Relation drei Attribute, welche im Folgenden beschrieben werden: • „advisetable” Dieses Attribut ist vom Typ „Oid”. Damit wird die Relation angegeben, in welcher sich das Attribut eines Index-Kandidaten befindet. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 69 • „adviseattr” Das Attribut „adviseattr” ist vom Typ Integer. Mit der enthaltenen Zahl wird ein Attribut der Relation „advisetable” referenziert. • „adviseprofit” Der mögliche Performancegewinn durch die Indizierung von „adviseattr” ist in diesem Attribut abgelegt. Er wird, wie auch die Kosten einer Anfrage, in DiskPages angegeben. In der Abbildung 4.10 ist die Tabelle „pg_indexadvisor” beispielhaft dargestellt. Insgesamt sind fünf Einträge vorhanden. Diese können aus der Analyse einer einzigen oder aus der Analyse verschiedener Anfragen stammen. Mehrere Einträge mit derselben Kombination von „advisetable” und „adviseattr” sind üblich. Der Grund dafür ist, dass der „Advisor” den „adviseprofit” nicht summiert. Jede Anfrage wird einzeln betrachtet. Kommt eine Anfrage sehr häufig vor, so werden ihre Index-Kandidaten oft in der Tabelle „pg_indexadvisor” stehen. Abbildung 4.10: Systemtabelle „pg_indexadvisor” 4.6 Ablauf Bei jeder komplexen Anfrage wird der Index-Advisor vom Datenbanksystem aufgerufen. Er führt dann einen festen Ablauf von Arbeitsschritten durch und speichert sein Ergebnis in die Datenbank. Die einzelnen Arbeitsschritte und ihr zeitlicher Ablauf werden in diesem Unterkapitel beschrieben. Eine Übersicht des Index-Advisor-Ablaufes gibt die Abbildung 4.11. Dabei ist der Ablauf in einzelne Funktionsblöcke gegliedert. Diese Blöcke stellen jeweils einen Arbeitsschritt dar und sind zur besseren Beschreibung nummeriert. Der Ablauf in jedem Funktionsblock ist mit Pseudo-Code dargestellt. Dies soll die einzelnen Arbeitsschritte verständlicher machen, ohne verwirrende Details des eigentlichen Quellcodes. Die Blöcke werden im Anschluss noch genauer beschrieben. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 70 Abbildung 4.11: Ablauf des Index-Advisors 4.6.1 Input Der oberste Block ist kein Funktionsblock und ist deshalb nur gestrichelt umrandet. Er stellt die Definition der Index-Advisor-Funktion dar. Anhand dieser ist ersichtlich, welche Eingabewerte der „Advisor” vom Datenbanksystem erhält. Der Input-Block ist in Abbildung 4.12 nochmals dargestellt. Der einzige Input des Index-Advisors ist ein Zeiger auf den Query-Tree. Dieser repräsentiert die Anfrage und ist damit die Basis für die Arbeit des „Advisors”. Der genaue Aufbau und die Funktion des Query-Trees wurde bereits in Kapitel 3.6.2 beschrieben. Abbildung 4.12: Ablauf des Index-Advisors - Input 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 4.6.2 71 Ausführung ohne virtuelle Indizes Um einen Performancegewinn ermitteln zu können, benötigt der „Advisor” einen Vergleichswert. Dieser wird im ersten Arbeitsschritt gebildet. In der Abbildung 4.13 ist der zugehörige Funktionsblock abgebildet. Als erster Arbeitsschritt trägt er auch die Nummer „1”. Zur Ermittlung des Vergleichswertes ruft der Index-Advisor den Planner auf. Dieser benötigt als Input den Query-Tree der Anfrage. Während der Arbeit des Planners liest dieser nicht nur den Query-Tree aus, sondern modifiziert ihn auch. Dies dient der Optimierung der Anfrage, verändert aber den Query-Tree unwiederbringlich. Mit solch einem modifizierten Query-Tree ist keine erneute Anfrageplanung möglich. Aus diesem Grund wird von dem Query-Tree eine Kopie angefertigt. Dies erfolgt in der ersten Zeile des Funktionsblockes. Die dazu genutzte Funktion „copyObject” wird von PostgreSQL bereitgestellt. Sie macht von der Baumstruktur des Query-Trees eine Tiefenkopie. Mit der erstellten Tiefenkopie wird in der zweiten Zeile der Planner aufgerufen. Seine Modifikationen erfolgen nun ausschließlich an der Kopie. Das Ergebnis des Planners ist ein Plan-Tree, welcher in der Variable „plan” gespeichert wird. In der dritten und letzten Zeile des Funktionsblockes werden die vom Planner geschätzten Kosten ausgelesen. Die Variable „orgCost” speichert sie bis zum Vergleich mit den optimierten Kosten. Abbildung 4.13: Ablauf des Index-Advisors - Ausführung ohne virtuelle Indizes 4.6.3 Index-Kandidaten Für alle weiteren Arbeitsschritte benötigt der Index-Advisor die so genannten IndexKandidaten. Dies sind alle Attribute der Anfrage, deren Indizierung einen Performancegewinn bringen können. Die Ermittlung der Index-Kandidaten erfolgt im zweiten Funktionsblock. Dieser ist in der Abbildung 4.14 dargestellt. Zur Bestimmung der Index-Kandidaten wird das in Kapitel 4.3 vorgestellte Regelwerk benutzt. Dabei überprüfen die Regeln u.a. die Operatoren zwischen den Attributen. Solche sind z.B. „=”, „<=” oder „>”. Im Query-Tree werden die Operatoren allerdings nicht in dieser Form gespeichert. Der Parser konvertiert die textuelle Form der Operatoren in Oid’s. Dies sind Zahlen, welche jeden Operator eindeutig identifizieren. Somit führt die erste Zeile des Funktionsblockes das Auslesen der Operator-Oid’s aus. Dazu wird die dafür erstellte Funktion „getOpnos” aufgerufen. Sie liest aus dem 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 72 Abbildung 4.14: Ablauf des Index-Advisors - Index-Kandidaten System-Catalog alle Operator-Oid’s aus, die der B-Tree (siehe Kapitel 3.1.3) unterstützt. In der Variable „opnos” werden diese für ihre Verwendung gespeichert. Die zweite Zeile des Funktionsblockes führt die Ermittlung der Index-Kandidaten durch. Dies geschieht in der Funktion „scanQuery”. Sie durchläuft den Query-Tree und überprüft gleichzeitig die Regeln des Regelwerkes. Als Parameter benötigt „scanQuery” den Query-Tree und die bereits beschriebenen Operator-Oid’s. Das Ergebnis ist die Index-Kandidaten-Liste (siehe Kapitel 4.2), welche in der Variable „candidates” abgelegt wird. Nicht alle gefundenen Index-Kandidaten können vom „Advisor” genutzt werden. Die Funktion „cleanup” entfernt die nicht nutzbaren Kandidaten. Dieser Arbeitsschritt erfolgt in der dritten Zeile. Entfernt werden dabei folgende Index-Kandidaten: • doppelte Kandidaten Ein Attribut kann mehrfach in einer Anfrage auftauchen. Dadurch ist es auch möglich, dass dieses Attribut mehrmals in der Index-Kandidaten-Liste steht. Solche Einträge werden entfernt, damit jedes Attribut maximal einmal in der Liste vorkommt. • bereits indizierte Kandidaten Im Query-Tree ist nicht erkennbar, ob ein Attribut bereits indiziert ist. Dies muss für jeden Index-Kandidaten überprüft werden. Ist bereits ein Index vorhanden, wird der Kandidat aus der Liste entfernt, denn eine doppelte Indizierung macht keinen Sinn. Für die Überprüfung werden alle Attribute der Datenbank ermittelt, die bereits indiziert sind. Dabei werden allerdings nur die Relationen betrachtet die auch in der Kandidaten-Liste vorkommen. Beide Listen, also die der bereits indizierten Attribute und die der Index-Kandidaten werden verglichen. Gibt es Übereinstimmungen, so werden die entsprechenden Kandidaten entfernt. • Kandidaten von Systemrelationen Das Indizieren von Attributen aus dem System-Catalog kann nicht zur Laufzeit erfolgen. Sind solche Attribute unter den Index-Kandidaten, müssen sie entfernt werden. Diese Überprüfung erfolgt mittels der PostgreSQL-Funktion „IsSystemRelation”. • Kandidaten von temporären Tabellen Während der Anfrageverarbeitung können temporäre Tabellen erstellt werden. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 73 Diese enthalten Zwischenergebnisse und werden nach Abschluss der Verarbeitung wieder gelöscht. Bei temporären Tabellen gilt ähnliches wie bei den Systemrelationen. Auch sie dürfen nicht indiziert werden. Ein Teil einer Anfrage kann als temporäre Tabelle vorliegen. Dies können z.B. die Tupel einer Unteranfrage sein. Dadurch ist es möglich, dass die IndexKandidaten-Liste Attribute von temporären Tabellen enthält. Auch diese Kandidaten müssen entfernt werden. Die Überprüfung erfolgt anhand der Parameter einer Tabelle. Ist der Wert des Parameters „rd_istemp” gleich true, so handelt es sich um eine temporäre Tabelle und der zugehörige Kandidat wird entfernt. Nach dem Aufruf von „cleanup” sind nur noch „gültige” Einträge in der IndexKandidaten-Liste. Diese können in den weiteren Arbeitsschritten des Index-Advisors auf ihren Nutzen überprüft werden. 4.6.4 Anlegen der virtuellen Indizes Der dritte Funktionsblock ist für das Anlegen der „virtuellen Indizes” zuständig. Dabei wird für jeden Index-Kandidaten ein „virtueller Index” angelegt. Dies geschieht in der ersten Zeile des Funktionsblockes und wird durch die Funktion „createVirtualIndizes” symbolisiert. Die Bedeutung und Funktionsweise der „virtuellen Indizes” wurde bereits im Kapitel 4.4 beschrieben. In der zweiten Zeile erfolgt ein Aufruf der Funktion „CommandCounterIncrement”. Mit dieser werden die erstellten Indizes „sichtbar” gemacht. Ohne den Aufruf würde dies erst mit dem Abschluss der Anfrageverarbeitung erfolgen. Der Grund ist, dass jede Anfrage in einer eigenen Transaktion verarbeitet wird. Ohne den Aufruf von „CommandCounterIncrement” würden die „virtuellen Indizes” in dem folgenden Arbeitsschritt vom Planner nicht genutzt werden. Eine ausführliche Beschreibung des „CommandCounters” ist in Kapitel 3.7 zu finden. Abbildung 4.15: Ablauf des Index-Advisors - Anlegen der virtuellen Indizes 4.6.5 Ausführung mit virtuellen Indizes Nachdem die „virtuellen Indizes” angelegt wurden, wird in diesem Arbeitsschritt der mögliche Performancegewinn ermittelt. Dazu wird erneut der Planner genutzt. Alle dafür notwendigen Aufrufe sind in der Abbildung 4.16 dargestellt. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 74 In der ersten Zeile erfolgt ein Aufruf der Funktion „copyObject”. Ihre Aufgabe wurde bereits im ersten Funktionsblock beschrieben. Eine weitere Kopie des QueryTrees ist notwendig, da der Planner nochmals aufgerufen wird. Zwar wird der QueryTree danach vom „Advisor” nicht mehr benötigt, allerdings hat der Index-Advisor nur einen Zeiger auf den Query-Tree bekommen. Eine Modifikation an diesem „Original” bedeutet, der Query-Tree wird unwiederbringlich zerstört. Eine Weiterverarbeitung der Anfrage in der PostgreSQL-Pipeline wäre nicht mehr möglich. Wie schon im ersten Funktionsblock, wird der Planner mit der Kopie des QueryTrees aufgerufen. Dies geschieht in der zweiten Zeile. Der Planner erstellt nun für die Anfrage einen Ausführungsplan. Im Gegensatz zu seinem ersten Aufruf stehen ihm mehr Möglichkeiten zur Verfügung. Er kann die „virtuellen Indizes” in seinem PlanTree nutzen. In der Variable „plan” wird dieses Ergebnis gespeichert. Die dritte Zeile des Funktionsblockes dient der Ermittlung des Performancegewinns. Dazu werden mit „getCost” die Kosten des gerade erstellten „Plan-Trees” ausgelesen. Die Differenz dieser Kosten zu den Kosten des ersten Planner-Aufrufes ergeben den Gewinn. Dieser wird in der Variable „savedCost” gespeichert. Der letzte Schritt des Funktiosblockes ist die Ermittlung der „virtuellen Indizes”, welche vom Planner genutzt wurden. Dies erfolgt in der Funktion „scanPlan”. Wie bereits aus dem Namen ableitbar ist, durchläuft sie den erstellten Plan-Tree. Dies ist auch ihr erster Parameter. In dem Plan-Tree können mehrere Indizes vorkommen. Dabei ist keine Unterscheidungsmöglichkeit zwischen „realen” und „virtuellen Indizes” enthalten. Dies ist der Grund für den zweiten Parameter der Funktion, welche die Index-Kandidaten-Liste ist. Ein Vergleich der Kandidaten-Liste mit allen Indizes im Plan-Tree ermöglicht das Finden der „virtuellen Indizes”, sofern vom Planner einige genutzt wurden. Die Information, welcher der „virtuellen Indizes” genutzt wurde, wird in der Variable „used” abgelegt. Abbildung 4.16: Ablauf des Index-Advisors - Ausführung mit virtuellen Indizes 4.6.6 Entfernen der virtuellen Indizes Durch alle bisher beschriebenen Arbeitsschritte liegt das Ergebnis des „Advisors” bereits vor. Dies sind die genutzten „virtuellen Indizes” und die gesparten Kosten. Die Aufgabe des fünften und damit vorletzten Funktionsblockes ist das Entfernen der „virtuellen Indizes”. Der Funktionsblock ist in Abbildung 4.17 dargestellt. Die Funktion „dropVirtualIndizes” in der ersten Zeile entfernt alle angelegten „virtuellen Indizes”. Ihr einziger Parameter ist die Index-Kandidaten-Liste. Diese durch2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 75 läuft die Funktion und entfernt den „virtuellen Index” zu jedem Kandidaten. In der zweiten Zeile schließt sich der Aufruf der Funktion „CommandCounterIncrement” an. Dieser Aufruf soll die Entfernung der Indizes „sichtbar” machen. Ohne der Erhöhung des „CommandCounters” ständen die „virtuellen Indizes” noch bis zum Abschluss der Anfrageverarbeitung zur Verfügung. Erscheinen sie im Plan-Tree, so werden sie auch vom Executor genutzt. Aber für solch einen Einsatz sind die „virtuellen Indizes” nicht vorgesehen. Abbildung 4.17: Ablauf des Index-Advisors - Entfernen der virtuellen Indizes 4.6.7 Speicherung des Ergebnisses Der letzte Arbeitsschritt des Index-Advisors ist die Speicherung des Ergebnisses. Der dazugehörige Funktionsblock ist in der Abbildung 4.18 dargestellt. Dabei führt die Funktion „insert” die Speicherung durch. Sie ist in der ersten Zeile zu finden. Der erste Parameter der Funktion ist der Name der Tabelle, in welche das Ergebnis gespeichert werden soll. Für den Index-Advisor lautet dieser „pg_indexadvisor”. Diese Tabelle ist ein Teil des System-Catalogs und wurde in Kapitel 4.5 vorgestellt. In die Tabelle „pg_indexadvisor” fügt die Funktion einen Eintrag für jeden IndexKandidaten ein, dessen „virtueller Index” vom Planner genutzt wurde. Die entsprechenden Kandidaten werden mittels der Variable „used” übergeben. Mit dem dritten Parameter werden die gesparten Kosten und damit der Gewinn der Funktion mitgeteilt. Der Gewinn wird durch die Anzahl der genutzten IndexKandidaten geteilt und zu jedem Kandidaten abgespeichert. Die entstandenen Einträge in der Tabelle „pg_indexadvisor” können von weiteren Routinen oder Applikation ausgewertet werden, wie z.B. vom QUIET-System (siehe Kapitel 2.4). Dabei müssen Statistiken über bestehende Indizes von diesen Systemen gebildet werden. Der Index-Advisor betrachtet nur nicht vorhandene Indizes. In der zweiten Zeile des Funktionsblockes wird der Index-Advisor beendet. Abbildung 4.18: Ablauf des Index-Advisors - Ergebnis 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 4.7 76 Implementierung Bei der Implementierung des „Index-Advisors” wurden verschiedene Dateien modifiziert und hinzugefügt. Die dabei erfolgten Änderungen werden in diesem Kapitel beschrieben. Dadurch soll ein Überblick der Implementierung und dessen Verteilung über die Quell-Dateien gegeben werden. Die erfolgten Änderungen sind im Einzelnen: • virtuelle Indizes Bei der Verarbeitung von Indizes werden im Wesentlichen zwei Datenstrukturen eingesetzt. Dies ist erstens der „IndexInfo-Node” (siehe Datei „include/nodes/execnodes.h”) und zweitens der „IndexStmt-Node” (siehe Datei „include/nodes/parsenodes.h”). Beide Datenstrukturen wurden um eine Variable mit dem Namen „virtual” erweitert. Diese ist vom Typ „boolean” und gibt an, ob der Index als virtuell angelegt werden soll. In der Datei „backend/parser/gram.y” sind alle Regeln enthalten, nach denen eine Anfrage geparst wird. Diese Datei wurde um das Schlüsselwort „VIRTUAL” und dessen Auswertung erweitert. Zusätzlich musste in „backend/parser/keywords.c” das Schlüsselwort „VIRTUAL” eingetragen werden. Erst dadurch wird es vom System als solches erkannt. Mit den modifizierten Datenstrukturen („IndexInfo-Node”, „IndexStmt-Node”) arbeiten Funktionen. Sie müssen ebenfalls angepasst werden. Die erste Funktion trägt den Namen „DefineIndex”. Sie ist in der Datei „backend/commands/indexcmds.c” abgelegt und wird bei jeder Eingabe von „CREATE INDEX” aufgerufen. Die Funktion „DefineIndex” wurde so angepasst, dass auch sie mit der Variable „virtual” korrekt umgeht. Die eigentliche Erstellung des Index erfolgt durch die Funktion „index_create”. Diese ist in der Datei „backend/catalog/index.c” zu finden. Auch diese Funktion wurde für den Umgang mit „virtual” angepasst. Des Weiteren befindet sich in der Datei „index.c” die Implementierung der virtuellen Indizes. Dafür wurden zwei Hilfsfunktionen mit den Namen „get_attrType” und „estimate_indexpages” erstellt. Sie dienen dazu, die Kategorie eines Attributes zu bestimmen (siehe Kapitel 4.4) und die Diskpages des virtuellen Index abzuschätzen. Innerhalb der Funktion „index_create” wird die Variable „virtual” ausgewertet. Ist sie auf „true” gesetzt, so wird der Index nicht gefüllt und seine Größe geschätzt. Dadurch entsteht ein virtueller Index. Die Datei „backend/bootstrap/bootparse.y” und „backend/tcop/utility.c” wurden ebenfalls für die virtuellen Indizes modifiziert. In ihnen erfolgen Aufrufe der Funktion „DefineIndex”. Diese mussten um den Parameter „virtual” erweitert werden. • Index-Advisor Der Index-Advisor ist in der Datei „backend/indexadvisor/indexadvisor.c” abgelegt. Dort sind alle Funktionen zu finden, welche in Kapitel 4.6 beschrieben 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 77 wurden. Mittels der Funktion „indexadvisor” wird er aufgerufen. Dieser Aufruf findet in der Datei „backend/tcop/postgres.c” statt. Diese enthält die Schritte der Verarbeitungs-Pipeline und wurde um den Aufruf des Index-Advisors erweitert. • pg_indexadvisor Die Definition der Relation „pg_indexadvisor” (siehe Kapitel 4.5) erfolgt in der Datei „include/catalog/pg_indexavisor.h”. Diese wurde eigens dafür angelegt. Die Datei „include/catalog/catname.h” wurde um die Definition des Namens der Systemtabelle erweitert. Für das Einbinden und Anlegen der Systemtabelle ist lediglich ein Eintrag im „Make-File” notwendig. Die restlichen Abläufe erfolgen automatisch. • Konfiguration Für die „Run-time Configuration” des Index-Advisors (siehe Kapitel 4.8) wurden die Dateien „backend/utils/misc/guc.c” und „include/utils/guc_tables.h” modifiziert. Die erste Datei wurde um die Parameter erweitert, welche den Index-Advisor steuern. Die zweite Datei definiert die Systemtabelle, in welcher die Konfigurationswerte abgelegt werden. Auch diese wurde für den IndexAdvisor erweitert. 4.8 Advisor-Configuration Nicht in jedem Fall kann es wünschenswert sein, dass der Index-Advisor aktiv ist. Jedoch als fester Teil der Verarbeitungspipeline wird er immer in die Anfrageverarbeitung einbezogen. Wie bereits im Kapitel 3.10 vorgestellt wurde, verfügt PostgreSQL über eine „Runtime Configuration”. Diese erlaubt eine vielfältige Konfiguration des Datenbanksystems. Auch die Steuerung des Index-Advisors gehört dazu. Dieser ist über folgende zwei Parameter konfigurierbar: • „enable_indexadvisor” Dieser Parameter aktiviert den Index-Advisor. Mit dem Parameter kann entschieden werden, ob der „Advisor” in die Anfrageverarbeitung einbezogen wird. Hat der Parameter den Wert „true”, so werden für jede Anfrage nützliche Indizes ermittelt und das Ergebnis in der Datenbank abgespeichert. Mit dem Wert „false” wird der „Advisor” vom Datenbanksystem nicht aufgerufen. Der Parameter kann mit allen Möglichkeiten der „Run-time Configuration” gesetzt werden. So auch mittels des Kommandos „SET”. Dies ermöglicht es z.B. den „Advisor” vor einer Anfrage zu aktivieren und sofort danach zu deaktivieren. So kann der Index-Advisor nur für einen Teil von Anfragen genutzt werden. • „enable_indexadvisor_terminaloutput” Wenn der „Advisor” aktiviert ist, kann mittels dieses Parameters festgelegt werden, ob er sein Ergebnis grafisch darstellt. Diese Ausgabe beinhaltet eine Liste 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 78 aller Attribute, deren Indizierung von Vorteil ist, des Weiteren die Kosten der Anfrage ohne und mit diesen Indizes. Zur schnelleren Einschätzung des Ergebnisses berechnet der „Advisor” die Differenz zwischen diesen Kosten als Zahlenund prozentualen Wert. Die letzte ausgegebene Information ist die Zeit, welche der „Advisor” für alle seine Arbeitsschritte benötigt hat. Auch der Parameter „enable_indexadvisor_terminaloutput” kann mit allen Möglichkeiten der „Runtime Configuration” geändert werden. INFO: INFO: INFO: INFO: INFO: INFO: index on „ps_partkey” used index on „s_suppkey” used old cost 114507..114507 new cost 8061..8061 cost saved 106446..106446, these are 92..92% advisor: 24ms Abbildung 4.19: Beispiel der Index-Advisor-Ausgabe Eine Beispiel-Ausgabe des „Advisors” ist in der Abbildung 4.19 dargestellt. In dem Beispiel schlägt der „Advisor” zwei Attribute zur Indizierung vor. Diese lauten „ps_partkey” und „s_suppkey”. Die Kosten der Anfrage ohne diese Indizes werden mit „old cost” bezeichnet. Sie betragen minimal 114507 und auch maximal 114507. Mit den Indizes reduzieren sich die Kosten auf genau 8061, was mit „new cost” bezeichnet wird. Die nächste Zeile stellt den Unterschied zwischen den Kosten dar. Dies ist der mögliche Gewinn durch die Indizierung der zwei Attribute. Hinter „cost saved” steht als erstes der Zahlenwert, um den sich die Kosten der Anfrage reduzieren. Die Werte sind 106446 bis 106446. Dem schließt sich die prozentuale Rechnung an. Die Kosten lassen sich also um 92 Prozent verringern. Hinter „advisor” steht die Zeit, welche der Index-Advisor benötigt hat. Dies sind 24ms. Mit den zwei Parametern der „Run-time Configuration” kann das Verhalten des IndexAdvisors an die jeweiligen Bedürfnisse angepasst werden. Durch die Möglichkeit die Parameter zur Laufzeit zu ändern, sind mit ihnen vielfältige Optimierungsstrategien möglich. 4.9 Auswertung Zur Einschätzung des Index-Advisors wurden praktische Test durchgeführt. Dabei sind insbesondere die Funktionstüchtigkeit, Qualität des Ergebnisses und Geschwindigkeit des „Advisors” interessant. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 79 Die dazu verwendete Testumgebung ist ein Computer mit AMD Athlon XP 1600+ Prozessor und 512 MB DDR-RAM. Als Betriebssystem kam Suse Linux 9 zum Einsatz. Um aussagekräftige Ergebnisse zu erhalten wurde für die Tests die Datenbank des „TPC-H Benchmarks” eingesetzt. Eine Beschreibung dieser ist im Kapitel 3.3 zufinden. Die Datenbank wurde mit einem „Scalefactor” von 1 erzeugt und ist damit 1 GB groß. Bei den Tests wurden die folgenden Auswertungen durchgeführt: • Vollständigkeit der Indexempfehlung Diese Auswertung soll darüber Aufschluss geben, ob der Index-Advisor alle Index-Kandidaten erkennt. Zum Vergleich dient eine vollständig indizierte „TPC-H Datenbank”. Die Indizes, welche der Planner bei dieser Datenbank nutzt, müssen vom „Advisor” in der normalen „TPC-H Datenbank” empfohlen werden. In den durchgeführten Testfällen hat der „Advisor” alle Index-Kandidaten korrekt ermittelt. Damit lässt sich die Aussage treffen, dass das Regelwerk funktioniert und nur unnütze Attribute entfernt. Zur Durchführung der Tests wurde für jedes Attribut einer Anfrage ein „realer Index” erstellt. Eine Ausführung des „Explain-Kommandos” lieferte alle Indizes, welche für die Anfrage von Nutzen sind. Dies sind die Index-Kandidaten und damit der Vergleichswert für den Index-Advisor. Nach dem Entfernen der „realen Indizes” wurde der „Advisor” aufgerufen und sein Ergebnis ermittelt. Ein Vergleich der beiden Ergebnisse ergab immer eine vollständige Übereinstimmung. • Qualität der Indexempfehlung Neben dem Finden aller Index-Kandidaten ist auch die Qualität der Empfehlung von Interesse. Damit ist die Qualität der Kostenschätzung gemeint. Diese fließt in den errechneten Gewinn der Index-Kandidaten ein. Stimmen die Kosten nicht, so können bezüglich des Nutzens jedes Index-Kandidaten keine korrekten Entscheidungen getroffen werden. Programme, welche den errechneten Gewinn auswerten, würden falsche Entscheidungen treffen. Um die Qualität zu testen, werden die berechneten Kosten verglichen. Dabei werden für jede Anfrage zwei Berechnungen durchgeführt. Bei der ersten existieren alle Index-Kandidaten nur als „virtuelle Indizes”. Dies entspricht dem Ergebnis des „Advisors”. Bei der zweiten Berechnung existieren die IndexKandidaten als „reale Indizes”. Die beiden Werte werden anschließend verglichen. Das Ergebnis ist in Abbildung 4.20 dargestellt. Die Schätzung des IndexAdvisors stimmt fast immer mit den realen Kosten überein. Damit kann die Qualität der Indexempfehlung als gut eingestuft werden. • Index-Advisor Overhead 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 80 Abbildung 4.20: Qualität der Kostenschätzung Der Index-Advisor ist ein Teil der PostgreSQL-Verarbeitungspipeline. Damit beeinflusst er den Ressourcenverbrauch des Systems. Der Mehrverbrauch, welcher durch den „Advisor” entsteht, wird als „Overhead” bezeichnet. Die vom Index-Advisor genutzen Ressourcen sind vor allem Rechenzeit und Arbeitsspeicher. Da der Speicherverbrauch des „Advisors” minimal ist, wird dieser nicht weiter betrachtet. Die Rechenzeit hingegen ist eine kritische Ressource bei Datenbanksystemen. Von ihr hängt direkt die Zeit ab, welche zur Verarbeitung einer Anfrage benötigt wird. Zur Ermittlung des Overheads wurde wieder die Datenbank des „TPC-H Benchmarks” genutzt. Ausgewertet wurde dabei die Zeit, welche der „Advisor” für eine Anfrage benötigt. Die Zeitmessung übernimmt dabei der „Advisor” selbst, da sie ein Teil seiner Terminal-Ausgabe ist (siehe Kapitel 3.10). Um aussagekräftigere Werte zu erhalten, wurden die Anfragen mehrmals hintereinander ausgeführt und ein Mittelwert gebildet. Dadurch können große Schwankungen durch z.B. Diskzugriffe gemindert werden. Das Ergebnis der Tests ist in Abbildung 4.21 dargestellt. Die vom „Advisor” benötigte Zeit reicht von nur 3ms bis zu 64ms und ist damit recht gering. Eine bessere Einschätzung dieser Zeiten ist mit dem Vergleich zu der gesamten Zeit für die Anfrageverarbeitung möglich. Dies wurde für die zweite Anfrage 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 81 Abbildung 4.21: Overhead des Index-Advisors aus dem „TPC-H Benchmark” gemacht. Der dazugehörige Vergleich ist in Abbildung 4.22 dargestellt. Dabei wurde die Zeit zur Verarbeitung der Anfrage zweimal gemessen. Die erste Ausführung erfolgte ohne jegliche Indizes. Dafür benötigte das Datenbanksystem 75.000ms. Die 20ms des Index-Advisors sind dagegen verschwindend gering und fallen nicht ins Gewicht. Bei der zweiten Ausführung standen für die Anfrage alle nützlichen Indizes zur Verfügung. Damit reduzierte sich die Verarbeitungszeit auf nur 1.300ms. Aber selbst bei dieser recht kurzen Zeit ist der Overhead des „Advisors” nicht von Interesse. Damit kann der Overhead des Index-Advisors als minimal bezeichnet werden und geht nur sehr gering in die Zeit zur Anfrageverarbeitung ein. Abbildung 4.22: Verhältnis des Overheads Die Auswertung hat gezeigt, dass die Leistungsfähigkeit des „Advisors” durchaus gut ist. Er liefert brauchbare Ergebnisse, ohne das Datenbanksystem zu sehr zu belasten. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 4.10 82 Einsatz Die Einsatzmöglichkeiten des Index-Advisors sind vielfältig. In jedem Fall dient er der Optimierung einer Datenbank und schafft durch angepasste Indizes mehr Performance. Denkbare Einsatzgebiete sind im Einzelnen: • QUIET-System Das QUIET-System wurde in Kapitel 2.4 vorgestellt. Für seine Funktion nutzt es den „DB2 Advisor”. Die Gründe, welche gegen diesen sprechen, wurden ebenfalls schon beschrieben. Der Index-Advisor stellt einen vollwertigen Ersatz für den „DB2 Advisor” dar. So könnte das QUIET-System auf das PostgreSQLDatenbanksystem portiert werden. • Workload-Optimierung Mittels des Index-Advisors ist es möglich, für eine Menge von Anfragen ein Index-Set zu bestimmen. Für diese Aufgabe werden alle Anfragen des Workloads an die Datenbank gestellt. Anhand der Systemtabelle des „Advisors” kann der Nutzen möglicher Indizes ermittelt werden. • Anfrage-Optimierung Auch für einzelne Anfragen ist der Index-Advisor von Nutzen. So kann der „Advisor” vom Datenbank-Administrator eingesetzt werden, um mögliche Indizes für eine Anfrage zu ermitteln. Das Ergebnis ist als Terminal-Output des IndexAdvisors sofort ablesbar. • Statistik Der „Advisor” kann auch als Index-Statistik eingesetzt werden. Durch seine Aktivierung wird er bei jeder Anfrage aufgerufen und speichert sein Ergebnis in die Tabelle „pg_indexadvisor”. Diese kann in regelmäßigen Abständen ausgewertet werden. Dies kann nach den verschiedensten Gesichtspunkten erfolgen. Einer ist z.B. die Anzahl der Einträge. Ist die Tabelle leer, deutet dies daraufhin, dass sich das Anlegen neuer Indizes nicht lohnt. Ein weiterer Anhaltspunkt ist die Größe des Gewinns pro Eintrag. Nur bei großen Werten können neue Indizes mehr Performance bringen. Anhand der Auswertung können Entscheidungen zum Anlegen von Indizes getroffen werden. 4.11 Fazit Die Umsetzung des Index-Advisors ist voll funktionsfähig und liefert gute Resultate. Die erstellten Empfehlungen sind sehr nah an realen Indizes. Dadurch ist die Ermittlung eines optimalen Index-Sets möglich. 2004-09-30/101/IN99/2254 KAPITEL 4. INDEX-ADVISOR 83 Der dabei erzeugte Overhead des Advisors ist gering. Im Verhältnis zur kompletten Verarbeitungszeit einer Anfrage kann er sogar vernachlässigt werden. Dies macht eine uneingeschränkte Nutzbarkeit möglich. Die Schätzung der Kostenersparnis ist ebenfalls gut. Auch sie stimmt meist mit realen Indizes überein. So bringen erstellte Indizes auch den vom Advisor berechneten Geschwindigkeitsgewinn. Dies alles ermöglicht den praktischen Einsatz des Index-Advisors. Dabei kann er für vielfältige Aufgaben eingesetzt werden. 2004-09-30/101/IN99/2254 KAPITEL 5. AUSBLICK 84 Kapitel 5 Ausblick In einigen Datenbanksystemen sind bereits Ansätze zur automatischen IndexBestimmung vorhanden. Diese aber berücksichtigen jeweils nur eine Anfrage, arbeiten nicht parallel zum Datenbanksystem oder benötigen zu viel Zeit. Damit sind sie für Anfragegetriebenes Index-Tuning unzureichend. Der in PostgreSQL implementierte Index-Advisor macht es möglich, alle diese Probleme zu lösen. Durch die vollständige Integration bietet er eine schnelle und effiziente Verarbeitung. Diese erfolgt bei jeder Anfrage, welche an das Datenbanksystem gestellt wird. Auf diese Weise kann kontinuierlich eine Statistik gebildet werden. Diese Informationen bilden die Grundlage für Anfragegetriebenes Index-Tuning. Für die Auswertung der Statistik wird eine eigene Applikation benötigt. Diese kann z.B. das in Kapitel 2.4 vorgestellte QUIET-System sein. Die Aufgabe der Applikation besteht in der Verwaltung des Index-Pools. Aus Performancegründen ist eine Integrierung dieser Software in PostgreSQL empfehlenswert. Ein weiterer Entwicklungspunkt ist der Index-Advisor selbst. In seiner jetzigen Umsetzung ist er nicht mehrinstanzfähig. Dies bedeutet, dass Fehler auftreten können, sobald mehrere Instanzen des Advisors gleichzeitig arbeiten. Dieser Fall trifft ein, sobald das Datenbanksystem mehrere Anfragen parallel verarbeitet. Das Problem dabei sind die virtuellen Indizes. Sie sind während der Optimierung einer Anfrage in der ganzen Datenbank „sichtbar”. Werden sie in einer anderen Anfrage genutzt, entsteht ein Fehler im Datenbanksystem. Zu den Aufgaben des Index-Advisors gehört nicht die Einschätzung von bereits angelegten Indizes. Für ein komplettes Anfragegetriebenes Index-Tuning sind diese Informationen aber notwendig. Als Lösung können sie von der Applikation ermittelt werden, welche den Advisor nutzt. Denkbar ist aber auch eine Weiterentwicklung des Index-Advisors selbst. Abschließend lässt sich sagen, dass der Index-Advisor praktisch einsetzbar und funktionsfähig ist. Allerdings ohne Mehrinstanzfähigkeit und eine Applikation, die seine Empfehlungen auswertet, kann sein Potential nicht voll ausgeschöpft werden. 2004-09-30/101/IN99/2254 ABBILDUNGSVERZEICHNIS 85 Abbildungsverzeichnis 1.1 Beispiele der Größenverhältnisse zwischen Tabelle und Index . . . . . 2.1 2.2 2.3 2.4 2.5 2.6 Oracle - „Virtual Index Wizard” ([4]) . . . . . . . . . . . . . Architektur des „Index Selection Tool” ([3] / S.2) . . . . . . . Architektur des „DB2 Advisors” ([1] / S.2) . . . . . . . . . . Architektur des QUIET-Systems ([2] / S. 3) . . . . . . . . . . „Index Configuration Monitor” des QUIET-Systems ([2] / S. 4) Übersicht der vorgestellten Systeme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 12 13 15 16 17 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.20 3.21 3.22 3.23 3.24 Beispiel einer Index-Nutzung . . . . . . . . . . . . Schema der TPC-H Datenbank ([16] / S.12) . . . . Verlauf der Anfrageverarbeitung [7] . . . . . . . . List-Package Beispiel . . . . . . . . . . . . . . . . Definition des List-Nodes . . . . . . . . . . . . . . Definition des Expr-Nodes . . . . . . . . . . . . . Definition des Plan-Nodes . . . . . . . . . . . . . Zusammenhang zwischen State-Tree und Plan-Tree Definition des SortBy-Nodes . . . . . . . . . . . . Beispiel-Anfrage . . . . . . . . . . . . . . . . . . Teilmenge des Ergebnisses der Beispiel-Anfrage . Query-Tree der Beispiel-Anfrage . . . . . . . . . . Query-Tree Ausschnitt - Query-Node . . . . . . . Query-Tree Ausschnitt - Rangetable . . . . . . . . Query-Tree Ausschnitt - Jointree . . . . . . . . . . Query-Tree Ausschnitt - Targetlist . . . . . . . . . Query-Tree Ausschnitt - GroupClause . . . . . . . Query-Tree Ausschnitt - HavingQual . . . . . . . . Plan-Tree der Beispiel-Anfrage . . . . . . . . . . . Plan-Tree Ausschnitt - Agg-Node . . . . . . . . . Plan-Tree Ausschnitt - MergeJoin-Node . . . . . . Plan-Tree Ausschnitt - Sort-Node . . . . . . . . . . Plan-Tree Ausschnitt - SeqScan-Node . . . . . . . CommandCounter-Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 24 26 28 29 30 31 32 33 34 34 36 37 37 38 39 40 40 42 43 44 45 46 47 2004-09-30/101/IN99/2254 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 ABBILDUNGSVERZEICHNIS 86 3.25 3.26 3.27 3.28 3.29 3.30 3.31 Struktur einer Disk-Page . . . . . . . Prinzip der PostgreSQL-Operatoren . Ausschnitt der Datei „postgresql.conf” Parameter als „Command line option” Parameter mit Benutzer verbinden . . Parameter mit „SET” setzen . . . . . Parameter mit „SHOW” anzeigen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 51 54 54 55 56 56 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15 4.16 4.17 4.18 4.19 4.20 4.21 4.22 genutzte Tabellen bei der Anfrageverarbeitung . . . . . . . . . Arbeitsschritte des Index-Advisors . . . . . . . . . . . . . . . Definition des IndexCandidates . . . . . . . . . . . . . . . . . Beispiel-Anfrage2 . . . . . . . . . . . . . . . . . . . . . . . . Spaltentypen und ihre Größe . . . . . . . . . . . . . . . . . . Formel - Kategorie 1 und 2 . . . . . . . . . . . . . . . . . . . Formeln - Kategorie 3 . . . . . . . . . . . . . . . . . . . . . . Formeln - Kategorie 4 . . . . . . . . . . . . . . . . . . . . . . Formel - Index-Größe . . . . . . . . . . . . . . . . . . . . . . Systemtabelle „pg_indexadvisor” . . . . . . . . . . . . . . . . Ablauf des Index-Advisors . . . . . . . . . . . . . . . . . . . Ablauf des Index-Advisors - Input . . . . . . . . . . . . . . . Ablauf des Index-Advisors - Ausführung ohne virtuelle Indizes Ablauf des Index-Advisors - Index-Kandidaten . . . . . . . . Ablauf des Index-Advisors - Anlegen der virtuellen Indizes . . Ablauf des Index-Advisors - Ausführung mit virtuellen Indizes Ablauf des Index-Advisors - Entfernen der virtuellen Indizes . Ablauf des Index-Advisors - Ergebnis . . . . . . . . . . . . . Beispiel der Index-Advisor-Ausgabe . . . . . . . . . . . . . . Qualität der Kostenschätzung . . . . . . . . . . . . . . . . . . Overhead des Index-Advisors . . . . . . . . . . . . . . . . . . Verhältnis des Overheads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 59 60 63 65 65 66 66 68 69 70 70 71 72 73 74 75 75 78 80 81 81 2004-09-30/101/IN99/2254 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . LITERATURVERZEICHNIS 87 Literaturverzeichnis [1] G. Valentin, M. Zuliani, D. Zilio, G. Lohman, A. Skelley: „DB2 Advisor: An Optimizer Smart Enough to Recommend Its Own Indexes”, In Proceedings of ICDE 200, S. 1-10, 2000 [2] K. U. Sattler, I. Geist, E. Schallehn: „QUIET: Continuous Query-driven Index Tuning”, Proceedings of the 29th VLDB Conference, S. 1-4, Berlin, Germany, 2003 [3] S. Chaudhuri, V. Narasayya: „An Efficent, Cost-Driven Index Selection Tool for Microsoft SQL Server”, Proceedings of the 23rd VLDB Conference, S. 1-10, Athens, Greece, 1997 [4] Oracle: „Data sheet: Oracle Tuning Pack”, www.oracle.com [5] Oracle: „Test Drive An Index With the Virtual Index Wizard”, www.oracle.com [6] S. Simkovics: „Enhancement of the ANSI SQL Implementation of PostgreSQL”, www.urv.net, S. 52-60 [7] B. Momjian: „How PostgreSQL Processes a Query”, developer.postgresql.org [8] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. i-ii [9] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 164-171 [10] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 913-914 [11] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 916 [12] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 917-918 [13] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 482, 936 2004-09-30/101/IN99/2254 LITERATURVERZEICHNIS 88 [14] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 487-493, 935 [15] The PostgreSQL Global Development Group: „PostgrSQL 7.4.1 Documentation”, www.postgresql.org, S. 209 [16] TPC Benchmark H: „Standard Specification, Revision 2.1.0” www.tpc.org, S. 6 [17] A. Kemper, D. Kossmann, B. Zeller: „Performance Tuning for SAP R/3”, IEEE Data Engineering Bulletin, S. 2, 1999 2004-09-30/101/IN99/2254