Fakultät für Elektrotechnik und Informatik Institut für Praktische Informatik Fachgebiet Datenbanken und Informationssysteme Universität Hannover Bachelorarbeit im Studiengang Informatik Entwicklung eines Parsers für relationale Anfragen und einer Verwaltung von Ausführungsplänen für den Anfragesimulator RELOpt Alexander Fabig Matr.-Nr. 2178802 30. September 2005 Erstprüfer: Prof. Dr. Udo Lipeck Zweitprüfer: Dr. Hans Hermann Brüggemann Betreuer: Dipl.-Math. Mazeyar E. Makoui Erklärung Hiermit versichere ich, Alexander Fabig, dass ich die vorliegende Arbeit und die zugehörige Implementierung selbständig verfaßt und dabei nur die angegebenen Quellen und Hilfsmittel verwendet habe. Hannover, am 30. September 2005 Danksagung An dieser Stelle möchte ich die Gelegenheit nutzen und mich bei allen bedanken, die mich während der Entstehung dieser Arbeit unterstützt haben. Besonders bedanken möchte ich mich bei Herrn Prof. Dr. Lipeck, der mir mit seinen Fragen und Anmerkungen zu meiner Arbeit sehr geholfen hat. Mein Dank geht ebenfalls an Herrn Dr. Brüggemann für seinen Einsatz als Zweitprüfer. Für seine andauernde Motivation möchte ich meinem Betreuer Herrn Dipl.-Math. Mazeyar Makoui danken. Desweiteren bedanke ich mich bei Herrn Radoslaw Rudnicki für die hilfreichen Diskusionen. Schließlich möchte ich mich bei meinen Eltern, Peter und Delia Lüer, bedanken, die mir dieses Studium ermöglicht haben. Hannover, am 30. September 2005 Zusammenfassung In dieser Arbeit wurde ein relationaler Parser und eine Datenstruktur zur Verwaltung von Ausführungsplänen für den Anfragesimulator RELOpt entwickelt. Die neue Datenstruktur, die als Basis für ein Einphasenoptimierungssystem verwendet wird, heißt generalisierter Baum. In einem solchen Baum wird jede Relation und jede Operation in einem Knoten gespeichert. Außerdem enthält er die für die Optimierung benötigen Metadaten. Um einen Ausführungsplan bearbeiten und verändern zu können, werden von einem generaliserten Baum eine Reihe von Methoden bereitgestellt, wie z. B. Methoden zur Ausgabe der Knotenattribute oder zum Austausch von Teilbäumen. Als Eingabesprache für das Einphasenoptimierungssystem wurde die relationale Algebra ausgewählt. Der Parser ist in der Lage aus einer Anfrage einen generalisierten Baum zu generieren. Da eine veränderte Form des generalisierten Baumes zum Speichern von Termersetzungsregeln verwendet wird, wurde die Sprache erweitert um auch solche variablen und bedingungsbehafteten Regelterme zu erzeugen. Inhaltsverzeichnis 1 Einleitung 9 1.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.2 Ziel dieser Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.3 Aufbau dieser Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2 Relationaler Parser 2.1 Sprachelemente . . . . . . . . . . . . 2.1.1 Relationen und Attribute . . 2.1.2 Operatoren . . . . . . . . . . 2.1.3 Parametrisierungen . . . . . . 2.1.4 Zusätzliche Elemente . . . . . 2.2 Entwicklung der Grammatik . . . . . 2.3 Inhaltliche Analyse . . . . . . . . . . 2.3.1 Vereinfachung von komplexen 2.3.2 Operationsvereinfachung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Selektionsbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 13 14 15 16 17 19 19 22 3 Regelparsing 3.1 Bedingungsarten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Existenzbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.2 Eigenschafts- und Zuweisungsbedingungen . . . . . . . . . . . . . . 3.1.3 Aufteilen und Zuweisen von Selektions- oder Verbundbedingungen 3.1.4 Bestimmung von Vergleichsoperatoren . . . . . . . . . . . . . . . . 3.1.5 Kostenbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Entwicklung einer Grammatik . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Semantische Überprüfung einer Termersetzungsregel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 23 23 24 25 26 26 27 27 4 Generalisierte Bäume 4.1 Knotenattribute . . . . . . . . . . . . . . . . . . . . . . 4.2 Kostenabhängige Auswahl der physischen Umsetzungen 4.3 Sortierungen . . . . . . . . . . . . . . . . . . . . . . . . 4.4 Indexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5 Pipelining . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6 Verwendung von Regelbäumen in einem Termersetzer . 4.7 Baum-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 29 30 31 35 35 37 39 . . . . . . 41 41 42 45 47 47 47 5 Berechnung der Kosten 5.1 Selektivitätsabschätzungen . . . . . . 5.2 Schätzungen für Attributwertanzahlen 5.3 Kardinalität und Pagegröße . . . . . . 5.4 Kostenfunktionen . . . . . . . . . . . . 5.4.1 Selektion und Projektion . . . 5.4.2 Aggregation . . . . . . . . . . . 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 INHALTSVERZEICHNIS 5.5 5.4.3 Join . . . . . . . . . . . . . . . . . . . . 5.4.4 Semi-/Antisemijoin . . . . . . . . . . . . 5.4.5 Mengenoperatoren . . . . . . . . . . . . 5.4.6 Erzeugen von Sortierungen oder Indexen 5.4.7 Verwendung von geclusterten Indexen . Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Implementierung 6.1 GUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 Hauptfenster . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.2 Regeleingabe . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.3 Baumdarstellung . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Package dbs.unihannover.sopt.struc.general . . . . . . . . 6.2.2 Package dbs.unihannover.sopt.struc.general.tree . . . . . . 6.2.3 Package dbs.unihannover.sopt.struc.general.tree.condition 6.2.4 Erweiterungen am Package dbs.unihannover.sopt.gui . . . 6.2.5 Erweiterungen am Package dbs.unihannover.sopt.tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 49 50 51 51 52 . . . . . . . . . . 55 55 55 57 58 59 59 59 63 67 68 7 Ausblick 69 A Relationaler Parser A.1 Grammatik . . . . . . . . . . . . . A.1.1 Nichtterminale Symbole . . A.1.2 Terminale Symbole . . . . . A.1.3 Startsymbol . . . . . . . . . A.1.4 Produktionen . . . . . . . . A.2 Beweis LL(1)-Bedingung . . . . . . A.3 Beispiel für einen Ableitungsbaum B Regelbedingungsparser B.1 Grammatik . . . . . . . . . . . B.1.1 Nichtterminale Symbole B.1.2 Terminale Symbole . . . B.1.3 Startsymbol . . . . . . . B.1.4 Produktionen . . . . . . B.2 Beweis LL(1)-Bedingung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 71 71 71 72 72 73 80 . . . . . . 81 81 81 81 81 82 83 Abbildungsverzeichnis 89 Tabellenverzeichnis 91 Literaturverzeichnis 93 Kapitel 1 Einleitung 1.1 Einführung Anfragen an eine Datenbank werden mit Hilfe von Sprachen, wie z. B. SQL oder der relationalen Algebra, gestellt. Diese Sprachen sind deklarativ, d. h. die gestellten Anfragesätze geben das gewünschte Ergebnis an, aber nicht die Art und Weise mit der dieses erreicht werden soll. Um ein Format zu erhalten das von der Datenbank verarbeitet werden kann, muss das Datenbanksystem aus der Anfrage einen Ausführungsplan erstellen, z. B.: Anfrage Ausführungsplan in Baumdarstellung Rel π(P.N ame,H.Kurse) (P ROJECT ION (P.N ame, H.Kurse)) (JOIN (P.ID = H.ID) P H)) ./Rel−Ind (P.ID=H.ID) P H Abbildung 1.1: Übersetzung einer Anfrage in einen Ausführungsplan Eine 1:1-Übersetzung der Eingabe in eine Form, die vom Optimierungssytem bearbeitet werden kann, wird von einem sogenannten Parser erzeugt. Dieser liest die Eingabe Wort für Wort und baut daraus die benötigte Baumstruktur auf. Wie der Aufbau einer solchen Struktur sein muss, hängt vom jeweiligen System ab, z. B. wird in einem Dreiphasenoptimierungssystem erst ein algebraischer Baum generiert, der im Laufe der Optimierung zu einem konkreten Ausführungsplan wird. Die drei wichtigsten Bestandteile eines Parsers sind in Abbildung 1.2 dargestellt: Anfrage als Zeichenkette lexikale Analyse syntaktische Analyse semantische Anfragebaum Analyse Abbildung 1.2: Vereinfachter Aufbau eines Parsers In der lexikalen Analyse wird der übergebene Anfragesatz in eine Menge von Wörtern zerlegt, wobei z. B. überflüssige Leerzeichen entfernt werden. Die syntaktische Analyse prüft, ob die von der vorherigen Analyse gelieferten Wörter Elemente der Sprache sind und ob ihre 9 10 KAPITEL 1. EINLEITUNG Anordnung korrekt ist. Außerdem wird in dieser Phase eine Baumstruktur erzeugt, die den Anfrageplan wiederspiegelt. Anschließend beginnt die semantische Analyse, in der geprüft wird, ob die Anfrage inhaltlich korrekt ist. Beispielsweise wäre die Anfrage “AGGREGAT ION (#P.N ame, (count(P.N ame) as X.Count)) P“ syntaktisch richtig, aber nicht semantisch, denn bei einer Aggregation müssen die projizierten Attribute auch zur Gruppierung verwendet werden. Ist der Vorgang des Parsens abgeschlossen, wird die gewonnene Baumstruktur dem Optimierungssystem übergeben. Dies versucht mit Hilfe von Termersetzungsregeln einen kostengünstigen Ausführungsplan zu erstellen. 1.2 Ziel dieser Arbeit Bei dem Programm RELOpt handelt es sich um einen Simulator für relationale Anfragen, welcher nach einem klassischen Dreiphasenoptimierungssystem arbeitet. Er wurde in der Diplomarbeit [Mak03] entworfen und in den folgenden Bachelor- und Studienarbeiten [War03], [Die03] und [Zle05] weiterentwickelt, z. B. um die Anbindung an eine Oracle-Datenbank zum Importieren von Tabellen und Statistiken zu ermöglichen. In Zukunft wird dieser Anfragesimulator auf ein Einphasenoptimierungssystem umgestellt. Der strukturelle Aufbau dieses Systems wird in Abbildung 1.3 beschrieben: Relationaler Parser deklarative Anfrage generalisierter Baum generalisierter Baum GUI Steuerung generalisierter Baum + Ersetzungsregeln generalisierte Bäume (Varianten) Termersetzer Abbildung 1.3: Aufbau eines Einphasenoptimierungssystems Vom Benutzer wird über eine GUI eine Anfrage in relationaler Algebra gestellt. Daraus erzeugt der Parser einen Ausführungsplan, der an die Steuerung übergeben wird. Im Gegensatz zu einem Dreiphasenoptimierer, in dem ein algebraischer und ein physischen Baum verwendet wird, gibt es hier eine Baumstruktur, in der sämtliche zur Optimierung benötigten Daten gespeichert werden. Diese neue Datenstruktur wird generalisierter Baum genannt. 1.2. ZIEL DIESER ARBEIT 11 In jedem Optimierungsschritt wird von der Steuerung ein Baum oder Teilbaum an den Termersetzer übergeben. Zusätzlich übergibt sie eine Menge von Termersetzungsregeln mit deren Hilfe neue Teilbäume generiert werden. Das Ziel ist es, einen kostengünstigeren Anfrageplan zu erhalten. Informationen über den genauen Aufbau der Steuerung und des Termersetzers sowie über den Ablauf einer Einphasenoptimierung sind in [Rud05] zu finden. Ziel dieser Arbeit ist die Entwicklung und Implementierung eines relationalen Parsers für das Programm RELOpt. Dieser Parser wird in der Lage sein, aus einer Anfrage in relationaler Algebra einen generalisierten Baum zu erzeugen. Die Entwicklung dieser neuer Datenstruktur zum Speichern und Verwalten von Ausführungsplänen ist ein weiterer Bestandteil dieser Arbeit. In einem generalisierten Baum wird jeder relationale Operator mit den zugehörigen Funktionen zur Berechnung der Kosten in einem Knoten gespeichert. Außerdem werden Methoden bereitgestellt mit deren Hilfe ein Ausführungsplan durchlaufen und verändert werden kann. In [War03] wurden Kostenfunktionen für einen Großteil der relationalen Operatoren aufgestellt. Allerdings nicht für Aggregation und Outerjoins. In dieser Arbeit werden die Kostenformeln für diese Operationen entwickelt und das bestehende Kostenmodell der Verbundoperatoren um Formeln für Non-Equijoins erweitert. Eine veränderte Form des generalisierten Baumes wird zum Speichern von Termersetzungsregeln verwendet. Eine Ersetzungsregel besteht aus einem linken und einem rechten Term, welche selbst generalisierte Bäume sind, allerdings nicht mit konkreten Relationen und Bedingungen, sonderen mit variablen Werten. Außerdem gehören eine Menge von Überführungsbedingungen zu einer Regel. Wird in einem Ausführungsplan eine Übereinstimmung mit einer der Regelseiten gefunden, wird abhängig von den Bedingung eine Ersetzung mit der anderen Seite durchgeführt. Dies ist gut an dem folgenden Beispiel einer Regel aus [Rud05] zu erkennen: σpRel ./Rel−Rel q ./Rel−Rel q R1 R2 1 2 σpRel R2 R1 1) falls attr(p) ⊆ sch(R1 ) 2) falls cost(τ1 ) < cost(τ2 ) Abbildung 1.4: Beispiel für eine Termersetzungsregel Der relationale Parser ist in der Lage Term, die Bestandteil von Ersetzungsregeln sind, zu erzeugen, da diese ebenfalls in einer Form des generalisierte Baumes gespeichert werden. Um die Erstellung kompletter Regeln zu ermöglichen, wird ein zusätzliches Modul für den Parser entwickelt, welches für die Eingabe von Regelbedingungen verwendet wird. In dieser Arbeit verwendete Abkürzungen: R1 , R2 , R3 , . . . q, p, p1 , p2 , . . . l1 , l2 attr(p) sch(R) Relationen (z. B. Zwischenergebnisse) Bedingungen Attributmengen die Menge der in p enthaltenen Attribute Schema der Relation R 12 KAPITEL 1. EINLEITUNG Operationen: Γg#f σp πl × − ∩ ∪ ./p .<p .<p A./p ./@p A./@p 1.3 Aggregation einer Relation mit Projektion auf die Attributmenge f und Gruppierung nach den Attributen der Menge g Selektion einer Relation durch die Bedingung p Projektion einer Relation auf die Attributmenge l Kartesisches Produkt zweier Relationen Differenz zweier Relationen Durchschnitt zweier Relationen Vereinigung zweier Relationen Join zweier Relationen durch die Bedingung p Semijoin zweier Relationen durch die Bedingung p Antisemijoin zweier Relationen durch die Bedingung p Left-Outerjoin zweier Relationen durch die Bedingung p Right-Outerjoin zweier Relationen durch die Bedingung p Left-Right-Outerjoin zweier Relationen durch die Bedingung p Aufbau dieser Arbeit Im 2. Kapitel wird der Aufbau des relationalen Parsers und die Entwicklung einer Eingabesprache erläutert. Das 3. Kapitel beschäftigt sich mit der Entwicklung einer Sprache zur Eingabe von Regelbedingungen und der semantischen Überprüfung von kompletten Termersetzungsregeln. Die Basis des Einphasenoptimierungssystems, der generalisierte Baum, wird mit seinen Methoden und Knotenattributen in Kapitel 4 beschrieben. Die Kostenfunktionen für die einzelnen relationalen Operatoren die in einem generalisierten Baum gespeichert sind, werden in Kapitel 5 behandelt. Im 6. Kapitel wird die Implementierung der aufgestellten Theorien dargestellt. Abschließend gibt Kapitel 7 einen Ausblick auf Veränderungen und Verbesserungen die zukünftig möglich wären. Kapitel 2 Relationaler Parser Wie bereits in der Einleitung beschrieben, generiert der Parser aus einer Anfrage in relationaler Algebra, einen generalisierten Baum. In diesem Baum wird der Ausführungsplan und alle Daten, die für eine Optimierung nötig sind, gespeichert. Die Attribute der Knoten eines Baumes und Methoden zum Bearbeiten eines Ausführungsplanes werden in Kapitel 4 beschrieben. Das erste Modul des Parsers ist der lexikale Scanner. Dieser zerlegt den Anfragesatz in eine Folge von Wörtern und entfernt dabei unnötige Zeichen, wie z. B. mehrfache Leerstellen. Die verwendeten Wörter sind in Kapitel 2.1 aufgeführt und erklärt. Der nächste Teil ist die syntaktische Analyse, in der geprüft wird, ob die in der Anfrage verwendeten Wörter Bestandteil der Sprache sind und ob sie in der richtigen Reihenfolge eingegeben wurden. Um diese Überprüfungen machen zu können wird eine Grammatik benötigt, die den Aufbau der Sprache beschreibt. Wie die Grammatik aufgebaut ist und wie sie entwickelt wurde, ist in Kapitel 2.2 zu finden. Anschließend beginnt die semantische Überprüfung, in der analysiert wird, ob der eingegebene Anfragesatz inhaltlich korrekt ist. Kapitel 2.3 beschäftigt sich mit der Analyse des Inhalts und möglichen Vereinfachungen. 2.1 Sprachelemente Die vom Parser akzeptierte Sprache ist die Relationale Algebra. Im wesentlichen besteht sie aus Relationen, Operatoren und Parametrisierungen. In diesem Abschnitt werden die einzelnen Elemente und ihre Bezeichnung in der Sprache sowie in der Grammatik aufgeführt. 2.1.1 Relationen und Attribute Eine Relation R besteht aus einem oder mehreren Attributen A1 , ..., An . Diese Menge wird als Relationenschema bezeichnet. Jedes Attribut Ai speichert eine Menge von Werten wi1 ...wim . Eine Zeile aus der Relation enthält mehrere Werte und wird Tupel genannt. R Tupel 1 .. . A1 w11 .. . ··· ··· An wn1 .. . Tupel m w1m ··· wnm Abbildung 2.1: Darstellung einer Relation 13 14 KAPITEL 2. RELATIONALER PARSER In der vom Parser akzeptierten Sprache müssen Attribute immer mit dem Namen der dazugehörigen Relation, in der Form RELAT ION.AT T RIBU T , angegeben werden. Die Bezeichnungen von Relationen und Attributen müssen mit einem Buchstaben beginnen und kürzer als 30 Zeichen sein. 2.1.2 Operatoren Operatoren arbeiten auf Argumentrelationen, indem sie Tupel oder Attribute auswählen oder verknüpfen. Das Ergebnis einer Operation ist eine Menge von Tupeln, also wieder eine Relation, auf der weitere Operationen ausgeführt werden können. Die in der relationalen Algebra verwendeten Operatoren lassen sich in drei Gruppen einteilen: • Scanoperatoren arbeiten auf einer Relation, indem sie Tupel oder Attribute selektieren. Bei einer Aggregation können auch neue Attribute durch die Anwendung von Group-by-Funktionen hinzukommen. Dies wird genauer in Kapitel 5 erläutert. Operator Bezeichnung Bedeutung π P ROJECT ION Projektion einer Relation auf eine Attributmenge σ SELECT ION Γ AGGREGAT ION Selektion von Tupeln aus einer Relation Aggregation einer Relation mit möglicher Gruppierung und Anwendung von Group-by-Funktionen Tabelle 2.1: Bezeichnungen für Scanoperatoren • Mengenoperatoren verbinden zwei Relationen ohne die Angabe von Verbundbedingungen. Allerdings gibt es für die Anwendung der Operatoren Voraussetzungen. Zur Formulierung der Anwendungsbedingungen wird eine Funktion sch(R) verwendet, die das Schema der Argumentrelation R wiedergibt. Operator Bezeichnung Bedingung × P RODU CT sch(R1 ) ∩ sch(R2 ) = ∅ ∪ U N ION sch(R1 ) = sch(R2 ) ∩ IN T ERSECT sch(R1 ) = sch(R2 ) − M IN U S sch(R1 ) = sch(R2 ) Tabelle 2.2: Bezeichnungen für Mengenoperatoren • Joinoperatoren verbinden die Tupel zweier Relationen mit Hilfe von Verbundbedingungen, beispielsweise: “OU T ERJOIN (P.ID = X.ID) P, X“. Ausnahmen sind Semi- und Antisemijoins, da diese Tupel aus der ersten Argumentrelation selektieren und keine Tupel verbinden. Da diese Operatoren allerdings auf Joinoperatoren basieren, werden sie hier mit aufgeführt. 2.1. SPRACHELEMENTE 15 Operator Bezeichnung Bedeutung ./ JOIN A./ LEF T OU T ERJOIN Verbund zweier Relationen mit anschließendem Hinzufügen der linken, partnerlosen Tupel ./@ RIGHT OU T ERJOIN Verbund zweier Relationen mit anschließendem Hinzufügen der rechten, partnerlosen Tupel A./@ OU T ERJOIN Verbund zweier Relationen mit anschließendem Hinzufügen der linken und rechten partnerlosen Tupel .< SEM IJOIN Selektion von Tupel aus der ersten Relation in Abhängigkeit von der zweiten .< AN T ISEM IJOIN Selektion von Tupel aus der ersten Relation in Abhängigkeit von der zweiten Verbund zweier Relationen Tabelle 2.3: Bezeichnungen für Verbundoperatoren 2.1.3 Parametrisierungen Die Parametrisierungen der Operationen beschreiben, welche Tupel oder Attribute ausgewählt oder verbunden werden. In dieser Arbeit werden die Parametrisierungen in vier Kategorien eingeteilt: 1. Selektionsbedingungen Diese Art von Parametrisierung wird bei einer Selektion verwendet und gibt an welche Tupel ausgewählt werden sollen. Als Verleichsoperatoren können ≥, >, =, 6=, <, ≤ , dargestellt durch >=, >, =, ! =, <, <=, genutzt werden. Selektionsbedingungen lassen sich verknüpfen durch ∧, ∨ und ¬. Diese Verknüpfungen werden eingegeben durch AN D, OR und N OT , z. B.: SELECT ION (AN D((S.Semester >= ’5’), (S.Semester ! = ’6’))) S Desweiteren sind arithmetische Ausdürcke möglich, wie z. B.: SELECT ION (((S.Semester/2) + 3) =’5’) S Für diese Ausdrücke stehen die Operatoren +, −, ∗, und / zur Verfügung. Es muss bei der Eingabe solcher Bedingungen darauf geachtet werden, dass die einzelnen Berechnungen, wie es in dem Beispiel gezeigt wird, eingeklammert sind. 2. Projektionsparametrisierung Als Parameter einer Projektion werden Attribute angegeben auf die projiziert werden soll. Dabei sind tupelweise Berechnungen auf den Attributen der Argumentrelation möglich, z. B.: P ROJECT ION (S.M atrN r, ((S.Semester + 15) as S.SEM )) S Wie man an dem Beispiel sieht, ist es ebenfalls möglich Attribute umzubenennen. Bei arithmetischen Ausdrücken muss in jedem Fall ein neuer Name angegeben werden, damit in der Ergebnisrelation eine eindeutige Attributbezeichnung vorhanden ist, wie es in Kapitel 2.1.1 gefordert wird. 16 KAPITEL 2. RELATIONALER PARSER 3. Aggregationsparametrisierung Aggregationsparametrisierungen bestehen aus zwei Teilen die durch # getrennt werden. Die linke Seite gibt an, nach welchen Attributen gruppiert werden soll. Auf der rechten Seite können zusätzlich Group-by-Funktionen, wie z. B. count(∗) angegeben werden. Bsp.: AGGREGAT ION (P.N ame#P.N ame, (count(P.Kurse) as A.Count)) P Für die Attribute, welche durch die Aggregationsfunktionen neu entstehen, muss eine eindeutige Bezeichnung angegeben werden. Eine Attributbezeichnung muss die Form RELAT ION.AT T RIBU T haben. Für die Relation kann ein beliebiger, noch nicht vorhandener Name verwendet werden. Es ist sinnvoll für alle neu entstandenen Attribute den selben Namen für die Relation zu verwenden. 4. Verbundbedingungen Diese Bedingungen beziehen sich immer auf die Attribute zweier Relationen und geben an, auf welche Art und Weise diese verbunden werden sollen. Einzelne Joinbedingungen können durch ∧-Verknüpfungen zu komplexen Bedingungen verbunden werden. Als Vergleichsoperatoren innerhalb einer Bedingung sind ≥, >, =, 6=, <, ≤ zugelassen, z. B.: OU T ERJOIN (P.ID <= X.ID) P, X 2.1.4 Zusätzliche Elemente Zum Umfang der Sprache gehören noch weitere Elemente, wie z. B. das Setzen von Aliasnamen. Aliase können an Basisrelationen, Operationsergebnisse und Attribute vergeben werden. Dies könnte beispielsweise bei Self-Joins benötigt werden. Das Ändern von Attributbezeichnungen wurde bereits in Kapitel 2.1.3 mit den Projektionsbedingungen erläutert. Das Vergeben von Aliasnamen an Operationen und Basisrelationen wird beispielhaft im folgenden dargestellt, wobei das Wort as zur Angabe einer neuen Bezeichnung verwendet wird: P ROJECT ION (Z.ID) (SELECT ION (X.ID =’5’) (S as X) as Z) Der Parser soll zusätzlich in der Lage sein, Terme die Bestandteil von Ersetzungsregeln sind zu parsen, da diese ebenfalls in einem generalisierten Baum gespeichert werden. Ein Term einer Regel wird genutzt, um Stellen in einem zu optimierenden Ausführungsplan zu identifizieren, bei denen eine Termersetzung möglich ist. Der andere gibt eine Struktur vor, nach der eine gefundenen Stelle umgeformt wird. Genauere Informationen über den Aufbau von Termersetzungsregeln sind im nächsten Kapitel zu finden. Die Seiten einer Regel enthalten weder konkrete Parametrisierungen der Operationen, noch konkrete Relationen. Stattdessen werden Variablen angegeben. Bei der Eingabe muss darauf geachtet werden, dass für jeden Operator eine physische Umsetzung ausgewählt wird und die variablen Parametrisierungen und Relationen in eckigen Klammern geschrieben werden. Somit ist der Parser in der Lage zu erkennen, dass eine Regel geparst werden soll, z. B.: JOIN rel-ind [p] [R1 ] [R2 ] Bei den physischen Umsetzungen bezeichnet rel einen Relationenscan, ind einen Indexzugriff, sort steht für die Ausnutzung einer Sortierung und hash für das Verwenden eines Hash-Algorithmus. Um anzugeben, dass die physische Realisierung beliebig ist, wird das 2.2. ENTWICKLUNG DER GRAMMATIK 17 Wort epsilon verwendet. In einer Regel können noch weitere Operatoren vorhanden sein, die angeben, dass ein Index oder eine Sortierung erzeugt werden soll. Die Bezeichnungen und Beschreibungen dieser Operatoren sind in Tabelle 2.4 aufgeführt: Operation Beschreibung IN DEX [l] [R] Erzeugung eines Index auf den übergebenen Attributen l der Relation R. SORT [l] [R] Sortierung der Relation R nach der Attributmenge l. Tabelle 2.4: Erzeugende Operatoren Das folgende Beispiel zeigt die Eingabe eines Regelterms, in dem ein Index erzeugt werden soll, um eine Projektion über einen Indexzugriff zu ermöglichen: P ROJECT ION ind [l] (IN DEX ([l]) [R]) 2.2 Entwicklung der Grammatik Die Grammatik stellt den Kern des Parsers dar. Eine Grammatik G = (N, T, P, S) wird definiert durch die Menge ihrer nichtterminalen Symbole N , dies sind Hilfsmittel zum Konstruieren von Sätzen, durch die Menge der terminalen Symbole T , sie sind die Wörter der Eingabesprache, die Produktionen P , die eine Menge von Ableitungsregeln zur Erzeugung von Sätzen bilden und der Angabe eines Startsymbols S. Eine Produktion besteht aus einer linken, einer rechten Seite und einem Überführungspfeil. Die linke Seite besteht nur aus einem nichtterminalen Zeichen. Auf der rechten Seite können Nichtterminale und Terminale vorkommen, z. B.: <operation> → SELECTION <phySingle> (<conditionSelection>) <start> <alias> An dieser Beispielproduktion wird deutlich auf welche Weise Nichtterminale genutzt werden. Bei der Verarbeitung eines Satzes werden die nichtterminalen Symbole durch ihre Ableitungen ersetzt. In dieser Arbeit wird ein deterministischer Top-Down-Parser entwickelt, d. h. nach [Par04]: 1. Man ließt von rechts nach links jedes Wort der Eingabe nur einmal. 2. Parallel wird der Ableitungsbaum von oben nach unten und von links nach rechts erzeugt. Dies bedeutet, dass eine Linksableitung der Anfrage rekonstruiert wird. Ein Beispiel für einen Ableitungsbaum ist im Anhang A.3 zu finden. 3. Ist man an einem Knoten des Ableitungsbaumes angekommen, der mit einem nichtterminalen Symbol A markiert ist und der als nächstes gemäß 2. verarbeitet werden muss, kann man mit Hilfe des nächsten zu lesenden Wortes die anzuwendene A-Produktion eindeutig bestimmen. Diese drei Punkte werden mit Hilfe der sogenannten LL(1)-Bedingung überprüft. Ein Definition dieser Bedingung folgt auf den nächsten Seiten. Für den Parser wurde zuerst eine kontextfreie Grammatik aufgestellt, die den gewünschten Sprachumfang gewährleistete. Diese konnte allerdings nicht eindeutig geparst werden, da sie linksrekursive Produktionen und Produktionen mit gemeinsamen Präfixen enthielt. Um arbeiten zu können, muss der Parser nach jedem gelesenen Wort wissen in welcher Produktion er sich befindet. Dies ist nicht möglich, wenn Produktionen mit der selben linken Seite 18 KAPITEL 2. RELATIONALER PARSER gemeinsame Präfixe auf der rechten Seite haben. Linksrekursive Produktionen beginnen auf der rechten Seite mit dem nichtterminalen Symbol der linken, wodurch Endlosschleifen entstehen können. Unter Anwendung der in [Par04] vorgestellten Methoden wurde die Grammatik in eine eindeutige Form gebracht: 1. Entfernen gemeinsamer Präfixe: Seien A → αβ1 |...|αβn |γ1 |...|γm alle A-Produktionen der Grammatik, wobei α 6= ε und kein γi das Präfix α hat. Ersetze diese Produktionen durch: A → αA0 |γ1 |...|γm und A0 → β1 |...|βn 2. Beseitigung linksrekursiver Produktionen: Seien A → Aα1 |...|Aαn |β1 |...|βm alle A-Produktionen, wobei kein βi mit A beginnt und alle αi = 6 ε sind. Dann ersetze man diese Produktionen durch: A → β1 A0 |...|βn A0 und A0 → α1 A0 |...|αm A0 |ε wobei A0 ein neues nichtterminales Symbol ist. Das Entfernen von Linksrekursionen in der Grammatik wird an einem Beispiel demonstriert: Rekursive Produktionen: <conditionProjection> → <calcTarget> <alias> <conditionProjection> → <conditionProjection>, <conditionProjection> Bereinigte Produktionen: <conditionProjection> → <calcTarget> <alias><additionalCondition> <additionalCondition> → , <conditionProjection> | ε Um abschließend sicherzustellen, dass Anfragen eindeutig geparst werden können, wird die Grammatik auf die LL(1)-Bedingungen hin überprüft: • Definition Eine kontextfreie Grammatik G = (N, T, P, S) heißt LL(1)-Grammatik, falls für alle Symbole A aus der Menge der Nichtterminalen gilt1 : Seien A → α1 | . . . |αn alle A-Produktionen in P . 1. First(α1 ),. . . , First(αn ) sind paarweise disjunkt. 2. Ist ε ∈ First(αj ), dann ist Follow(A)∩ First(αi ) = ∅ für 1 ≤ i ≤ n, i 6= j Die Funktion First(α) bestimmt die Menge der terminalen Symbole die bei einer Linksableitung von α als erstes auftreten. Dazu kann auch das leere Wort ε gehören. Follow(α) ist die Menge der Terminale, die bei einer Ableitung, beginnend beim Startsymbol, direkt auf α folgen. Das Ende eines Satzes wird vom Parser mit dem Symbol $ markiert. Wenn α das letzte Wort sein kann, gehört die Endmarkierung $ zu Follow(α). Algorithmen zur Bestimmung der First- und Followmengen, sind in [Par04] zu finden. • Behauptung Die entwickelte Grammatik erfüllt die LL(1)-Bedingung! 1 nach [Par04] 2.3. INHALTLICHE ANALYSE 19 • Beweis Da der Beweis sehr lang ist und wenig neue Informationen mit sich bringt, ist er in Anhang A.2 zu finden. Die wichtigsten Erkenntnisse sollen hier aber kurz aufgelistet werden: – Bei der Überprüfung der <operation>-Produktionen zeigte sich, dass Probleme beim Parsen auftreten, wenn die Bezeichnung einer Relation gleich der Bezeichnung einer Operation ist: First(SELECTION <phySingle> (<conditionSelection>) <start> <aliasRelation>) = {selection}, First(RELAT ION <aliasRelation>) Der Ausdruck RELAT ION steht für den Namen einer Relation und ist ein terminales Zeichen, das zur Laufzeit bestimmt werden muss. Damit die beiden Firstmengen, wie in der LL(1)-Bedingung gefordert, disjunkt sind, muss der Name der Relation sich von der Operatorbezeichnung unterscheiden. – Ein ähnliches Problem tritt bei den Produktionen für das nichtterminale Symbol <start> auf: First((<operation>)) = {(}, First([<varRelation>]) = {[} und First(RELAT ION ) = {RELAT ION } Damit kein Fehler beim Parsen entsteht, darf eine Relationsbezeichnung nicht ’(’ oder ’[’ sein. Das Ergebnis dieser Arbeit, die fertige Grammatik, ist in Anhang A.1 zu finden. 2.3 Inhaltliche Analyse Nachdem die syntaktische Analyse abgeschlossen ist, wird der Anfragesatz auf inhaltliche Korrektheit überprüft. Dabei wird z. B. untersucht, ob Aliasnamen bereits verwendet werden und ob die Attribute einer Operationsparametrisierung in der bzw. den Argumentrelationen vorhanden sind. Bei komplexen Selektionsbedingungen, deren Prädikate mehrfach durch ∧, ∨ oder ¬ verknüpft sind, treten häufig redundante Ausdrücke auf. Außerdem dauert die Optimierung eines Ausführungsplanes umso länger, je komplexer solche Bedingungen sind. Aus diesen Gründen wird beim Parsen eine disjunktive Minimalform aus allen Selektionsbedingungen erstellt. 2.3.1 Vereinfachung von komplexen Selektionsbedingungen Nachdem geprüft wurde, dass sich die Attribute einer Selektionsbedingung auf die Argumentrelation bzw. Argumentrelationen beziehen, wird der Bedingungsterm mit Hilfe der Quine-McCluskey-Methode in eine Minimalform transformiert. Dafür wird die Bedingung zuerst in eine disjunktive Form gebracht, d. h. es wird ein Term aus Disjunktionen von Konjunktionen aufgebaut. Beispiel: Atomare Bedingungen seien durch p1 , ... , pn notiert. p1 ∧ (p2 ∨ p3 ) → (p1 ∧ p2 ) ∨ (p1 ∧ p3 ) 20 KAPITEL 2. RELATIONALER PARSER Mit Hilfe der disjunktiven Form kann eine Wertetabelle aufgebaut werden, die benötigt wird, um mit dem Quine-McCluskey-Verfahren beginnen zu können. Nach [Bart03] unterteilt sich der Ablauf des Algorithmus in drei Phasen: 1. Erstellen der ersten Quine’schen Tabelle Definition: Ordnung Die Ordung i entspricht den Ergebnissen der i’ten Iteration des Algorithmus. Die Ausdrücke innerhalb jeder Ordnung sind sortiert nach der Anzahl der in ihnen vorkommenden nicht negierten, atomaren Bedingungen. Schritt 1: Erzeugung der Ordnung 0 Die Minterme, dies sind Terme für die die Bedingungsfunktion wahr ist, werden nach der Anzahl der in ihnen vorkommenden, nicht negierten, atomaren Bedingungen geordnet. Sind in der Wertetabelle keine Minterme zu finden, wird der Algorithmus abgebrochen und f alse ausgegeben. Schritt 2: Erzeugung der Ordnung i + 1 Es werden jeweils zwei Ausdrücke gesucht, die sich nur in einer Belegung unterscheiden, und durch Ersetzen der unterschiedlichen Belegungen zu einem ’don0 t care T erm’, zusammengefasst. Die zusammengefassten Ausdrücke bilden die nächste Ordnung. Durch die Anordnung der Minterme müssen nur Zeilen in benachbarten Gruppen einer Ordnung verglichen werden. Zwei Ausdrücke aus denen ein neuer entstanden ist, werden markiert, nehmen aber weiterhin an den Vergleichen teil. Entsteht ein Ausdruck der ausschließlich ’don0 t care T erme’ enthält, endet der Algorithmus und es wird true ausgegeben. Beispiel: In den Tabellen zur Darstellung einer Ordnung, wird die letzte Spalte verwendet, um Ausdrücke aus denen ein neuer entstanden ist, mit x zu markieren. Die Wertetabelle ist durch die Selektionsbedingung f in disjunktiver Form gegeben: f = (¬p2 ∧ ¬p1 ∧ ¬p0 ) ∨ (¬p2 ∧ p1 ∧ p0 ) ∨ (p2 ∧ ¬p1 ) ∨ (¬p2 ∧ p1 ) Bestimmung der 0’ten Ordnung: Nr. 0 p2 0 p1 0 p0 0 f 1 1 0 0 1 0 2 0 1 0 1 3 0 1 1 1 4 1 0 0 1 5 1 0 1 1 6 1 1 0 0 7 1 1 1 0 Gruppe 0 1 1 2 2 Nr. 0 2 4 3 5 p2 0 0 1 0 1 p1 0 1 0 1 0 p0 0 0 0 1 1 x x x x x Bestimmung der 1’ten Ordnung: Wertetabelle Gruppe 0 0 1 1 Nr. 0,2 0,4 2,3 4,5 p2 0 0 1 p1 0 1 0 p0 0 0 - - 2. Bestimmung der Primimplikanten Es werden die nicht verwendeteten Ausdrücke der ersten Phase in eine boolesche Form übertragen. Dies sind die Primimplikanten. 2.3. INHALTLICHE ANALYSE 21 Beispiel: (Fortsetzung) In diesem Beispiel wurden die Ausdrücke mit der Nummer (0,2), (0,4), (2,3) und (4,5) nicht verwendet: Ausdruck Boolesche Form 0-0 ¬p2 ∧ ¬p0 -00 ¬p1 ∧ ¬p0 01¬p2 ∧ p1 10p2 ∧ ¬p1 3. Bestimmung der Kern-Primimplikanten Die Primimplikanten werden zusammen mit den Nummern der Minterme, aus denen sie hervorgegangen sind, in die 2. Quine’sche Tabelle eingetragen. Diese wird auch Überdeckungstabelle genannt. Beispiel: (Fortsetzung) Die Spalten mit der Nummer heitstabelle. Ausdruck ¬p2 ∧ ¬p0 ¬p1 ∧ ¬p0 ¬p2 ∧ p1 p2 ∧ ¬p1 0 bis 7 stehen für die überdeckten Minterme der Wahr0 x x 1 2 x 3 4 5 6 x x x x x 7 Kosten a b c d Die Aufgabe besteht darin, eine Überdeckung aller Minterme mit möglichst wenigen Primimplikanten zu erreichen. Die Überdeckungsfunktion entsteht aus der konjunktiven Verknüpfung der Spalten der Minterme, wobei die markierten Ausdrücke disjunktiv verbunden werden. Aus der vereinfachten Überdeckungsfunktion ergibt sich die disjunktive Minimalform bzw. alternative Minimalformen, da das Ergebnis nicht eindeutig sein muss. In solchen Fällen wird die Auswahl des realiserenden Terms abhänging von einer Kostenfunktion getroffen. In dieser Arbeit wird folgende Funktion verwendet: Negation kostet eine Einheit, jede Verknüpfung kostet eine Einheit. Beispiel: (Fortsetzung) Vollständige Überdeckungsfunktion: u = (a ∨ b) ∧ (a ∨ c) ∧ c ∧ (b ∨ d) ∧ d Vereinfachte Überdeckungsfunktion: u = (a ∧ c ∧ d) ∨ (b ∧ c ∧ d) Auswahl des realisierenden Terms: Aus der vereinfachten Überdeckungsfunktion ergeben sich zwei Möglichkeiten, um eine disjunktive Minimalform aufzubauen. Die Auswahl wird mit Hilfe der beschriebenen Kostenfunktion getroffen: K(a ∧ c ∧ d) = (2 + 2) + (2 + 1) + (2 + 1) = 10 K(b ∧ c ∧ d) = (2 + 2) + (2 + 1) + (2 + 1) = 10 Da beide Realisierungen die gleichen Kosten verursachen, wird eine beliebige ausgewählt: DM F (b ∧ c ∧ d) = (¬p1 ∧ ¬p0 ) ∨ (¬p2 ∧ p1 ) ∨ (p2 ∧ ¬p1 ) 22 KAPITEL 2. RELATIONALER PARSER 2.3.2 Operationsvereinfachung Es kann, besonders nach der Minimierung einer Selektionsbedingung vorkommen, dass eine Operation ausgeführt werden soll, die wenig Sinn macht. Z. B. wäre ein Selektion mit der Bedingung true schlecht, da die komplette Argumentrelation unnötig gelesen wird, also ersetzt man σtrue (R) durch R. Diese Art der Vereinfachung kann bei fast allen Operatoren, unter gewissen Voraussetzungen, ausgeführt werden. In Tabelle 2.5 sind die Operatoren mit den kritischen Bedingungen und die vereinfachte Form dargestellt: Anfrage relationaler Ausdruck Ersetzung SELECT ION true R σtrue (R) R SELECT ION f alse R σf alse (R) ∅ (LEF T /RIGHT OU T ER-)JOIN true R S R ./true S R×S JOIN f alse R S R ./f alse S ∅ RIGHT OU T ERJOIN f alse R S R ./@f alse S S LEF T OU T ERJOIN f alse R S R A./f alse S R SEM IJOIN true R S R .<true S R SEM IJOIN f alse R S R .<f alse S ∅ AN T ISEM IJOIN true R S R .<true S ∅ AN T ISEM IJOIN f alse R S R .<f alse S R Tabelle 2.5: Vereinfachung von Operationen Eine Selektion mit der Bedingung f alse gibt kein Tupel zurück, d. h. das Ergebnis ist eine leere Relation, im folgenden Null-Relation genannt. Im Allgemeinen wird aus einer Verbundoperation mit der Bedingung true ein kartesisches Produkt, da jedes Tupel mit jedem verbunden wird. Bei der Bedingung f alse ergibt sich wieder eine Null-Relation, außer bei einem Left- bzw. Right-Outerjoin, da hier die Tupel der linken bzw. rechten Relation auch ohne Joinpartern ausgegeben werden. Weitere Ausnahmen sind Semi- und Antisemijoin, da sie eher einer Selektion ähneln. Wird ein Semijoin mit true ausgeführt, ist das Ergebnis die komplette linke Argumentrelation. Ist die Bedingung hingegen f alse ergibt sich die Null-Relation. Für den Antisemijoinoperator gelten die selben Argumentationen nur mit vertauschten Bedingungen. Kapitel 3 Regelparsing Wie in der Einleitung beschrieben wurde, gehören zu einer Termersetzungsregel zusätzlich zu den beiden Regeltermen eine oder mehrere Regelbedingungen. Diese Bedingungen geben an, wann eine Termersetzung sinnvoll ist. An der Regel in Abbildung 3.1 ist zu sehen, dass sie ebenfalls zum Zuweisen von Werten verwendet werden. τ1 := πlRel (R1 × R2 ) 1 τ2 := πlRel ((πlRel R1 ) × (πlRel R2 )) 1 2 3 2 1) falls cost(τ2 ) < cost(τ1 ), 2) falls cost(τ1 ) < cost(τ2 ), l1 = l, l = l1 ∪ l2 ∪ l3 l2 = l ∩ sch(R1 ), l3 = l ∩ sch(R2 ) Abbildung 3.1: Termersetzungsregel Im vorherigen Kapitel wurde beschrieben, wie Regelterme eingegeben werden. Dieses Kapitel beschäftigt sich mit der Eingabe der einzelnen Bedingung und der abschließenden inhaltlichen Prüfung einer Regel. In der Arbeit [Rud05] beschäftigt sich Herr Rudnicki mit einem Termersetzer für ein Einphasenoptimierungssystem und den dazugehörigen Termersetzungsregeln. Mit ihm zusammen wurde der Aufbau von Regelbedingungen und die darin verwendeten Bezeichnungen festgelegt. 3.1 Bedingungsarten Die in Regeln auftretenen Bedingungen lassen sich in mehrere, voneinander getrennte Klassen einteilen. Die verwendeten Symbole <attributmenge>, <bedingung> und <relation> stehen für Variablenbezeichnungen wie sie in Termersetzungsregeln vorkommen und nicht für konkrete Werte. Dabei sind mit <bedingung> Bezeichnungen für Selektions- oder Verbundbedingungen gemeint. 3.1.1 Existenzbedingungen Mit dieser Art von Regelbedingung kann geprüft werden, ob Sortierungen oder Indexe vorhanden sind und ob sie bei relationalen Operationen genutzt werden können. Eine Existenzbedingung hat immer die Form: <negation> <existenzf unktion> 23 24 KAPITEL 3. REGELPARSING • <existenzf unktion> Es können mehrere Funktionen verwendet werden, deren Ergebnis ein Wahrheitswert, also true oder f alse, ist: 1. index supported(<attributmenge>, <relation>): Diese Funktion liefert true, falls auf einer Relation ein Index auf der übergebenen Attributmenge vorhanden ist. 2. merge supported(<bedingung>, <relation>, <relation>): Um einen Merge-Join anwenden zu können, ist es nicht ausreichend, dass die Argumentrelationen auf den Joinattributen sortiert sind. Es werden weitere Anforderungen an den Aufbau der Verbundbedingung gestellt. Diese werden genauer in Kapitel 5.4.3 erläutert. Mit dieser Funktion kann überprüft werden, ob ein Merge-Join zweier Relationen mit Hilfe einer Verbundbedingung möglich ist. 3. sorted(<attributmenge>, <relation>): Mit dieser Funktion kann man herrausfinden, ob eine Relation auf einer Attributmenge sortiert ist. • <negation> Das Ergebnis der verwendeten Funktionen kann mit dem Wort N OT negiert werden. Beispiele: merge supported(p, R1, R2), N OT index supported(l, R) 3.1.2 Eigenschafts- und Zuweisungsbedingungen In dieser Bedingungsart wird mit Attributmengen gearbeitet. Es kann der Schnitt und die Vereinigung zweier Mengen bestimmt werden. Desweiteren kann man in diesen Bedingungen Variablen erzeugen und ihnen einen Wert zuweisen. Die Form einer solchen Regelbedingung ist: <element> <berechnung> <operator> <element> <berechnung> • <element> Die Grundelemente sind: 1. attr(<bedingung>): Mit dieser Funktion werden die in einer Selektions- oder Verbundbedingung enthaltenen Attribute beschrieben. 2. sch(<relation>): Das Schema einer Relation wird mit dieser Funktion dargestellt. 3. <attributmenge>: Attributmengenbezeichnungen können ebenfalls als Grundelemente verwendet werden. Die leere Attributmenge wird mit dem Wort null dargestellt. • <berechnung> Es ist möglich den Schnitt oder die Vereinigung der aufgeführten Grundelemente zu bilden. Die Operatoren ∪ und ∩ werden durch union und intersect eingegeben. • <operator> In jeder dieser Bedingungen muss ein Vergleichs- oder Zuweisungsoperator auftreten: 3.1. BEDINGUNGSARTEN 25 1. <vergleichsoperator>: Um Attributmengen vergleichen zu können werden die Operatoren ⊇, ⊃, =, 6=, ⊂ und ⊆ bereitgestellt. Um zu kennzeichnen, dass ein Vergleich und keine Zuweisung gefordert ist, wird = eingegeben durch ==. Die restlichen Operatoren werden notiert durch ]=, ], !=, [ und [=. Bei einem Vergleichsoperator können auf beiden Seiten Vereinigungen, Schnitte und Attributmengen verwendet werden, beispielsweise: attr(p) intersect sch(R) != null oder attr(q) [= sch(R) 2. <zuweisungsoperator>: Um Variablen einen Wert zuzuweisen, wird ein einfaches Gleichheitszeichen verwendet. Bei Zuweisungsbedingungen ist links vom Zuweisungsoperator nur eine Variablenbezeichnung für eine Attributmenge zugelassen. Auf der rechten Seite wird die Berechnung des Wertes notiert, z. B.: l = attr(p) union sch(R) 3.1.3 Aufteilen und Zuweisen von Selektions- oder Verbundbedingungen In einigen Termersetzungsregeln ist es nötig, logisch verknüpfte Selektions- oder Verbundbedingungen aufzuteilen. Um dies zu ermöglichen wird diese Regelbedingungsart zur Verfügung gestellt. Der Aufbau ist folgendermaßen: <bedingung> <zuweisungsoperator> <f unktion> <verknüpf ung> • <bedingung>: Auf der linken Seite einer solchen Regelbedingung muss eine Variablenbezeichnung für eine Selektions- oder Verbundbedingung stehen. Darauf folgt ein ’=’ als Zuweisungsoperator. • <f unktion>: Die Voraussetzung, um die folgenden Funktionen anwenden zu können, ist, dass die atomaren Bedingungen ausschließlich durch ∧ verknüpft sind. 1. f etch condition(<bedingung>, <relation>): Diese Funktion liefert die atomaren Teile einer Selektionsbedingung, welche sich ausschließlich auf die übergebene Relation beziehen. Beispielsweise: p := (R1 .A = R2 .D ∧ R1 .A =’5’ ∧ R1 .B ≥’7’ ∧ R2 .D =’10’) f etch condition(p, R1 ) = (R1 .A =’5’ ∧ R1 .B ≥’7’) f etch condition(p, R2 ) = (R2 .D =’10’) 2. f etch joinCondition(<bedingung>, <relation>, <relation>): Mit dieser Methode werden die Teile einer Selektionsbedingung, welche sich als Verbundbedingung zwischen zwei Relationen eignen, ausgegeben, z. B: p := (R1 .A = R2 .D ∧ R1 .A =’5’ ∧ R1 .B ≥’7’ ∧ R2 .D =’10’) f etch joinCondition(p, R1 , R2 ) = (R1 .A = R2 .D) 26 KAPITEL 3. REGELPARSING 3. f etch f irst(<bedingung>): Diese Funktion bestimmt die erste atomare Formel einer Selektionsbedingung. 4. f etch rest(<bedingung>): Der Rest der Selektionsbedingung kann mit dieser Methode gefunden werden. • <verknüpf ung>: Auf der rechten Seite einer Zuweisungsbedingung können Selektions- und Verbundbedingungen verknüpft werden durch ∧, ∨ und ¬, notiert durch AN D, OR und N OT , z. B.: p = f etch f irst(p) AN D f etch rest(p), q = p0 OR p1 OR p2 Soll aus einer Bedingung ein Teil entfernt werden, der durch eine der gegebenen Funktionen zuvor identifiziert wurde, kann der Operator ’−’ genutzt werden. Beispiel: p1 = f etch joinCondition(p, R1, R2) p2 = p − p1 3.1.4 Bestimmung von Vergleichsoperatoren Für manche Termersetzungsregeln ist es nötig die in Selektions- oder Verbundbedingungen verwendeten Vergleichsoperatoren zu kennen. Man erhält sie mit Hilfe der Funktion op(<bedingung>). Mehrfach vorhandene Operatoren werden nicht mit ausgegeben. Eine Regelbedingung dieser Art hat immer die Form: op(<bedingung>) = {<operatoren>} Innerhalb der geschweiften Klammern können, durch Kommata getrennt, mehrere der Operatoren >=, >, =, ! =, <, <= vorkommen. 3.1.5 Kostenbedingungen Diese Art von Regelbedingung kann erst überprüft werden, wenn mit Hilfe der Regel eine Variante erzeugt wurde. Dies ist eine Ausnahme. Die anderen Bedingungsarten können geprüft werden, ohne einen neuen Term erzeugen zu müssen. Eine Kostenbedingung hat die Form: <multiplikator> <kostenf unktion> <vergleich> <multiplikator> <kostenf unktion> • <kostenf unktion>: Zur Erzeugung einer solchen Bedingung stehen folgende Funktionen zur Verfügung: 1. card(<relation>): Mit dieser Funktion wird die Kardinalität einer Relation beschrieben. 2. page(<relation>): Die Pagegröße einer Relation wird mit dieser Funktion angegeben. 3. cost(<baum>): Diese Funktion wird genutzt um die Kosten des ursprünglichen Baumes mit denen des neu entstanden zu vergleichen. Der Baum, der zu der rechten Seite einer Regel gehört, wird mit right eingegeben. Entsprechend wird der andere Baum mit left notiert. 3.2. ENTWICKLUNG EINER GRAMMATIK 27 4. sel(<bedingung>, <relation>): Mit Hilfe dieser Funktion lässt sich die Selektivität einer Selektionsbedingung auf einer Relation bestimmen. • <multiplikator>: Die Ergebnisse der aufgeführten Funktionen lassen sich mit einem Multiplikator gewichten, z. B.: 5 ∗ cost(lef t) <= cost(right) • <vergleich>: Für den Vergleichsoperator gibt es die Möglichkeiten ≥, >, =, <, ≤, notiert durch >= , >, =, <, <=. 3.2 Entwicklung einer Grammatik Die Sprache zur Eingabe von Regelbedingungen wurde auf die selbe Art und Weise entwickelt wie für den relationalen Parser. Die Elemente der Sprache und der Aufbau der verschiedenen Bedingungsarten wurde im vorherigen Abschnitt dargestellt. Mit dieser Grundlage wurde eine erste Version einer kontextfreien Grammatik erstellt. Wie bei der Grammatik des relationalen Parsers traten auch hier linksrekursive Produktionen und Produktionen mit gemeinsamen Präfixen auf. Es wurden erneut die Funktionen aus [Par04] verwendet um diese Probleme zu beseitigen. Zum Abschluss wurde diese Grammatik ebenfalls auf die LL(1)-Bedingung hin überprüft. Im Anhang ist die Überprüfung und die endgültige Version der Grammatik aufgeführt. 3.3 Semantische Überprüfung einer Termersetzungsregel Wurden die beiden Regelterme und die Regelbedingungen erfolgreich geparst, müssen sie auf inhaltliche Richtigkeit hin überprüft werden. Zuerst wird geprüft, ob in beiden Regelbäumen die selben Relationenbezeichnungen verwendet werden. Beispielsweise würde bei der Termersetzungsregeln in Abbildung 3.1 überprüft werden, ob in den beiden Regeltermen τ1 und τ2 die Bezeichnungen R1 und R2 vorkommen. Es muss sichergestellt sein, dass bei einer Termersetzung auf beiden Regelseiten jeder Variablen ein Wert zugewiesen werden kann. Entweder wird die selbe Variablenbezeichnung auf beiden Seiten verwendet, d. h. der Termersetzer kann, falls er in einem Ausführungsplan eine Übereinstimmung mit einer Regelseite findet, die Werte direkt der entsprechenden Variablen der anderen Seite zuweisen. Zu sehen ist dies in Abbildung 1.4. Dort werden auf beiden Seiten die selben Variablenbezeichnungen p und l verwendet. Wird einer Variablen nicht auf diese Weise ein Wert zugewiesen, muss dies in der Regel über eine Zuweisungsbedingung geschehen, so wie es in der Ersetzungsregel in Abbildung 3.1 der Fall ist. Abschließend wird überprüft, ob die Parameter der Funktionen innerhalb der Regelbedingungen den richtigen Typ haben, z. B. muss p bei der Funktion attr(p) eine Selektions- oder Verbundbedingung sein. Ist p in einem Regelbaum als Attributmenge deklariert, entstände ein Fehler. 28 KAPITEL 3. REGELPARSING Kapitel 4 Generalisierte Bäume Für eine Einphasenoptimierung wird als Basis eine Datenstruktur benötigt, die den Ausführungsplan und sämtliche Daten, die für eine Optimierung benötigt werden, speichert. Diese Struktur ist der generalisierte Baum, in dem jeder relationale Operator in einem Knoten gespeichert wird. Die Attribute der einzelnen Knoten eines Anfragebaumes werden in den nächsten Abschnitten dargestellt. Ersetzungsregeln, die in einem Termersetzer verwendet werden, bestehen aus zwei Ausdrücken. Nach dem einen wird in einem Ausführungsplan gesucht und der andere gibt die Struktur vor, in die der gefundene Teil umgeformt werden soll. Um diese Terme zu speichern wird ebenfalls eine Form des generalisierten Baumes verwendet. Dieses Thema wird im 6. Abschnitt dieses Kapitels behandelt. Damit ein Termersetzer Varianten aus einem Ausführungsplan erzeugen kann, werden Methoden zum Bearbeiten eines generalisierten Baumes benötigt. In Kapitel 4.7 werden diese aufgezählt und beschrieben. 4.1 Knotenattribute Jeder Knoten und jedes Blatt im generaliserten Baum enthält eine Reihe von relationenbezogenen Daten, Metainformationen und Funktionen zum Bestimmen dieser. Die folgenden Attribute werden bei der Erzeugung eines Knoten gesetzt bzw. berechnet. Die dafür benötigten Formeln und ein Beispiel zur Berechnung der Kosten sind in Kapitel 5 zu finden. Attribute die beim Erzeugen eines Knotens gesetzt werden müssen: • Kindknoten Dem erzeugten Knoten muss die Argumentrelation bzw. Argumentrelationen, falls es sich um einen binären Operator handelt, bekannt sein. Daher kann ein Knoten erst nach der Erstellung seiner Kinder erzeugt werden. • Physische Umsetzung Für jeden relationalen Operator gibt es eine Reihe von physischen Umsetzungen, z. B. kann eine Selektion mit einem Relationenscan oder einem Indexzugriff realisiert werden. Eine der Möglichkeiten sollte ausgewählt und bei der Erzeugung angegeben werden. Ansonsten wird eine kostenabhängige Auswahl der physischen Realisierung getroffen. Dies wird im nächsten Abschnitt dieses Kapitels genau erklärt. 29 30 KAPITEL 4. GENERALISIERTE BÄUME • Parametrisierung Zu den meisten Operatoren wird in einer Anfrage eine Parametrisierung angegeben, beispielsweise SELECT ION S.Semester=’5’ S. Bei Mengenoperatoren wird keine Parametrisierung benötigt. Allerdings müssen für ihrer Anwendung andere Vorraussetzungen erfüllt sein, aufgeführt in Kapitel 2.1.2. Attribute die berechnet werden: • Attribute der Ergebnisrelation Es handelt sich hierbei um die Attribute einer Relation, wie sie in Kapitel 2.1.1 beschrieben wurden, und nicht um Knotenattribute. Die Menge der Attribute einer Relation, welche nach Anwendung einer relationalen Operation, z. B. einem Join, vorhanden ist, wird in dem Knoten der ausgeführten Operation gespeichert. • Kardinalität Dieser Wert gibt die Anzahl der Tupel der Ergebnisrelation an. • Attributwertanzahlen Für jedes Attribut der Ergebnisrelation wird die Anzahl verschiedener Werte nach Anwendung der entsprechneden Operation abgeschätzt und gespeichert. • Pagegröße Die Pagegröße gibt den von der Relation belegten Speicherplatz an. Sie ergibt sich aus der Anzahl und der Länge der Tupel. • Kosten Die CPU- und I/O-Kosten, die eine Operation verursacht, werden ebenfalls mit Funktionen im Knoten berechnet und gespeichert. Die Kostenformeln werden in Kapitel 5.4 aufgeführt und begründet. • Sortierungen Nach der Anwendung einer Operation ist es möglich, dass Sortierungen erhalten bleiben oder neu entstehen, die im weiteren Verlauf für eine Optimierung genutzt werden können. Dies wird von den einzelnen Knoten während der Laufzeit überprüft. Die Menge der sortierten Attribute wird im zugehörigen Knoten in einer Liste gespeichert. • Pipelining Pipelining stellt eine Möglichkeit zur Verringerung der Kosten dar, bei der Zwischenergebnisse nicht gespeichert, sondern tupelweise zum nächsten Operator übergeben werden. Dadurch entfallen die Input/Output-Kosten für diese Relation bei der folgenden Operation. Es wird von den einzelnen Knoten geprüft, ob Pipelining möglich ist. 4.2 Kostenabhängige Auswahl der physischen Umsetzungen Es ist möglich die physische Umsetzung eines Operators, abhängig von den lokalen Kosten, bestimmen zu lassen. Diese Möglichkeit wird z. B. bei dem vom relationalen Parser gelieferten Baum ausgenutzt, denn so erhält man eine Startkonfiguration mit der im Termersetzer neuentstandene Ausführungspläne verglichen werden können. Steigen die Gesamtkosten einer Variation zu stark an, kann der entsprechende Anfrageplan ausgeschlossen werden. Die 4.3. SORTIERUNGEN 31 genaue Arbeitsweise des Termersetzers ist ein komplexes Thema und wird in [Rud05] behandelt. Die Bestimmung der physischen Umsetzungen lässt sich am Besten an einem einfachen Beispiel zeigen. Die Daten über die Basisrelationen und eine detailierte Berechnung der Kosten sind in Kapitel 5.5 zu finden. Aus der folgenden Anfrage soll von dem relationalen Parser ein generalisierter Baum erzeugt und dessen Kosten berechnet werden. Dieses Beispiel beschränkt sich auf I/O-Kosten und es wird kein Pipelining angewandt. P ROJECT ION (V.T itel, H.Count) RIGHT OU T ERJOIN (V.V orlN r = H.V orlN r) V, (AGGREGAT ION (H.V orlN r#H.V orlN r, (count(H.M atrN r) AS H.Count)) H) Zuerst wird die Aggregation auf der Relation H erstellt. Als mögliche physische Umsetzungen ergeben sich: ΓSort g#f 1. ΓRel g#f (H): I/O-Kosten= 186 2. ΓSort g#f (H): I/O-Kosten= 6 H Da die Kosten von ΓSort g#f am geringsten sind, wird die 2. Umsetzungsmöglichkeit gewählt. Als nächstes wird über die Realisierung des Right-Outerjoins entschieden. Zur Auswahl stehen: 1. V ./@Rel−Rel p (ΓSort g#f (H)): I/O-Kosten= 123 ./@pInd−Rel V 2. V ./@pInd−Rel (ΓSort g#f (H)): I/O-Kosten= 85 ΓSort g#f H Ausgewählt wird ein Index-Rel-Join, da die entstehenden Kosten bei dieser Variante am geringsten sind. Im letzten Schritt wird über die physische Umsetzung der Projektion entschieden. Es gibt allerdings nur die Möglichkeit einen Relationenscan auszuführen, da weder nutzbare Indexe noch Sortierungen vorhanden sind: 1. πlRel (V ./@pInd−Rel (ΓSort g#f (H))): I/O-Kosten= 257 So entsteht folgender generalisierter Baum mit Gesamtkosten von 6 + 85 + 257 = 348: Rel πV.T itel,H.Count Ind−Rel ./@(V.V orlN r=H.V orlN r) V ΓSort (H.V orlN r#H.V orlN r,count(H.M atrN r)) H 4.3 Sortierungen In diesem Abschnitt sind die Formeln für den Erhalt oder die Entstehung von Sortierungen aufgeführt. 32 KAPITEL 4. GENERALISIERTE BÄUME Zur Darstellung wird eine Funktion sorted(R, A) verwendet, die ’true’ liefert, falls die übergebene Relation R auf der Attributmenge A sortiert ist. Sonst ist die Rückgabe ’f alse’. • Selektion Bei einem Relationenscan bleiben Sortierungen erhalten, da die Argumentrelation tupelweise von Anfang bis Ende eingelesen wird: sorted(σpRel (R), A) = sorted(R, A) Ist ein Indexzugriff möglich, müssen zwei Attributmengen unterschieden werden. Die Menge der Attribute A die in dem Index enthalten sind und die restliche Attribute B der Argumentrelation. Die Attribute A werden sortiert ausgegeben, da die Tupel im Index geordnet gespeichert sind. Bei den Attributen B der Relation kann nicht davon ausgegangen werden das Sortierungen erhalten bleiben oder gar neu entstehen: sorted(σpIndex (R), A) = true sorted(σpIndex (R), B) = f alse • Projektion Wird ein Relationenscan für eine Projektion verwendet, gilt die selbe Argumentation wie bei einer Selektion. Da die Argumentrelation tupelweise gelesen wird, bleiben Sortierungen auf den projizierten Attributen A erhalten: sorted(πlRel (R), A) = sorted(R, A) Um π Sort verwenden zu können, ist Voraussetzung, dass die zu projizierenden Attribute A sortiert sind. Nach Ausführung der Projektion bleiben diese Sortierungen erhalten. In einem Index sind die Tupel nach ihren Attributwerten geordnet, daher ist die Ausgabe auf diesen Attributen ebenfalls sortiert: sorted(πlSort (R), A) = sorted(πlIndex (R), A) = true • Aggregation Bei den Attributen A, die durch Aggregations-Funktionen wie z. B. count(∗), neu entstanden sind, kann nicht davon ausgegangen werden, dass Sortierungen entstehen. Falls eine Gruppierung verlangt wird und die dafür verwendeten Attribute ebenfalls projiziert werden, kann eine Sortierung nur auf diesen Attributen B erhalten bleiben. Eine genaue Beschreibung der Arbeitsweise einer Aggregation ist in Kapitel 5.4.2 zu finden. sorted(ΓRel g#f (R), A) = f alse sorted(ΓRel g#f (R), B) = sorted(R, B) Für ΓSort g#f (R) gilt das selbe. Da hier Voraussetzung ist, dass die Attribute nach denen gruppiert wird, sortiert sind, ist eine Sortierung in der Ergebnisrelation vorhanden, falls auf diese Attributmenge ebenfalls projiziert wird. sorted(ΓSort g#f (R), A) = f alse sorted(ΓSort g#f (R), B) = true 4.3. SORTIERUNGEN 33 • Join Im Folgenden seien B ∈ attr(p) ∩ sch(R1 ) die Joinattribute die sich auf R1 und C ∈ attr(p) ∩ sch(R2 ) diejenigen die sich auf R2 beziehen. A und D sind die Attribute der einzelnen Relationen ohne die zugehörigen Joinattribute. Bei einem Nested-Loop-Join wird die Reihenfolge der Tupel aus R1 nicht verändert und daher bleiben sortierte Spalten weiterhin sortiert: sorted(R1 ./pN ested−Loop R2 , A) = sorted(R1 , A) sorted(R1 ./pN ested−Loop R2 , B) = sorted(R1 , B) Ist R1 auf einem Joinattribut B sortiert und die Joinbedingung p enthält ausschließlich Gleichheitsbedingungen, dann wird das dazugehörige Attribut C aus R2 ebenfalls im Ergebnis des Joins sortiert sein. Ansonsten kann man nicht annehmen, dass Sortierungen entstehen oder erhalten bleiben. Das selbe gilt für einen Rel-Index-Join. falls p aus Gleichheitsbedin sorted(R1 , B) gungen besteht sorted(R1 ./pN ested−Loop R2 , C) = f alse sonst sorted(R1 ./pN ested−Loop R2 , D) = f alse Damit ein Merge-Join angewandt werden kann, müssen die Argumentrelationen auf den Joinattributen sortiert sein und die Joinbedingung darf nur Gleichheitsprädikate enthalten. Da er die Reihenfolge der Tupel nicht ändert, bleiben die Sortierungen der beiden Relationen erhalten: sorted(R1 ./pSort−Sort R2 , A) = sorted(R1 , A) sorted(R1 ./pSort−Sort R2 , B) = true sorted(R1 ./pSort−Sort R2 , C) = true sorted(R1 ./pSort−Sort R2 , D) = sorted(R2 , D) Bei einem Index-Index-Join werden die Tupel nach den Joinattributen sortiert, da die Werte der Reihe nach von den Indexen geliefert werden. Sortierungen auf NichtJoinattributen gehen dabei im Allgemeinen verloren: sorted(R1 ./pIndex−Index R2 , A) = f alse sorted(R1 ./pIndex−Index R2 , B) = true sorted(R1 ./pIndex−Index R2 , C) = true f alse falls p aus Gleichheitsbedingungen besteht sonst sorted(R1 ./pIndex−Index R2 , D) = f alse • Anti-/Semijoin Die Formeln für Semijoins enthalten als zusätzliche physische Realisierung Index-Rel, da hier die Relationen nicht kommutativ sind. Ansonsten stimmten sie mit den vorherigen Funktionen für ein Join überein. Allerdings bleiben keine Attribute aus R2 erhalten, da diese nur darüber entscheiden, ob ein Tupel aus R1 erhalten bleibt oder nicht: 34 KAPITEL 4. GENERALISIERTE BÄUME sorted(R1 .<pN ested−Loop R2 , A) = sorted(R1 , A) sorted(R1 .<pN ested−Loop R2 , B) = sorted(R1 , B) sorted(R1 .<pSort−Sort R2 , A) = sorted(R1 , A) sorted(R1 .<pSort−Sort R2 , B) = true sorted(R1 .<pIndex−Rel R2 , A) = sorted(R1 .<pIndex−Index R2 , A) = f alse sorted(R1 .<pIndex−Rel R2 , B) = sorted(R1 .<pIndex−Index R2 , B) = true Bei einem Index-Rel-Semijoin werden die Tupel aus R1 nach den Joinattributen sortiert ausgegeben, da der Index die Tupel der Reihe nach ausgibt. Vorherige Sortierung bleiben dabei nicht erhalten. Also unterscheiden sich die Formeln nicht von denen eines Index-Index-Semijoins. • Left-/Right-Outerjoin Bei einem Left-Outerjoin werden die Tupel der linken Relation zum Ergebnis hinzugefügt, auch wenn sie keinen Joinpartner gefunden haben. Daher bleiben Sortierungen, die auf R1 existieren, bestehen. Da die linke Relation komplett ausgelesen wird, macht dabei ein Indexzugriff keinen Sinn. Bei der rechten Argumentrelation können durch das Einfügen von Null-Werten keine Sortierungen entstehen oder erhalten bleiben. Die Attribute aus R1 werden mit A und die aus R2 werden mit B gekennzeichnet: sorted(R1 A./pN ested−Loop R2 , A) = sorted(R1 A./Rel−Index R2 , A) = sorted(R1 , A) p sorted(R1 A./pN ested−Loop R2 , B) = sorted(R1 A./Rel−Index R2 , B) = f alse p Um einen Merge-Algortihmus ausnutzen zu können, müssen die Joinattribute der Relationen sortiert sein, d. h. dass die Joinattribute C aus R1 im Ergebnis in jedem Fall sortiert sind: sorted(R1 A./pSort−Sort R2 , A) = sorted(R1 , A) sorted(R1 A./pSort−Sort R2 , C) = true sorted(R1 A./pSort−Sort R2 , B) = f alse Für ein Right-Outerjoin gelten die selben Funktionen mit vertauschten Argumentrelationen. • Full-Outerjoin Ein NestedLoop-Outerjoin arbeitet wie ein NestedLoop-Left-Outerjoin, nur werden in einem letzten Schritt die partnerlosen Tupel der zweiten Relation zum Ergebnis hinzugefügt. Aus diesem Grund gelten die selben Formeln für R1 A./@pN ested−Loop R2 : sorted(R1 A./@pN ested−Loop R2 , A) = sorted(R1 , A) sorted(R1 A./@pN ested−Loop R2 , B) = f alse Da beide Relationen komplett ausgelesen werden, macht weder ein A./@Rel−Ind noch ein A./@Ind−Ind Sinn. Bei einem Merge-Full-Outerjoin werden beide Relationen seriell eingelesen. Dabei werden die Tupel ohne Partner direkt zum Ergebnis hinzugefügt. Daher kann man nicht davon ausgehen, dass Sortierungen erhalten bleiben: sorted(R1 A./@pSort−Sort R2 , A) = f alse sorted(R1 A./@pSort−Sort R2 , B) = f alse 4.4. INDEXE 4.4 35 Indexe In einem generalisierten Baum können normale und geclusterte Indexe vorhanden sein. Es wird davon ausgegangen, dass ein Index nach Anwendung einer Operation nicht mehr für deren Ergebnis genutzt werden kann. Der Aufbau von geclusterten Indexen wird in Kapitel 5.4.6 beschrieben. Informationen über dauerhaft vorhandene Indexe auf Basisrelationen werden beim Erzeugen eines Baumes in den Blättern gespeichert. Während der Optimierung ist es möglich, dass zusätzliche temporäre Indexe auf Blättern oder Teilbäumen erzeugt werden. Ist dies der Fall, wird der komplette Baum durchlaufen und nach übereinstimmenden Stellen durchsucht. Wird eine Übereinstimmung gefunden, werden die Indexinformationen ebenfalls zu diesem Knoten hinzugefügt. 4.5 Pipelining In diesem Abschnitt wird gezeigt, wann Pipelining angewandt werden kann. Ein Indexzugriff verhindert grundsätzlich diese Optimierungsmöglichkeit. • Selektion und Projektion: Bei σpRel können nacheinander alle Tupel der Eingabe direkt auf die Bedingung p hin überprüft werden. σp (R) σpRel σpIndex Pipelining von R ja nein πl (R) Pipelining von R πlRel nein πlSort ja πlIndex nein Tabelle 4.1: Pipelining bei Projektionen und Selektionen Für ein Projektion muss im allgemeinen die Argumentrelation mehrfach durchlaufen werden, um Duplikate zu entfernen. Wenn die Relation sortiert ist, kann die Duplikateleminierung mit einem Durchlauf ausgeführt werden. • Aggregation: Werden keine Attribute angegeben, nach denen gruppiert werden soll, ist das Berechnen der Aggregationsfunktionen parallel in einem Durchlauf möglich. Da die Relation komplett eingelesen werden muss, wird grundsätzlich ein Relationenscan verwendet, d. h. die Argumentrelation kann tupelweise übergeben werden. Γ#f (R) ΓRel #f Pipelining von R ja Γg#f (R) Pipelining von R ΓRel g#f nein ΓSort g#f ja Tabelle 4.2: Pipelining bei einer Aggregation Wird nach einem oder mehreren Attributen gruppiert, muss bei einem Relationenscan die Argumentrelation mehrfach durchlaufen werden, also ist keine tupelweise Übergabe möglich. Ist eine Sortierung auf den entsprechenden Attributen vorhanden, kann das Ergebnis wieder mit einem Durchlauf bestimmt werden, da zusammengehörige Tupel 36 KAPITEL 4. GENERALISIERTE BÄUME nur nacheinander auftreten können. Genaue Informationen über die Arbeitsweise einer Aggregation sind in Kapitel 5.4.2 zu finden. • Join: Der Nested-Loop-Join durchläuft in einer Schleife die erste Argumentrelation und für jedes Tupel aus R1 durchläuft er ein Mal die zweite. Dabei können die Tupel von R1 der Reihe nach übergeben werden. Die zweite Relation muss mehrfach gelesen werden, wodurch Pipelining verhindert wird. Die gleiche Argumentation gilt für RelIndex-Joins. Da ein Merge-Join sequentiell auf R1 und R2 arbeitet um ein Ergebnis zu erziehlen, kann Pipelining bei beiden Relationen angewandt werden. R1 ./p R2 Pipelining von R1 Pipelining von R2 ./pN ested−Loop ja nein ./pSort−Sort ./Rel−Index p ./pIndex−Index ja ja ja nein nein nein Tabelle 4.3: Pipelining bei einem Join • Semi- und Antisemijoin: Der Semi- und Antisemijoin arbeitet wie eine normaler Join, nur werden keine Werte aus der zweiten Relation zum Ergebnis hinzugefügt. Mit Hilfe der Tupel aus R2 wird lediglich entschieden, ob ein Tupel aus R1 die Bedingung p erfüllt. R1 .<p R2 Pipeling von R1 Pipelining von R2 .<pN ested−Loop ja nein .<pSort−Sort .<pIndex−Rel .<Rel−Index p Index−Index .<p ja ja nein nein ja nein nein nein Tabelle 4.4: Pipelining bei Semijoins Daraus folgt, dass auch die gleichen Möglichkeiten für Pipelining bestehen. Allerdings gilt in diesem Fall für Index-Rel-Joins nicht das selbe wie für Rel-Index-Joins, da die beiden Argumentrelationen nicht kommutativ sind. Bei einem Index-Rel-Semijoin wird auf die erste Relation mit einem Index zugegriffen, wodurch Pipelining verhindert wird. Da die zweite Relation mehrfach gescannt wird, ist es auch hier nicht anwendbar. • Outerjoins: Die Algorithmen für die Umsetzung eines Left-Outerjoins ähneln denen eines normalen Joins, nur werden die Tupel der linken Relation, die keinen Joinpartner finden, zum Ergebnis hinzugefügt. Daher gelten die selben Arugmentationen für die Anwendung von Pipelining bei diesen physischen Realisierungen. 4.6. VERWENDUNG VON REGELBÄUMEN IN EINEM TERMERSETZER 37 Die Tabelle 3.5 gilt ebenfalls für Right-Outerjoins, allerdings müssen die Argumentrelationen vertauscht werden. R1 A./p R2 Pipeling von R1 Pipelining von R2 A./pN ested−Loop ja nein A./pSort−Sort A./Rel−Index p ja ja ja nein Tabelle 4.5: Pipelining bei Outerjoins Bei einem Full-Outerjoin werden beide Relationen komplett ausgelesen. Aus diesem Grund wird kein A./@Rel−Index verwendet. Sonst stimmen die Formeln mit denen eines p Left-Outerjoins überein. • Mengenoperatoren: Schnitt, Differenz und Produkt lassen sich wie folgt darstellen, wobei die Bedingung p die gleichnamigen Attribute der beiden Argumentrelationen verbindet. Mengenoperation Verbundoperation R1 × R2 R1 ./true R2 R1 − R2 R1 .<p R2 R1 ∩ R2 R1 .<p R2 Tabelle 4.6: Äquivalente Darstellungsformen für Mengenoperatoren Pipelining ist bei diesen Operatoren möglich, falls es bei den Darstellungen mit Hilfe von Verbundoperatoren der Fall ist. Eine Vereinigung nutzt Antisemijoin-Algorithmen zur Berechnung des Ergebnisses. Daher kann Pipelining angewandt werden, wenn es bei den Antsemijoinoperatoren möglich ist. R1 ∪ R2 Pipeling von R1 Pipelining von R2 ∪N ested−Loop ja nein ∪Sort−Sort ja ja ∪Rel−Index ja nein nein nein ∪Index−Index Tabelle 4.7: Pipelining bei Vereinigungen 4.6 Verwendung von Regelbäumen in einem Termersetzer Wie in der Einleitung erwähnt, wird eine veränderte Form des generaliserten Baumes zum Speichern von Termersetzungsregeln verwendet. In diesen Bäumen wird ebenfalls jeder relationale Operator in einem Knoten gespeichert. Allerdings enthalten die Knoten nicht die selben Attribute wie die eines normalen generaliserten Baumes. Die einzigen vorhandenen Attribute sind eine physische Umsetzung der Operation und eine Variable für die Parametrisierung. Den Variablen der Regelbäume wird ein Wert von einem Termersetzer zugewiesen, wenn eine mit der Regel übereinstimmende Stelle in einem konkreten Ausführungsplan gefunden wurde. In diesem Abschnitt wird lediglich beschrieben wie die Regelbäume bei einer 38 KAPITEL 4. GENERALISIERTE BÄUME Termersetzung verwendet werden. Die genaue Funktionsweise eines Termersetzers in einem Einphasenoptimierungssystems wird in [Rud05] behandelt. Beispiel: Gegeben seinen die beiden Seiten einer Termersetzungsregel τ1 und τ2 und ein Ausführungsplan τ3 . Auf die Angabe einer Regelbedingung wird verzichtet, da nur der prinzipielle Ablauf einer Termersetzung dargestellt wird. τ3 : τ1 : τ2 : Rel σR 0 0 1 .A= 5 ∧R1 .A=R2 .B ./pN0ested−Loop σpRel ×N ested−Loop ×N ested−Loop σpRel 1 σpRel 2 R1 X Y X Rel πR 2 .B Y R2 Abbildung 4.1: Regelseiten und Ausführungsplan Mit Hilfe einer Funktion zum Durchlaufen eines generalisierten Baumes, beschrieben im letzten Abschnitt dieses Kapitels, findet ein Termersetzer eine Übereinstimmung in τ3 mit der linken Seite der Regel τ1 . Anschließend werden den Variablen in τ1 die im Ausführungsplan gefundenen Werte zugewiesen: τ3 : τ1 : Rel σR 0 0 1 .A= 5 ∧R1 .A=R2 .B Rel σp:={R 0 0 1 .A= 5 ∧R1 .A=R2 .B} ×N ested−Loop ×N ested−Loop =⇒ R1 Rel πR 2 .B R1 Rel πR 2 .B R2 R2 Abbildung 4.2: Setzen der Variablen Ein Termersetzer überprüft die im Allgemeinen vorhandene Regelbedingung und verteilt die Werte von τ1 in τ2 . Im generalisierten Baum ist eine Methode vorhanden, die aus einem gefüllten Regelbaum in einem abschließenden Schritt einen neuen Ausführungsplan erzeugt. Die genaue Funktionsweise dieser Methode wird im nächsten Abschnitt beschrieben. τ1 : τ2 : Rel σp:={R 0 0 1 .A= 5 ∧R1 .A=R2 .B} ×N ested−Loop R1 Rel πR 2 .B R2 =⇒ neuer Baum : ./pN0ested−Loop :={R1 .A=R2 .B} σpRel 0 0 1 :={R1 .A= 5 } σpRel 2 :={true} R1 Rel πR 2 .B R2 =⇒ N ested−Loop ./R 1 .A=R2 .B Rel σR 0 0 1 .A= 5 Rel πR 2 .B R1 R2 Abbildung 4.3: Verteilung der variablen Werte und Erzeugen eines neuen Baumes 4.7. BAUM-METHODEN 4.7 39 Baum-Methoden Um Informationen aus einem generalisierten Baum zu erhalten oder Veränderungen vorzunehmen, werden eine Reihe von Methoden zur Verfügung gestellt. • Ausgabe der Knotenattribute Für die Knotenattribute, die im ersten Abschnitt dieses Kapitels aufgezählt wurden, exisitieren Methoden zur Ausgabe der Werte. Beispielsweise lässt sich die Kardinalität, die Pagegröße oder eine Liste der sortierten Attribute ausgeben. • Berechnung der Gesamtkosten Die Gesamtkosten setzen sich zusammen aus den I/O- und den CPU-Kosten. Da sich Datenbanksysteme in der Konfiguration ihrer Hard- und Software unterscheiden, ist eine Gewichtung der CPU- zu den I/O-Kosten möglich, um das Kostenmodell individuell anpassen zu können. Die Formel für die Gesamtkosten lautet: Gesamtkosten := I/O-Kosten + ω · CP U -Kosten Mit den im Baum vorhandenen Methoden lassen sich die Gesamtkosten eines einzelnen Knotens oder eines kompletten Teilbaumes bestimmen. • Kopieren von Teilbäumen Das Kopieren von Teilbäumen ist recht simpel. Es wird ein Teilbaum übergeben, bei dem, beginnend mit den Blättern, Kopien der Knoten erstellt und zu einem neuen Baum zusammengefügt werden. • Austausch von Teilbäumen Problematisch ist der Austausch von Teilbäumen. Der Methode werden zwei Bäume übergeben, von denen der eine durch den anderen ersetzt wird. Nach der Ersetzung müssen die Attribute der übergeordneten Knoten des Ausgangsbaumes neu berechnet werden. Für den Fall, dass bei einem dieser Knoten die physische Umsetzung nicht mehr angewandt werden kann, wird die in Abschnitt 4.2 vorgestellte Möglichkeit genutzt, eine neue Umsetzung kostenabhängig zu bestimmen. • Umwandlung eines gefüllten Regelbaumes in einen generalisierten Baum Im vorherigen Abschnitt wurde beschrieben wie in den Variablen der Regelbäume konkrete Parametrisierungen und Relationen gespeichert werden. Mit Hilfe eines Regelbaumes, der auf diese Weise gefüllt wurde, kann ein neuer Ausführungsplan erzeugt werden. Die zur Verfügung gestellte Methode arbeitet ähnlich wie die Methode zum Kopieren von Teilbäumen. Aus den Blättern des Regelbaumes werden zuerst die Basisrelationen bzw. die Teilbäume, die im Ursprungsbaum an dieser Stelle vorhanden waren, geholt. Dann wird er von den Blättern aus weiter nach oben durchlaufen. Dabei wird für jeden Knoten des Regelbaumes ein konkreter Knoten für den neuen Ausführungsplan erzeugt. Das Beispiel des vorherigen Abschnittes zeigt im letzten Schritt die Anwendung dieser Methode. • Durchlaufen von Teilbäumen Es wird ein Iterator bereitgestellt, mit dem ein Durchlauf eines generalisierten Baumes möglich ist. Jeder next-Aufruf dieses Iterators liefert den nächsten Knoten in Post-Order Reihenfolge, d. h. es wird jeweils der linke, dann der rechte Teilbäum und 40 KAPITEL 4. GENERALISIERTE BÄUME anschließend die Wurzel besucht. In der folgenden Abbildung ist der Weg eines solchen Durchlaufes dargestellt: Abbildung 4.4: Baumdurchlauf Jeder Knoten speichert Verweise auf seine Kind- und seinen Elternknoten. Somit konnten Methoden erstellt werden, welche ermöglichen, dass man von einem Knoten aus in jede Richtung gelangen kann. Auf diese Weise sind individuelle Durchläufe eines generalisierten Baumes möglich. Kapitel 5 Berechnung der Kosten Die Funktionen zur Berechnung der Kosten aus [War03] und [Mak03] wurden erweitert und ergänzt. Die Bedingungen bei Verbundoperatoren beschränkten sich auf atomare Gleichheitsbedingungen, d. h. sie hatten die Form R1 .A = R2 .B und konnten nicht konjunktiv verknüpft werden. Die Bedingungen und Kostenformeln wurden erweitert auf allgemeine Verbundbedingungen. Hinzugekommen sind neue Formeln für die Abschätzung von Kardinalität, Pagegröße, Attributwertanzahlen und Kosten bei Outerjoins und Aggregationen. 5.1 Selektivitätsabschätzungen Für eine Optimierung ist es wichtig zu wissen, auf wie viele Tupel einer Relation R sich eine Bedingung p bezieht. Hierzu verwendet man die Selektivitätsabschätzung sel(p, R), da eine konkrete Aussage ein bereits berechnetes Ergebnis voraussetzten würde. Prädikat p Schätzung für sel(p, R) Standardwert 1 n(A,R) 1 10 A > a, A ≥ a max(A)−a max(A)−min(A) 1 3 A < a, A ≤ a a−min(A) max(A)−min(A) 1 3 A=a A=B min 1 1 n(A,R) , n(B,R) 1 10 A > B, A ≥ B - 1 3 A < B, A ≤ B - 1 3 1 − sel(p1 , R) - p1 ∧ p2 sel(p1 , R) · sel(p2 , R) - p1 ∨ p2 sel(p1 , R) + sel(p2 , R) − sel(p1 ∧ p2 , R) - ¬p1 Tabelle 5.1: Selektivität von Prädikaten 41 42 KAPITEL 5. BERECHNUNG DER KOSTEN Für die Abschätzungen in der zweiten Spalte der nächsten Tabelle wird die Anzahl der verschiedenen Werte eines Attributes benötigt. Dieser Wert wird als Attributwertanzahl n(A, R) bezeichnet und im nächsten Abschnitt behandelt. In der dritten Spalte befinden sich Standardwerte für die Abschätzungen. Wie in Kapitel 2.1.3 beschrieben wurde, sind Selektionsbedingungen, welche arithmetische Ausdrücke enthalten, ebenfalls möglich. Bei solchen Prädikaten wird der schlechteste Fall angenommen, und zwar dass die Selektivität gleich 1 ist, d. h. dass alle Tupel ausgewählt werden. 5.2 Schätzungen für Attributwertanzahlen Attributwertanzahlen geben an, wie viele verschiedene Werte ein Attribut A einer Relation R hat und wird mit n(A, R) bezeichnet. Bei der Selektion sind mehrere Fälle zu unterscheiden. Dabei ist es wichtig, ob das betrachtete Attribut A in der Selektionsbedingung p vorkommt oder nicht. Sei A ∈ / attr(p), r = |σp (R)|, m = n(A, R) , dann gilt: r falls r < m 2 r+m falls m n(A, σp (R)) = 3 2 ≤r ≤2·m m falls r > 2 · m Abbildung 5.1: Attributwertanzahlen Selektion Wenn A in attr(p) enthalten ist, wird die folgende Tabelle für die Abschätzung der Attributwertanzahlen einer Selektion verwendet. Die Abschätzungen der anderen Operationen, wie z. B. Produkt oder Outerjoin, sind in den Tabellen 5.3, 5.4 und 5.5 aufgeführt. In diesen Tabellen steht A wie gewohnt für ein vorhandenes Attribut der Argumentrelation. Bedingung p n(A, σp (R)) AΘa sel(p, R) · n(A, R) AΘB n(A, R) A=B min (n(A, R), n(B, R)) p1 ∧ p2 n(A, σp1 (σp2 (R))) p2 ∨ p2 n(A, σp1 (R) ∪ σp2 (R)) Tabelle 5.2: Schätzungen für Attributwertanzahlen einer Selektion Bei einer Aggregation können Attribute, durch das Anwenden von Group-by-Funktionen, wie z. B. count(∗), hinzukommen. Um Attributwertanzahlen dieser Spalten bestimmen zu können, bräuchte man konkrete Werte der Relation. Dies würde ein bereits berechnetes Ergebnis voraussetzen, dass aber nicht vorhanden ist, da hier ein Anfragesimulator entwickelt wird. 5.2. SCHÄTZUNGEN FÜR ATTRIBUTWERTANZAHLEN 43 Aus diesem Grund wird von einer Gleichverteilung der Attributwerte der Argumentrelation ausgegangen, wodurch bei einer Gruppierung gleich große Gruppen entstehen. Somit ist die Attributwertanzahl eines neu entstandenen Attributes durch die Funktion count() in jedem Fall 1. Für die anderen Aggregationsfunktionen wird angenommen, dass jede Gruppe einen unterschiedlichen Wert annimmt. Die Anzahl der Gruppen ist abhängig von den Attributwertanzahlen der Attribute nach denen gruppiert wird. Operation Bedingung n(A,Operation) Γg#f (R) A ∈ (g ∩ f ) n(A, R) A = count() 1 A = sum(), avg(), min(), max() min(|R|, Q X∈attr(g) n(X, R)) Tabelle 5.3: Attributwertanzahlen Aggregation Beispiel: Es wird die Anzahl Bestellungen pro Kunde gesucht n(Kunde, ΓKunde#Kunde,count(Bestellung)VERKÄUFE) = n(Kunde,VERKÄUFE) n(count(Bestellung), ΓKunde#Kunde,count(Bestellung)VERKÄUFE) = 1 Bei einem Full-Outerjoin bleiben die Attributwertanzahlen der Argumentrelationen erhalten, da auch die Tupel die keinen Joinpartner finden, anschließend zur Tabelle hinzugefügt werden. Ähnlich verhällt es sich mit Left-Outerjoin und Right-Outerjoin. Hier ändern sich allerdings die Wertanzahlen der Relation, bei der die Tupel ohne Joinpartner nicht betrachtet werden, wie bei einem normalen Join. Die Formeln zur Berechnung von Attributwertanzahlen bei einem Join, sind in Tabelle 5.5 zu finden. Operation Bedingung n(A,Operation) R1 A./@p R2 A ∈ sch(R1 ) n(A, R1 ) A ∈ sch(R2 ) n(A, R2 ) A ∈ sch(R1 ) n(A, R1 ) A ∈ sch(R2 ) n(A, σp (R1 × R2 )) A ∈ sch(R1 ) n(A, σp (R1 × R2 )) A ∈ sch(R2 ) n(A, R2 ) R1 A./p R2 R1 ./@p R2 Tabelle 5.4: Attributwertanzahlen Outerjoins In der folgenden Tabelle 5.5 sind die restlichen Funktionen für die übrigen Operatoren aufgeführt. Bei einem kartesischen Produkt ändern sich die Attributwertanzahlen nicht, da jedes Tupel mit jedem verbunden und kein Tupel entfernt wird. Bei einem Join werden nur die Tupel selektiert und verbunden, welche die Bedingung p erfüllen. Semi- und Antisemijoin ähneln sehr einer Selektion. Aus diesem Grund ist es sinnvoll, für die Attribute A ∈ / attr(p) die Abschätzung aus Abbildung 5.1 zu verwenden, mit r = |R1 .<p R2 | und m = n(A, R1 ). 44 KAPITEL 5. BERECHNUNG DER KOSTEN Operation Bedingung n(A,Operation) πl (R) A∈l n(A, R) R1 × R2 A ∈ sch(R1 ) n(A, R1 ) A ∈ sch(R2 ) n(A, R2 ) n(A, σp (R1 × R2 )) R1 ./p R2 R1 .<p R2 A ∈ attr(p) n(A, πsch(R1 ) (R1 ./p R2 )) R1 .<p R2 A ∈ attr(p) n(A, R1 ) − n(A, R1 .<p R2 ) R1 − R2 sch(R1 ) = sch(R2 ) n(A, R1 .<p R2 ) R1 ∩ R2 sch(R1 ) = sch(R2 ) n(A, R1 .<p R2 ) R1 ∪ R2 sch(R1 ) = sch(R2 ) n(A, R1 ) + n(A, R2 ) − n(A, R1 ∩ R2 ) Tabelle 5.5: Schätzungen für Attributwertanzahlen Bei einer Projektion werden Attribute ausgewählt und keine Tupel. Daher bleibt die Anzahl der verschiedenen Attributwerte erhalten. Ausnahmen sind Projektionen in denen arithmetische Ausdrücke vorkommen, wie sie in Kapitel 2.1.3 dargestellt wurden. In solchen Fällen wird davon ausgegangen, dass die Attributwertanzahl erhalten bleibt, falls eine Berechnung mit einem Attribut und einer Zahl verlangt wird: Es sei a eine bliebige Zahl und Θ = {+, −, ∗, /} n(AΘa, πl (R)) = n(A, R) Falls eine Berechnung mit zwei Attributen ausgeführt werden soll, wird angenommen, dass jede mögliche Berechnung ein unterschiedliches Ergebnis hat: n(AΘB, πl (R)) = min(|R|, (n(A, R) · n(B, R))) Um Abschätungen für die Mengenoperatoren machen zu können, werden die folgenden Darstellungen ausgenutzt, wobei p eine Bedingung ist, welche die gleichnamigen Attribute der Argumentrelationen vergleicht: Mengenoperation Verbundoperation R1 ∩ R2 R1 .<p R2 R1 − R2 R1 .<p R2 Tabelle 5.6: Darstellungen für Mengenoperatoren Bei der Vereinigung ergeben sich die Attributwertanzahlen aus der Summe der verschieden Attributwerte der Argumentrelationen. 5.3. KARDINALITÄT UND PAGEGRÖSSE 5.3 45 Kardinalität und Pagegröße Mit Hilfe der Selektivitätsabschätzungen lassen sich Kardinalitäten und Pagegrößen, nach der Anwendung einer Operation, bestimmen. Die Kardinalität gibt die Anzahl der Tupel wieder und die Pagegröße ist ein Maß für den verwendeten Speicher, da sich ihr Wert aus der Anzahl Tupel und deren Länge zusammensetzt. Operation |Operation| page(Operation) σp (R) sel(p, R) · |R| sel(p, R) · page(R) πl (R) min(|R|, Q Al n(A, R)) |πl (R)| |R| · |l| |sch(R)| · page(R) R1 × R2 |R1 | · |R2 | |R1 | · page(R2 ) + |R2 | · page(R1 ) R1 ./p R2 |σp (R1 × R2 )| page(σp (R1 × R2 )) R1 .<p R2 |πsch(R1 ) (R1 ./p R2 )| |R1 .<p R2 | |R1 | · page(R1 ) R1 .<p R2 |R1 | − |R1 .<p R2 | |R1 .<p R2 | |R1 | · page(R1 ) R1 − R2 |R1 .<p R2 | |R1 −R2 | |R1 | R1 ∩ R2 |R1 .<p R2 | |R1 ∩R2 | |R1 |+|R2 | · (page(R1 ) + page(R2 )) R1 ∪ R2 |R1 | + |R2 | − |R1 ∩ R2 | |R1 ∪R2 | |R1 |+|R2 | · (page(R1 ) + page(R2 )) · page(R1 ) Tabelle 5.7: Kardinalitäten und Pagegrößen Der Anteil Tupel der nach einer Selektion bleibt, wird mit Hilfe der Selektivität der Bedingung bestimmt. Um den selben Anteil verringert sich der benötigte Speicherplatz. Unter der Annahme das eine Projektion grundsätzlich eine Duplikateleminierung ausführt, ist die Kardinalität abhängig von der Anzahl verschiedener Werte bzw. verschiedener Wertkombinationen der Attribute auf die projiziert wird. Maximal können so viele Tupel zurückgeliefert werden, wie vor der Projektion vorhanden waren. Die Pagegröße ändert sich abhängig von der Anzahl und der Länge der Tupel. Bei einem kartesischen Produkt wird jedes Tupel der ersten Relation mit jedem Tupel der zweiten verbunden. Also ergibt sich die Kardinalität dieser Operation aus dem Produkt der Kardinalitäten der Argumentrelationen. Für die Pägegröße gilt, das für jedes Tupel einer Relation der Platzbedarf der anderen benötigt wird. Eine äquivalente Darstellung eines Joins ist eine Selektion über einem kartesischen Produkt. Dies wird für die Bestimmung der Kardinalität und Pagegröße ausgenutzt. Der Semijoinoperator entspricht einem normalen Join mit anschließender Projektion auf die Attribute der ersten Argumentrelation. Der verwendete Speicherplatz nimmt um den selben Anteil ab, wie die Anzahl der Tupel nach Anwendung der Operation. Für die Pagegröße eines Antisemijoins gilt das selbe. Da ein Antisemijoinoperator die Tupel liefert, die ein Semijoin nicht liefert, bestimmt sich seine Kardinalität durch die Differenz der Anzahl Tupel der ersten Argumentrelation und der Kardinalität einer Semijoinoperation. 46 KAPITEL 5. BERECHNUNG DER KOSTEN Für die Berechnung der Mengenoperatoren werden die Äquivalenzen aus Tabelle 4.7 ausgenutzt. Bei der Vereinigung ergibt sich die Kardinalität aus der Summe der verschieden Tupel der Argumentrelationen. Zur Bestimmung der Kardinalität und Pagegröße von Outerjoins wird die Darstellung aus [Lip04] verwendet: R1 A./@p R2 := (R1 ./p R2 ) ∪ ((R1 .<p R2 ) × {⊥, . . . , ⊥}) ∪ ({⊥, . . . , ⊥} × (R2 .<p R1 )) Damit und mit den bereits in [War03] aufgestellten Formeln für Join und Antisemijoin ergeben sich folgende Abschätzungen für Left-Outerjoin, Right-Outerjoin und Left-RightOuterjoin: Operation |Operation| R1 A./p R2 |R1 ./p R2 | + |R1 .<p R2 | page(σp (R1 × R2 )) + |R1 .<p R2 | |R2 | · page(R2 ) R1 ./@p R2 |R1 ./p R2 | + |R2 .<p R1 | page(σp (R1 × R2 )) + |R2 .<p R1 | |R1 | · page(R1 ) R1 A./@p R2 |R1 ./p R2 | + |R2 .<p R1 | page(σp (R1 × R2 )) + |R2 .<p R1 | |R2 | · page(R2 ) +|R1 .<p R2 | + page(Operation) |R1 .<p R2 | |R1 | · page(R1 ) Tabelle 5.8: Kardinalitäten und Pagegrößen von Outerjoins Ein Left-/Right-Outerjoin arbeitet wie ein normaler Join, allerdings werden die Tupel der linken/rechten Relation, die keinen Joinpartner finden, ebenfalls zu dem Ergebnis hinzugefügt. Der Anteil der zusätzlichen Tupel kann mit Hilfe eines Antisemijoins bestimmt werden. Das Selbe gilt für ein Full-Outerjoin, nur sind hier die partnerlosen Tupel beider Argumentrelationen Bestandteil der Ergebnisrelation. Die Kardinalität einer Aggregation ist abhängig von der Anzahl der gebildeten Gruppen. Werden keine Attribute angegeben nach denen gruppiert werden soll, liefert eine Aggregation nur ein Tupel mit den Ergebnissen der Aggregationsfunktionen. Ansonsten ergibt sich die Anzahl der möglichen Gruppen aus der Menge der verschiedenen Wertkombinationen der Attribute nach denen gruppiert wird. Operation |Operation| page(Operation) Γ#f (R) 1 1 Γg#f (R) min(|R|, Q A∈attr(g) n(A, R)) |Γg#f (R)| |R| · |f | |sch(R)| · page(R) Tabelle 5.9: Kardinalität und Pagegröße einer Aggregation Für den ersten Fall, in dem nur ein Tupel das Ergebnis ist, wird die Pagegröße ebenfalls mit 1 angenommen. Wird aber eine Gruppierung ausgeführt, ergibt sich die Pagegröße, wie bei der Projektion, aus der Anzahl und der Länge der Ergebnistupel. 5.4. KOSTENFUNKTIONEN 5.4 5.4.1 47 Kostenfunktionen Selektion und Projektion Eine Selektion kann auf mehrere Arten ausgeführt werden. Einmal über einen Relationenscan σpRel , wobei jedes Tupel aus dem Speicher gelesen und auf die Bedingung p hin überprüft werden muss. Eine andere Möglichkeit ist, für den Zugriff einen Index zu verwenden. Die I/O-Kosten setzen sich dabei aus den vom Index belegten Speicherseiten und den Zugriffen auf die selektierten Tupel zusammen. Für die CPU-Kosten muss bedacht werden, dass der Index verarbeitet und eine Auswahl der Tupel zu der entsprechenden TID-Folge gemacht werden muss. Das Übersetzen der TID in Tupel wird durch |σp (R)| modelliert. σp (R) I/O-Kosten CPU-Kosten σpRel page(R) |R| σpIndex page(Indexl (R)) + |σp (R)| |Indexl (R)| + |σp (R)| Tabelle 5.10: Selektionskosten Bei der Projektion kann ein einfacher Relationenscan, ein Scan mit Nutzung einer Sortierung oder ein Indexzugriff verwendet werden. πl (R) I/O-Kosten page(R) 2 CPU-Kosten |R| 2 πlRel page(R) + |R| · πlSort page(R) |R| πlIndex page(Indexl (R)) |Indexl (R)| + |πl (R)| |R| + |R| · Tabelle 5.11: Projektionskosten Um bei einem relationalen Scan eine Duplikateleminierung auszuführen, muss für jedes neue Tupel geprüft werden, ob es schon einmal gelesen wurde. Der bereits betrachtete Teil ist im Mittel die Hälfte der Relation. Wird bei der Projektion eine Sortierung genutzt, können Duplikate nur nacheinander folgen, wodurch es möglich ist sequentiell zu arbeiten. Falls ein Index auf den zu projizierenden Attributen vorhanden ist, kann das Projektionsergebnis direkt aus ihm gelesen werden. Die I/O-Kosten und die CPU-Kosten entstehen daher durch das Lesen des Indexes. Zu den CPU-Kosten muss der Aufwand für die Bestimmung der auftretenen Attributwerte hinzugezählt werden. Diese Kosten werden geschätz mit |πl (R)|. 5.4.2 Aggregation Eine Aggregation lässt sich in zwei Typen einteilen. Ist der Teil der Bedingung vor dem Doppelkreuz leer, handelt es sich um eine einfache Aggregation ohne Gruppierung. Bei diesem 48 KAPITEL 5. BERECHNUNG DER KOSTEN Typ wird ein einzelnes Tupel für die angewendeten Group-by-Funktionen zurückgeliefert, z. B. enthält das Ergebnistupel der Anfrage Γ#count(Kunde),max(Betrag) VERKÄUFE die Anzahl Tupel und den Maximalwert des Attributes Betrag der Relation VERKÄUFE. Die Group-by-Funktionen können parallel berechnet werden. Im Allgemeinen wird dazu ein einzelner Durchlauf der Argumentrelation benötigt. Daher wird in solchen Fällen grundsätzlich ein Relationenscan verwendet: Γ#f (R) I/O-Kosten CPU-Kosten ΓRel #f page(R) |R| Tabelle 5.12: Kosten einer Aggregation ohne Gruppierung Eine Aggregation mit Gruppierung ähnelt einer Duplikateleminierung. Wird ein Relationenscan verwendet, muss für jedes neue Tupel geprüft werden, ob es bereits gelesen wurde. Dabei können die Group-by-Funktionen berechnet werden, ohne weitere Kosten zu erzeugen. Im Mittel ist der bereits betrachtete Teil der Relation etwa die Hälfte. Γg#f (R) I/O-Kosten ΓRel g#f page(R) + |R| · ΓSort g#f page(R) page(R) 2 CPU-Kosten |R| + |R| · |R| 2 |R| Tabelle 5.13: Kosten einer Aggregation mit Gruppierung Falls in der Relation die einzelnen Attribute aus g sortiert sind oder eine Sortierung über alle Attribute aus g existiert, kann ein sequentiell arbeitender Merge-Algortihmus genutzt werden. Es ist recht selten, dass in einer Basisrelation mehr als ein Attribut sortiert ist. Nach der Anwendung von Verbundoperatoren kann dies aber durchaus der Fall sein. 5.4.3 Join Im folgenden bezeichnet die Menge A der Joinattribute die sich auf R1 und B die Menge die sich auf R2 beziehen. In einem NestedLoop-Join wird jedes Tupel aus der ersten Relation mit jedem Tupel aus der zweiten verglichen. Das bedeutet, dass der gesamte Inhalt von R1 und für jedes Tupel daraus der Inhalt von R2 einmal gelesen werden muss. Man erhält in jedem Fall geringere Kosten wenn die linke Relation die kleinere Kardinalität hat. Aus diesem Grund wird diese Anordnung automatisch vom generalisierten Baum übernommen. Bei einen Rel-Index-Join wird die erste Relation und der Index komplett gelesen und für jedes Tupel aus R1 ein Indexscan gemacht. Um abschätzen zu können, wie viele Tupel zur gefundenen TID-Folge eingelesen werden müssen, ist es nötig, die Joinbedingung zu zerlegen. Die Joinattribute die sich auf R2 beziehen, werden in einer Hilfsbedingung mit einem Attributwert, anstatt mit einem Attribut aus R1 verbunden, z. B. wird R1 .A < R2 .B zu R1 .A <’a’ und R2 .B >’a’. Das Zerlegen in Hilfsbedingungen wird mit ∧X∈A R1 .XΘa bzw. ∧X∈B R2 .XΘa dargestellt. Die Anzahl der zu selektierenden Tupel ergibt sich dann mit |σ∧X∈B R2 .XΘa (R2 )|. 5.4. KOSTENFUNKTIONEN R1 ./p R2 ./pN ested−Loop erge ./M p ./Rel−Index p ./pIndex−Index 49 I/O-Kosten CPU-Kosten page(R1 ) + |R1 | · page(R2 ) |R1 | + |R1 | · |R2 | page(R1 ) + page(R2 ) |R1 | + |R2 | page(R1 ) + page(IndexB (R2 )) |R1 | + |R1 |· +|R1 | · |σ∧X∈B R2 .XΘa (R2 )| (|IndexB (R2 )| + |σ∧X∈B R2 .XΘa (R2 )| page(IndexA (R1 )) +page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) min(|R1 |, Q X∈A n(X, R1 )) (·|IndexA (R1 )| + |σ∧Y ∈A R1 .Y =a (R1 )| +|IndexB (R2 )| + |σ∧X∈B R2 .XΘa (R2 )|) ·(|σ∧X∈A R1 .X=a (R1 )| +|σ∧Y ∈B R2 .Y Θa (R2 )|) Tabelle 5.14: Kosten eines Joins Damit ein Merge-Join genutzt werden kann, dürfen ausschließlich Gleichheitsbedingungen für den Verbund verwendet werden. Enthält die Joinbedingung mehr als ein Prädikat, kann ein Merge-Algorithmus nur angewandt werden, falls alle Attribute der Bedingung einzeln sortiert sind oder eine einzige Sortierung über diesen Attributen existiert. Ist der zweite Fall gegeben, muss ebenfalls sichergestellt sein, dass die Bedingungsattribute nach ihrer Ordnung verbunden sind, z. B.: R1 ist sortiert nach ’R1 .A, R2 .B, R3 .C’ und R2 nach ’R2 .C, R2 .A, R2 .B’, dann müssen die Attribute von links beginnend verbunden sein. Mögliche Bedingungen wären p1 := R1 .A = R2 .C ∧ R1 .B = R2 .A ∧ R1 .C = R2 .B p2 := R1 .A = R2 .C ∧ R1 .B = R2 .A p3 := R1 .A = R2 .C Bei einem Index-Index-Join werden beide Indexe eingelesen. Dann wird für jeden Joinattributwert bzw. Attributwertkombination aus R1 ein Indexscan auf beiden Relationen gemacht. Dabei wird in der ersten Relation nach dem konkreten Wert gesucht und in der zweiten nach Tupeln, die die Bedingung mit dem Attributwert erfüllt. Zur Abschätzung der zu lesenden Tupel werden auch hier Hilfsbedingungen erzeugt. Diese Kostenfunktionen gelten ebenfalls für die entsprechenden Outerjoinoperatoren, da ihre Umsetzungen nur Erweiterungen der Join-Algorithmen sind. Bei Outerjoins ist es aber nicht sinnvoll auf die Relation, deren partnerlose Tupel am Ende zum Ergebnis hinzugefügt werden, mit einem Index zuzugreifen. Da alle Tupel der Argumentrelation gelesen werden müssen, würde ein Indexzuriff größere Kosten erzeugen als ein Relationenscan. 5.4.4 Semi-/Antisemijoin Der Vorteil eines NestedLoop-Semijoins ist, dass ein Durchlauf der inneren Schleife abgebrochen werden kann, wenn ein passendes Tupel gefunden wurde. Der Anteil der qualifizerenden Tupel ergibt sich aus sel(∧X∈B R2 .XΘa, R2 ). Unter der Annahme, dass die Attributwerte gleichverteilt und nicht sortiert sind, werden bei einem inneren Schleifendurchlauf durchschnittlich sel(∧X∈B R12 .XΘa,R2 ) , aber maximal |R2 |, Tupel gelesen, bis ein passendes gefunden wird. Erfüllen z. B. 13 der Tupel die Bedingung, muss man etwa 3 Stück lesen. 50 KAPITEL 5. BERECHNUNG DER KOSTEN R1 .<p R2 .<pN ested−Loop I/O-Kosten CPU-Kosten page(R1 ) + |R1 |· |R1 | + |R1 |· min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)· min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |) 1 |R2 | erge .<M p · page(R2 ) page(R1 ) + page(R2 ) |R1 | + |R2 | .<Rel−Index p page(R1 ) + page(IndexB (R2 )) |R1 | + |R1 | · |IndexB (R2 )| .<pIndex−Rel page(IndexA (R1 )) + |R1 .<p R2 | Q +min(|R1 |, X∈A n(X, R1 ))· min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)· 1 |R2 | .<pIndex−Index min(|R1 |, Q X∈A n(X, R1 ))· (|IndexA (R1 )| + |σ∧X∈A R1 .XΘa (R1 )| + min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |) · page(R2 ) page(IndexA (R1 )) +page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) min(|R1 |, Q X∈A n(X, R1 ))· (|IndexA (R1 )| + |σ∧Y ∈A R1 .Y =a (R1 )| +|IndexB (R1 )|) ·|σ∧X∈A R1 .X=a (R1 )| Tabelle 5.15: Kosten eines Anti-/Semijoins Falls die Joinbedingung ausschließlich aus Gleichheitsbedingungen besteht, kann ein MergeAlgorithmus angewandt werden, welcher beide Relationen nur einmal zu lesen braucht. Aber auch hier gelten die selben Bedingungen wie bei einem normalen Join. Für ein Rel-Index-Semijoin wird die erste Relation gelesen und für jedes Tupel wird im Index der zweiten Relation geprüft, ob es eine TID-Folge dazu gibt. Ist eine vorhanden, gehört das betrachtete Tupel zur Ergebnisrelation. Ein Index-Rel-Semijoin arbeitet ähnlich wie ein Nested-Loop-Semijoin. Allerding müssen nur die Tupel aus R1 gelesen werden, welche die Joinbedingung p erfüllen. Die Anzahl Durchläufe der inneren Schleife reduziert sich auf die Anzahl verschiedener Attributwerte bzw. Attributwertkombinationen, falls die Bedingung mehr als ein Attribut enthält. Der Unterschied zwischen einem Index-Index-Join und einem Index-Index-Semijoin besteht lediglich darin, dass keine Tupel aus der zweiten Argumentrelation gelesen werden. 5.4.5 Mengenoperatoren Mit Ausnahme der Vereinigung wird zur Bestimmung der Kosten der Mengenopertoren erneut die Darstellung aus Tabelle 4.16 verwendet. Eine Vereinigung setzt sich aus einem Relationenscan und einem Antisemijoin zusammen. Daher ergeben sich die Kosten aus der Summe der beiden Operationen. Eine Ausnahme ist der Merge-Algorithmus für eine Vereinigung. Dieser kann ebenfalls sequentiell auf beide Relationen arbeiten. 5.4. KOSTENFUNKTIONEN R1 ∪ R2 51 I/O-Kosten CPU-Kosten page(R2 ) + page(R1 ) + |R1 |· min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)· 1 |R2 | · page(R2 ) |R2 | + |R1 | + |R1 |· min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |) page(R1 ) + page(R2 ) |R1 | + |R2 | ∪Rel−Index page(R1 ) + page(IndexB (R2 )) +page(R2 ) |R2 | + |R1 | +|R1 | · |IndexB (R2 )| ∪Index−Index page(R2 ) + page(IndexA (R1 )) +page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) ·|σ∧X∈A R1 .X=a (R1 )| Q |R2 | + min(|R1 |, X∈A n(X, R1 ))· (|IndexA (R1 )| + |σ∧Y ∈A R1 .Y =a (R1 )| +|IndexB (R1 )|) ∪N ested−Loop ∪M erge Tabelle 5.16: Kosten einer Vereinigung 5.4.6 Erzeugen von Sortierungen oder Indexen Es exisiteren Operatoren zum Erzeugen von Indexen oder Sortierungen auf einer Relation. Die Kosten für diese Operatoren orientieren sich an den bekannten Laufzeiten von Sortieralgorithmen von n · log n: SORTl (R), IN DEXl (R) I/O-Kosten CPU-Kosten page(R) · log2 (page(R)) |R| · log2 (|R|) Tabelle 5.17: Kosten für die Erzeugung von Indexen und Sortierungen 5.4.7 Verwendung von geclusterten Indexen Werden ähnliche Daten nicht nur in einem Index erfasst sondern auch im Speicher nahe beieinander festgehalten, spricht man von einem geclusterten Index. Der Vorteil gegenüber einem normalen Index besteht darin, dass auf die Argumentrelation keine einzelnen Tupelzugriffe gemacht werden. Stattdessen werden Blöcke von Tupel eingelesen, wodurch eine Verringerung der I/O-Kosten erreicht wird. Operation I/O-Kosten σpCl Index page(IndexA (R)) + page(σp (R)) Index ./Rel−Cl p page(R1 ) + page(IndexB (R2 )) +|R1 | · page(σ∧X∈B R2 .XΘa (R2 )) Index−Cl Index ./Cl p page(IndexA (R1 )) + page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) ·(page(σ∧X∈A R1 .X=a (R1 )) + page(σ∧Y ∈B R2 .Y Θa (R2 ))) Tabelle 5.18: Geclusterte Indexe Teil 1 52 KAPITEL 5. BERECHNUNG DER KOSTEN Operation I/O-Kosten ./pClIndex−Index page(IndexA (R1 )) + page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) ·(page(σ∧X∈A R1 .X=a (R1 )) + |σ∧Y ∈B R2 .Y Θa (R2 )|) .<pCl Index−Rel page(IndexA (R1 )) + page(R1 .<p R2 ) Q +min(|R1 |, X∈A n(X, R1 ))· min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |) · 1 |R2 | · page(R2 ) Index−Cl Index .<Cl p page(IndexA (R1 )) + page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) · page(σ∧X∈A R1 .X=a (R1 )) ∪Cl Index−Cl Index page(R2 ) + page(IndexA (R1 )) + page(IndexB (R2 )) Q +min(|R1 |, X∈A n(X, R1 )) · page(σ∧X∈A R1 .X=a (R1 )) Tabelle 5.19: Geclusterte Indexe Teil 2 In einem System können geclusterte und normale Indexe vorkommen. Da bei einem Join die Argumentrelationen kommutativ sind, genügt die Darstellung eines Cl Index-Index-Join. Für ein Index-Cl Index-Join werden die selben Formeln, mit vertauschten Relationen, verwendet. 5.5 Beispiel Als Beispiel wird für folgende Anfrage ein Ausführungsplan erzeugt und dessen Kosten, mit Hilfe der aufgestellten Formeln, berechnet. Die physischen Umsetzungen der einzelnen Operatoren werden, wie in Kapitel 4.2 beschrieben, kostenabhängig ausgewählt. P ROJECT ION (V.T itel, A.Count) RIGHT OU T ERJOIN (V.V orlN r = H.V orlN r) V, (AGGREGAT ION (H.V orlN r#H.V orlN r, (count(H.M atrN r) AS A.Count)) H) Es seinen die Metadaten der nächsten Tabelle gegeben: R V Attribute {VorlNr, Titel} |R| 30 page(R) 3 n(A, R) VorlNr=15, Titel=30 H {MatrNr, VorlNr} 60 6 MatrNr=60, VorlNr=40 Index {VorlNr} Sortierung {VorlNr} Tabelle 5.20: Metadaten Die Kardinalität des Index auf V.V orlN r sei 10 und die Pagegröße sei 1. In diesem Beispiel wird kein Pipelining verwendet und es beschränkt sich auf die I/O-Kosten. 1. Schritt: Γ(H.V orlN r#H.V orlN r,count(H.M atrN r)) H Mögliche Umsetungen für die Aggregation: • Relationenscan I/O-Kosten:=page(H) + |H| · page(H) 2 = 6 + 60 · 3 = 186 5.5. BEISPIEL 53 • Merge-Aggregation I/O-Kosten:=page(H) = 6 Da die CPU-Kosten eines Merge-Algorithmus am geringsten sind, wird diese Variante ausgewählt. Metadaten: • Attributwertanzahlen: n(H.V orlN r, ΓSort g#f (H)) = n(H.V orlN r, H) = 40 n(H.Count, ΓSort g#f (H)) = 1 • Kardinalität: |ΓSort g#f (H)| = min(|H|, • Pagegröße: page(ΓSort g#f (H)) = Q A∈attr(g) |ΓSort g#f (H)| |H| · n(A, H)) = min(60, 40) = 40 |f | |sch(H)| · page(H) = 40 60 · 2 2 ·6=4 • Sortierungen: In der Ergebnisrelation ist das Attribut H.V orlN r sortiert. 2. Schritt: ./@(V.V orlN r=H.V orlN r) V ΓSort (H.V orlN r#H.V orlN r,count(H.M atrN r)) H Mögliche Umsetzungen für den Right-Outerjoin: • NestedLoop-Join: I/O-Kosten:=page(V ) + |V | · page(ΓSort g#f (H)) = 3 + 30 · 4 = 123 • Index-Rel-Join: Sort I/O-Kosten:=page(IndexV.V orlN r (V ))+page(ΓSort g#f (H))+|Γg#f (H)|·|σV.V orlN r=0 a0 (V )| = 1 + 4 + 40 ∗ 2 = 85 Als Umsetzung wird ein Index-Rel-Join gewählt, da er die geringsten Kosten verursacht. Metadaten: • Kardinalität: Sort Sort |V ./@p (ΓSort g#f (H))| = |V ./p (Γg#f (H))| + |(Γg#f (H)).<p V | = 30 + 25 = 55 • Attributwertanzahlen: Sort n(H.V orlN r, V ./@p (ΓSort g#f (H))) = n(H.V orlN r, Γg#f (H)) = 40 Sort n(H.Count, V ./@p (ΓSort g#f (H))) = n(H.Count, Γg#f (H)) = 1 Sort n(V.V orlN r, V ./@p (ΓSort g#f (H))) = min(n(V.V orlN r, V ), n(H.V orlN r, Γg#f (H))) = 15 n(V.T itel, V ./@p (ΓSort g#f (H))) = 20 Zur Berechnung der Attributwertanzahlen für V.T itel wurde die Abschätzung aus Abbildung 5.1, mit r = |V ./p (ΓSort g#f (H))| und m = n(V.T itel, V ), verwendet. 54 KAPITEL 5. BERECHNUNG DER KOSTEN • Pagegröße: Sort page(V ./@p (ΓSort g#f (H))) = page(V ./p (Γg#f (H))) + = 6 + 25 30 · 3 = 9 |(ΓSort g#f (H)).< p V | |V | · page(V ) • Sortierungen: In der Ergebnisrelation ist das Attribut H.V orlN r sortiert. 3. Schritt: πV.T itel,H.Count Ind−Rel ./@(V.V orlN r=H.V orlN r) V ΓSort (H.V orlN r#H.V orlN r,count(H.M atrN r)) H Mögliche Umsetzungen für die Projektion: • π Rel : Ind−Rel (ΓSort (H))| I/O-Kosten:=page(V ./@pInd−Rel (ΓSort g#f (H))) + |V ./@p g#f · page(V ./@Ind−Rel (ΓSort p g#f (H))) 2 = 9 + 55 · 9 2 = 257 Metadaten: • Kardinalität: Ind−Rel (ΓSort (H))|, |πlRel (V ./@pInd−Rel (ΓSort g#f (H)))| = min(|V ./@p g#f Q Ind−Rel (ΓSort (H)))) = min(40, 20 · 1) = 20 A∈l n(A, V ./@p g#f • Attributwertanzahlen: n(V.T itel, πlSort (V ./@pInd−Rel (ΓSort g#f (H)))) Sort = n(V.T itel, V ./@p (Γp#q (H))) = 20 n(H.Count, πlSort (V ./@pInd−Rel (ΓSort g#f (H)))) Sort = n(H.Count, V ./@p (Γg#f (H))) = 1 • Pagegröße: page(πlRel (V ./@pInd−Rel (ΓSort g#f (H)))) = · |l| |sch(V ./@Ind−Rel (ΓSort p g#f (H)))| = 20 55 · 2 4 |πlRel (V ./@Ind−Rel (ΓSort p g#f (H)))| |V ./@Ind−Rel (ΓSort p g#f (H))| · page(V ./@pInd−Rel (ΓSort g#f (H))) ·9=2 Die I/O-Kosten des kompletten Ausführungsplanes ergeben sich aus der Summe der Kosten, die an den einzelnen Knoten auftreten: I/O-Kosten gesamt:= 6 + 85 + 257 = 348 Kapitel 6 Implementierung Die in den vorangegangen Kapiteln aufgeführte Theorie wurde in dem Anfragesimulator RELOpt umgesetzt. In diesem Kapitel wird zuerst die grafische Oberfläche und ihre Benutzung beschrieben. Anschließend werden die implementierten Packages und Klassen dargestellt. 6.1 GUI Das Hauptfenster der Einphasenoptimierung ist über einen Knopf aus dem Startfenster von RELOpt zu erreichen. 6.1.1 Hauptfenster Die folgende Abbildung zeigt das Hauptfenster der Einphasenoptimierung: Abbildung 6.1: Hauptfenster der Einphasenoptimierung Wie in der Abbildung zu sehen ist, unterteilt es sich in drei Hauptbestandteile: 55 56 KAPITEL 6. IMPLEMENTIERUNG 1. Eingabe Das rot umrandete Fenster dient der Eingabe relationaler Anfragen. Wie in Kapitel 4.2 beschrieben wurde, wird bei dem vom Parser gelieferten generalisierten Baum eine kostenabhängige Auswahl der physischen Umsetzungen getroffen, um eine Startkonfiguration für den Termersetzer zu erhalten. Die Kosten dieses ersten Baumes werden im oberen Teil des Eingabefensters angezeigt. Sie sind aufgeteilt in I/O-, CPU- und Gesamtkosten. Nach der Eingabe einer Anfrage wird der Baum mit den gewählten physischen Realisierungen im Fenster dargestellt, wie man in Abbildung 6.1 sieht. Mit dem Knopf “Anfrage“ erhält man wieder die ursprüngliche Anfrage. 2. Ausgabe Das Ergebnis der Einphasenoptimierung wird in dem blau markierten Teil des Hauptfensters dargestellt. Wie bei dem Eingabefenster befindet sich auch hier am oberen Rand eine Anzeige der verschieden Kosten. 3. Regelauswahl Der grün umrandete Teil des Hauptfensters dient der Auswahl von Termersetzungsregeln die bei der Optimierung verwendet werden sollen. Auf der linken Seite werden die Ersetzungsregeln in Regelgruppen aufgeführt. Auf der rechten Seite kann ausgewählt werden ob eine Gruppe iteriert werden soll, d. h. diese Regeln werden angewendet bis keine Veränderungen mehr möglich sind. Sonst wird eine Gruppe nur ein Mal für eine Termersetzung genutzt. Genaue Informationen über eine Termersetzung und die Iteration von Regeln sind in [Rud05] zu finden. Soll eine Termersetzungsregel zu einer Regelgruppe hinzugefügt werden, muss die entsprechende Gruppe ausgewählt und der Knopf “Regel hinzufügen“ gedrückt werden. Anschließend erscheint eine leere Regel wie es in Abbildung 6.2 dargestellt ist. Mit einem Rechtsklick auf eine Ersetzungsregel öffnet sich der ebenfalls in Abbildung 6.2 zu sehende Dialog. Hier kann eine Regel ausgewählt oder betrachtet werden. Das Fenster zum Betrachten einer Regel entspricht dem Fenster zur Eingabe einer Regel. Dieses Fenster wird im nächsten Abschnitt beschrieben. Mit dem Knopf “Regel erzeugen“ gelangt man zu der Regeleingabe. Abbildung 6.2: Laden einer Regel Ist noch keine Gruppe vorhanden muss vor dem Hinzufügen einer Regel mit dem Knopf “Gruppe hinzufügen“ eine erstellt werden. Regeln und Gruppen lassen sich mit “Gruppe/Regel löschen“ aus der Liste wieder entfernen. Mit den Knöpfen Regelbaum speichern/laden können die Regelgruppen gespeichert und geladen werden. Die zwei Textfelder zur Eingabe einer sogenannten statischen und dynamischen Grenze dienen der Beschränkung der Varianten die bei einer Termersetzung entstehen. Dies wird ausführlich in [Rud05] behandelt. 6.1. GUI 6.1.2 57 Regeleingabe Das Fenster zur Regeleingabe besteht aus vier Teilen. Links oben wird der linke Regelbaum und rechts oben der recht Baum eingegeben. Die Felder links und rechts unten dienen der Eingabe der Überführungsbedingungen des linken bzw. rechten Regelbaumes. Keines der Fenster darf leer sein. Wenn ein Baum grundsätzlich ersetzt werden soll, muss als Bedingung true und wenn er nie ersetzt werden soll, muss f alse eingegeben werden. Abbildung 6.3: Erzeugen einer Regel Nach der Eingabe drückt man den Knopf “Regel parsen“. Wenn das Parsen erfolgreich war, wird der Knopf “Regel speichern“ freigegeben. Vor dem Speichern ist es möglich in dem Textfeld “Beschreibung“ eine Regelbeschreibung hinzuzufügen. Der Name der beim Speichern angegeben wird, ohne die Dateiendung “.grf“, ist der künftige Name der Regel. Über den Knopf “Hilfe“ wird ein Hilfedialog für die Regelterm- bzw. Regelbedingungseingabe geöffnet. Dieser ist in Abbildung 6.4 zu sehen: Abbildung 6.4: Hilfedialog 58 KAPITEL 6. IMPLEMENTIERUNG 6.1.3 Baumdarstellung Im Hauptfenster findet man zwei Knöpfe mit der Bezeichnung “Baumansicht“. Diese Knöpfe öffnen eine grafische Darstellung der generalisierten Bäume der Eingabe bzw. Ausgabe. Das Fenster für die Baumansicht ist in Abbildung 6.5 zu sehen: Abbildung 6.5: Baumansicht Jeder Knoten in diesem Baum kann per Mausklick ausgewählt werden. Für einen gewählten Knoten werden eine Reihe von Informationen auf der rechten Seite angezeigt. • Kardinalität der Relation • Pagegröße • Attributwertanzahlen der einzelnen Attribute Falls es sich bei dem ausgewählten Knoten um eine Operation und nicht um eine Basisrelation handelt, werden ebenfalls angezeigt: • die globale Kosten, welche bisher entstanden sind. • die lokale Kosten, aufgeteilt in I/O-, gewichtete und ungewichtete CPU- und die Gesamtkosten. • die physische Realisierung der Operation Werden zwei Knoten mit doppelten anstatt mit einfachen Strichen verbunden, wurde an dieser Stelle Pipelining angewandt. 6.2 Packages In der Abbildung 6.6 wird die Paketstruktur des Programmes dargestellt. Das Paket gui enthält die zur Darstellung des Hauptfensters und der Regeleingabe benötigten Klassen. Die Klassen zur grafischen Darstellung eines generalisierten Baumes befinden sich in tree. 6.2. PACKAGES 59 de.unihannover.dbs.sopt struc.general gui tree tree sun test conditions Abbildung 6.6: Paketstruktur Die Klasse RELOpt zum Ausführen des Hauptprogrammes liegt in test. In dem Paket struc.general befinden sich der relationale Parser und die für einen generalisierten Baum benötigten Klassen. 6.2.1 Package dbs.unihannover.sopt.struc.general Direkt in diesem Package befindet sich ausschließlich die Klasse GeneralRelationalP arser zum Umwandeln einer Anfrage in einen generalisierten Baum oder zum Erzeugen einer Termersetzungsregel. Der Aufbau dieses Parsers wurde in Kapitel 2 und Kapitel 3 dargestellt. Diese Klasse verfügt über folgende Methoden: • parseTree Diese Methode erwartet als Parameter eine relationale Anfrage als Zeichenkette, eine Liste der Basisrelationen und die zugehörigen Metadaten. Die Ausgabe ist ein generalisierter Baum, wie er in Kapitel 4 beschrieben wurde. • parseRule Die Übergabewerte an diese Methode sind jeweils zwei Zeichenketten für die Regelterme und die Regelbedingungen. Als Ausgabe erhält man eine komplette Termersetzungsregel. 6.2.2 Package dbs.unihannover.sopt.struc.general.tree In diesem Paket befinden sich die Klassen für den Aufbau von generalisierten Bäumen. Der Zusammenhang der Klassen wird in Abbildung 6.7 dargestellt. Die Klasse GeneralT ree speichert die Knoten des Baumes und bietet folgende Funktionen: • calculate Mit dieser Methode kann ein Baum neu berechnet werden, falls sich z. B. die Metadaten geändert haben. • countNodes Die Ausgabe dieser Methode ist die Anzahl Knoten und Blätter im Baum. • createTreeFromRule Dies ist die in Kapitel 4.7 beschriebene Funktion zum Erstellen eines generalisierten Baum aus einem gefüllten Regelterm. 60 KAPITEL 6. IMPLEMENTIERUNG • deepCopy Um eine Kopie des Baumes zu erhalten, muss diese Methode aufgerufen werden. • dump Mit dieser Funktion kann der Aufbau des Baumes als Zeichenkette ausgegeben werden. • equals Mit equals kann überprüft werden, ob zwei Bäume gleich sind. • getCost Die Gesamtkosten des Ausführungsplanes werden mit dieser Methode ausgegeben. • getIOCost Die I/O-Kosten des Ausführungsplanes werden mit dieser Methode ausgegeben. • getCPUCost Die ungewichteten CPU-Kosten werden mit dieser Methode ausgegeben. • getRoot Diese Funktion liefert die Wurzel des gespeicherten Baumes. • postOrderIterator Den in Kapitel 4.7 beschriebenen Iterator zum Durchlaufen eines Baumes erhält man mit einem Aufruf dieser Methode. • postOrderHasNext Mit dieser Methode wird geprüft, ob ein Post-Order-Iterator weitere Elemente enthält. • postOrderNext Die Ausgabe des nächsten Elementes geschieht mit dieser Funktion. Um später einfach Erweiterungen vornehmen zu können wurde ein Interface entwickelt, welches jeder Knoten in einem generalisierten Baum implementieren muss. Dieses Interface heißt GeneralT reeN ode. Es fordert folgende Methoden: • calculate - berechnet die Attribute eines Knotens neu • countNodes - gibt die Anzahl Knoten in diesem Teilbaum aus • deepCopy - liefert eine Kopie des Baumes ab diesem Knoten • equals - überprüft, ob zwei Teilbäume gleich sind • getAlias - liefert den Alias einer Operation 6.2. PACKAGES • getAttributeValueCount - gibt die Attributwertanzahl zu einem Attribut aus • getChild - liefert den Kindknoten an der übergebenen Position • getChildCount - gibt die Anzahl Kinder eines Knotens an • getCondition - gibt die zu der Operation gehörige Parametrisierung aus • getCost - liefert die lokalen Gesamtkosten • getGlobalCost - liefert die globalen Gesamtkosten • getCPUCost - gibt die lokalen CPU-Kosten aus • getGlobalCPUCost - gibt die globalen CPU-Kosten aus • getIOCost - liefert die entstanden I/O-Kosten an diesem Knoten • getGlobalIOCost - liefert die bisher entstanden I/O-Kosten des Teilbaumes • getIndexAttr - gibt eine Liste mit Attributmengen auf denen ein Index existiert zurück • getIndexCard - liefert die Kardinaliät eines Index • getIndexPage - liefert die Pagegröße eines Index • getName - gibt den Namen einer Operation oder Relation aus • getPageCardinality - liefert die Pagegröße einer Relation • getParent - liefert den übergeordneten Knoten • getPhysOp - gibt die physische Umsetzung einer Operation aus 61 62 KAPITEL 6. IMPLEMENTIERUNG • getScheme - liefert das Schema einer Relation • getSortedAttributes - gibt eine Liste von Attributmengen aus, auf den eine Sortierung existiert • getTupelCardinality - liefert die Kardinalität einer Relation • hasIndexOn - mit dieser kann bestimmt werden, ob auf einer Attributmenge ein Index existiert • isClusteredIndex - mit dieser kann bestimmt werden, ob ein Index geclustert ist • isSortedOn - mit dieser kann bestimmt werden, ob auf einer Attributmenge eine Sortierung existiert • pipeliningAtChild - gibt an, ob bei der Relation an der übergebenen Position Pipelining angewandt werden kann • setAlias - setzt bei einer Relation einen Alias • setChild - setzt das Kind an der übergebenen Position • setParent - setzt den übergeordneten Knoten • setPhysOp - setzt die physische Umsetzung Dieses Interface wird direkt in der Klasse GeneralT able, welche eine Basisrelation darstellt, umgesetzt. Für die anderen Operatoren wurden zur Unterscheidung von variablen und normalen Knoten abstrakte Oberklassen implementiert, welche das Interface nutzen. Für die normalen Operatoren existieren die abstrakten Klassen AbstractBinaryOperation für binäre Operatoren und AbstractU naryOperation für Operatoren mit nur einer Argumentrelation. Diese Klassen implementieren bereits einen Großteil der von GeneralT reeN ode geforderten Methoden. Die einzelnen Knoten müssen noch die folgenden Funktionen umsetzen: calculate, deepCopy, dump, equals, getName, setPhysOp Für variable Knoten wurde einmal die abstrakte Klasse AbstractV ariable eingeführt. Sie implementiert einen großen Teil von dem Interface GeneralT reeN ode, führt aber weitere Methoden ein, welche von variablen Knoten umgesetzt werden müssen: • getTreeNode - liefert einen konkreten Knoten indem er die gefüllten Werte des Regelknotens verwendet 6.2. PACKAGES 63 • getRealCondition - gibt bei einem gefüllten Knoten den Wert der Variablen aus • setRealCondition - setzt den Wert für die variable Parametrisierung Zur Unterscheidung von binären und einfachen variablen Operationen wurden ebenfalls zwei Klassen, AbstractU naryV ariable und AbstractBinaryV ariable, eingeführt. In der folgenden Abbildung ist dies dargestellt: <<interface>> GeneralTreeNode +calculate(MetaData): void +countNodes(): int +deepCopy(): GeneralTreeNode +dump(int): void +equals(Object): boolean +getAlias (): String +getAttributeValueCount(Attribute,MetaData): long +getChild(int): GeneralTreeNode +getChildCount(): int +getCondition(): ConditionTree +getCost(double): long +getGlobalCost(double): long +getCPUCost(): long +getGlobalCPUCost(): long +getIOCost(): long +getGlobalIOCost(): long +getIndexAttr(): Vector<AttributeSet> +getIndexCard(AttributeSet,MetaData): long +getIndexPage(AttributeSet,MetaData): long +getName(): String +getPageCardinality(MetaData): long +getParent(): GeneralTreeNode +getPhysOp(): String +getScheme(): AttributeSet +getSortedAttributes(MetaData): Vector<AttributeSet> +getTupelCardinality(MetaData): long +hasIndexOn(AttributeSet:MetaData): boolean +isClusteredIndex(AttributeSet,MetaData): boolean +isSortedOn(AttributeSet,MetaData): boolean +pipeliningAtChild(int): booelan +setAlias(String): void +setChild(int,GeneralTreeNode): void +setParent(GeneralTreeNode): void +setPhysOp(String): void GeneralTable VarRelation AbstractVariable AbstractBinaryOperation AbstractUnaryOperation GeneralJoin GeneralSemiJoin +getRealCondition(): ConditionTree +setRealCondition(ConditionTree): void +getTreeNode(MetaData): GeneralTreeNode Sort GeneralAntiSemiJoin Index AbstractBinaryVariable GeneralOuterJoin GeneralSelection VarJoin GeneralProjection VarSemiJoin AbstractUnaryVariable GeneralLeftOuterJoin GeneralRightOuterJoin GeneralAggregation VarAntiSemiJoin GeneralProduct VarSort VarIndex VarOuterJoin VarSelection GeneralUnion VarLeftOuterJoin VarProjection GeneralIntersection VarRightOuterJoin VarAggregation GeneralDifference VarProduct VarUnion VarIntersection VarDifference Abbildung 6.7: Klassendiagramm dbs.unihannover.sopt.struc.general.tree 64 KAPITEL 6. IMPLEMENTIERUNG Die Operatoren Sort und Index übernehmen die Knotenattribute ihres Kindknotens, fügen aber zu den Metadaten die Information über die erzeugten Indexe oder Sortierungen hinzu. Wird ein Index erzeugt, müssen für die Kostenabschätzungen in Kapitel 5 |Index| und page(Index) bekannt sein. Zur Abschätzung dieser Werte wurden die bereits in RELOpt implementierten Funktionen aus der Klasse de.unihannover.dbs.sopt.struc.phys.M etaData genutzt. 6.2.3 Package dbs.unihannover.sopt.struc.general.tree.condition Die Klassen zur Erstellung von Parametrisierungen für Operationen sind in dem Package .tree.condition zu finden. Um arithmetische Ausdrücke, wie sie in Kapitel 2.1.3 beschrieben sind, speichern zu können, wurde eine Baumstruktur für die Operatoren und Operanden entworfen. Jeder Knoten in dieser Struktur muss das Interface ArithmeticT erm implementieren: Plus Minus <<Interface>> ArithmeticTerm Times +addChild(ArithmeticTerm): void +deepCopy(): ArithmeticTerm +getAlias(): Attribute +getAttributes(): AttributeSet +getChildAt(int): ArtihmeticTerm +getChildCount(): int +removeChildAt(int): void +setAlias(String): void +toString(): String Divide Column Digit +getValue(): int Abbildung 6.8: Klassendiagramm arithmetische Terme Die Klassen Plus, Minus, Times und Divide stellen die Operatoren +, −, ·, / dar. Die Klasse Column wird genutzt zum Speichern von Attributen und die Klasse Digit zum Speichern von Zahlen. Folgende Methoden werden von dem Interface ArithmeticT erm verlangt: • addChild - fügt einen Kindknoten hinzu • deepCopy - liefert eine Kopie des Baumes ab diesem Knoten • getAlias - liefert den Alias für einen arithmetischen Term • getAttributes - gibt die in dem Teilbaum vorhandenen Attribute aus • getChildAt - liefert den Kindknoten an der übergebenen Position 6.2. PACKAGES 65 • getChildCount - gibt die Anzahl Kinder zurück • removeChildAt - entfernt einen Kindknoten • setAlias - setzt den Alias für einen arithmetischen Ausdruck • toString Da Selektions- und Verbundbedingungen logisch verknüpft werden können, wurde eine Baumstruktur zur einheitlichen Speicherung der Parametrisierungen verwendet. <<Interface>> ConditionAggregation ConditionTreeNode +getAliases(): Vector<Attribute> +getFunctions(): Vector<String> +getFunctionAttr(): Vector<Attribute> +getGroupAttr(): Vector<Attribute> +getProjAttr(): Vector<Attribtue> +addChild(ConditionTreeNode): void +countNodes(): int +deepCopy(): ConditionTreeNode +equals(Object): boolean +getAttributes(): AttributeSet +getChildAt(int): ConditionTreeNode +getChildCount(): int +getParent(): ConditionTreeNode +removeChildAt(int): void +replaceChildAt(int,ConditionTreeNode): void +setParent(ConditionTreeNode): void +toString(): String VarCondition ConditionProjection +getArithmeticAttr(): Vector<ArithmeticTerm> AbstractCondition +getSelektivity(GeneralTreeNode,MetaData): double +getThetas(): Vector<Theta> NOT AtomicCondition OR AND Abbildung 6.9: Klassendiagramm Parametrisierungen Jeder Knoten in einem solchen Baum muss das Interface ConditionT reeN ode implementieren. Gefordert werden von dem Interface folgende Methoden: • addChild - setzt den Kindknoten bei Verknüpfungen wie z. B. AND • countNodes - zählt die Knoten in einem Teilbaum • deepCopy - kopiert einen Teilbaum • equals - prüft, ob zwei Objekte gleich sind • getAttributes - liefert die Attribute auf die sich eine Parametrisierung bezieht 66 KAPITEL 6. IMPLEMENTIERUNG • getChildAt - liefert den Kindknoten an der übergebenen Position • getChildCount - gibt die Anzahl Kindknoten aus • getParent - liefert den übergeordneten Knoten • removeChildAt - entfernt einen Kindknoten • replaceChildAt - tauscht einen Kindknoten aus • setParent - setzt den übergeordneten Knoten • toString - liefert die Parametrisierung als Zeichenkette Die Klasse AtomicCondition wird verwendet zum Speichern von Selektions- und Verbundbedingungen. Objekte dieser Klasse können durch AN D, OR und N OT verknüpft werden. Das selbe gilt für die Klasse V arCondition, welche für variable Parametrisierungen in Regeltermen verwendet wird. Dabei sind AN D und OR nicht auf zwei Argumente beschränkt, sondern können beliebig viele Kinder haben. Die Klassen ConditionP rojection und ConditionAggregation dienen der Speicherung von Projektions- bzw. Aggregationsparametrisierungen. Objekte dieser Klassen können nur als einzelne Elemente verwendet und nicht durch AN D, OR oder N OT verknüpft werden. Die Knoten werden in der Klasse ConditionT ree geseichert. Diese Klasse stellt folgende Methoden zur Verfügung: • countNodes - zählt die Knoten in einem Teilbaum • deepCopy - kopiert einen Teilbaum • equals - prüft, ob zwei Objekte gleich sind • fetchCondition - realisiert die in Kapitel 3.1 vorgestellte Funktion • fetchJoinCondition - setzt die in Kapitel 3.1 vorgestellte Funktion um • minus - implementiert die in Kapitel 3.1 vorgestellte Funktion 6.2. PACKAGES 67 • getAttributes - liefert die Attribute auf die sich eine Parametrisierung bezieht • getRoot - gibt die Wurzel des Baumes zurück • setRoot - setzt die Wurzel eines Baumes • toString - liefert den Baum als Zeichenkette Das Paket .condition enthält außerdem die Klassen für das in Kapitel 2.3.1 beschriebene Verfahren von Quine-McCluskey zur Minimierung von logisch verknüpften Selektionsbedingungen. 1. ValueTable Mit dieser Klasse wird eine Wertetabelle dargestellt. Sie bietet folgende Methoden: - fill: Dieser Methode wird eine Selektionsbedingung in disjunktiver Form übergeben, aus der die Tabelle aufgebaut wird. - getMinTerms: Die Minterme der Wertetabelle können mit dieser Funktion ausgegeben werden. 2. FirstQuineTable Diese Klasse repräsentiert die erste Quine’sche Tabelle. Bei der Erzeugung eines Objektes dieser Klasse werden die Minterme, welche man aus V alueT able erhält, übergeben. Mit der Funktion getPrimterms können anschließend die gefundenen Primterme ausgegeben werden. 3. SecoundQuineTable Mit den Primtermen aus F irstQuineT able kann die zweite Quine’sche Tabelle aufgebaut werden. Dazu wird die Klasse SecoundQuineT able verwendet. Die Funktion getDMF wird genutzt um abschließend die disjunktive Minimalform ausgeben zu lassen. 4. ConditionMinimizer Die Klasse ConditionM inimizer bekommt bei der Erzeugung eines Objektes eine logisch verknüpfte Selektionsbedingung übergeben, welche in eine disjunktive Form gebracht wird. Anschließend werden Objekte der Klassen V alueT able, F irstQuineT able und SecoundQuineT able erzeugt, um eine disjunktive Minimalform zu erhalten. 6.2.4 Erweiterungen am Package dbs.unihannover.sopt.gui Die Klassen zum Aufbau der grafischen Benutzerschnittstelle der Einphasenoptimierung befinden sich in diesem Paket. Neu hinzugekommen sind die Klassen: • GeneralEinPhasEdit - realisiert das Hauptfenster der Einphasenoptimierung • GeneralRuleDialog - realisiert das Fenster zur Regeleingabe 68 KAPITEL 6. IMPLEMENTIERUNG • GeneralRulePanel - bietet einen Container für die Darstellung der Termersetzungsregeln im Hauptfenster • GeneralRuleTable - dient zum Anzeigen der Regelgruppen • GeneralRuleTreeTableModel - implementiert das Verhalten der Tabelle GeneralRuleT able in Bezug auf das Anzeigen von Daten oder Benutzereingaben • OperatorInfoDialog4 - realisiert den Hilfedialog für die Eingabe von Regeltermen • RuleInfoDialog2 - realisiert den Hilfedialog für die Eingabe von Regelbedingungen Die Klasse T reeT extArena wurde angepasst, um die Hilfedialoge OperatorInf oDialog4 und RuleInf oDialog2 in dem Fenster für die Regeleingabe öffnen zu können. 6.2.5 Erweiterungen am Package dbs.unihannover.sopt.tree Die Klassen in diesem Package wurden erweitert um eine grafische Darstellung von generalisierten Bäumen zu ermöglichen. Folgende Klassen mussten angepasst werden: • Node Diese Klasse repräsentiert einen allgemeinen Knoten in dem grafisch dargestellten Baum. • TreeView und TreeFrame Mit diesen Klassen werden Objekte die von N ode abstammen in einem Fenster positioniert und gezeichnet. Kapitel 7 Ausblick • Späte Duplikateneleminierung Eine Projektion in RELOpt ist momentan eine Projektion bei der grundsätzlich Duplikate entfernt werden. Um dem Optimierungssystem die Möglichkeit zu geben kostspielige Duplikateleminierungen erst beim Endergebnis auszuführen, wäre es sinnvoll eine nicht-duplikateleminierende Projektion zu implementieren. • Erweiterung der mitgeführten Metadaten und Verfeinerung des Kostenmodells Um eine verbesserte inhaltliche Analyse einer Anfrage und verfeinerte Kostenabschätzungen zu ermöglichen, sollten weitere Metadaten bei der Optimierung mitgeführt werden. Beispielsweise könnten Typen, Bytelängen oder minimale und maximale Werte der einzelnen Attribute einer Relation gespeichert werden. Die Information über den Attributtyp hilf bei der semantischen Analyse. Pagegrößen könnten genauer mit vorhanden Bytelängen bestimmt werden und das Speichern von minimalen und maximalen Attributwerten würde z. B. eine Verbesserung der Kostenfunktion für Merge-Joins ermöglichen. • Hash-Algorithmen Momentan verfügt das erstellte Kostenmodell nicht über Abschätzungen für HashJoins. Diese sollten in Zukunft hinzugefügt werden. • Erweiterung der Sprache zur Erzeugung von Regelbedingungen Eine große Menge von möglichen Bedingungen bei einer Termersetzungsregel ist die Grundlage um ein großes Spektrum an Optimierungsstrategien zu ermöglichen. Aus diesem Grund wäre es denkbar weitere Funktionen zum vorhandenen Wortschatz hinzuzufügen, z. B. eine Funktion zum bestimmen von Attributwertanzahlen. 69 70 KAPITEL 7. AUSBLICK Anhang A Relationaler Parser A.1 A.1.1 Grammatik Nichtterminale Symbole N ={ <start>, <operation>, <operatorSet>, <operatorJoin>, <groupby>, <additionalTarget>, <function>, <additionalFunction>, <conditionProjection>, <conditionSelection>, <conditionOperator>, <innerCS>, <innerFunction>, <additionalCS>, <additionalCO>, <phySingle>, <phyBinary>, <tree>, <bigLetter>, <aliasRelation>, <alias>, <calcOrTerm>, <calcTarget>, <numeric>, <calcOperand>, <calculation>, <target>, <letter>, <followTerm>, <term>, <additionalCondition>, <varRelation>, <varCondition>, <valueOperator>, <functionOperator>, <followNum>, <additionalTarget> }, A.1.2 Terminale Symbole Zu den terminalen Zeichen kommen zur Laufzeit noch die Namen von Relationen und Attributen hinzu. Diese werden in der Grammatik durch RELAT ION und AT T RIBU T E gekennzeichnet und müssen beim parsen auf ihr Vorhandensein geprüft werden. T ={ +, -, *, /, ≤, > , =, ! =, <, ≥ 0, 1, ... , 9, a, b, ... , z, A, B, ... , Z, %, , #, ,,’, RELAT ION , AT T RIBU T E, and, or, not, as, min, max, count, sum, avg, (, ) AGGREGATION, PROJECTION, SELECTION, JOIN, SEMIJOIN, ANTISEMIJOIN, LEFTOUTERJOIN, RIGHTOUTERJOIN, OUTERJOIN, UNION, INTERSECT, MINUS, PRODUCT, rel, ind, sort, rel-rel, ind-rel, rel-ind, ind-ind, sort-sort, hash-hash }, 71 72 ANHANG A. RELATIONALER PARSER A.1.3 Startsymbol S={ <start> } A.1.4 Produktionen P ={ <start> → (<operation>) <start> → RELAT ION <start> → [<varRelation>] <operation> → aggregation <phySingle> (<groupby>#<function>) <start> <aliasRelation> <operation> → projection <phySingle> (<conditionProjection>) <start> <aliasRelation> <operation> → selection <phySingle> (<conditionSelection>) <start> <aliasRelation> <operation> → <operatorJoin> <phyBinary> (<conditionOperator>) <start>, <start> <aliasRelation> <operation> → <operatorSet> <phyBinary> <start>, <start> <aliasRelation> <operation> → <tree> (<target><additionalTarget>) <start> <operation> → RELAT ION <aliasRelation> <phySingle> → rel | ind | sort | ε <phyBinary> → rel-rel | ind-rel | rel-ind | ind-ind | sort-sort | hash-hash | ε <varRelation> → <bigLetter><followTerm> <varCondition> → <letter><followTerm> <conditionProjection> → <calcTarget> <alias><additionalCondition> <conditionProjection> → [<varCondition>] <conditionSelection> → <calcTarget> <valueOperator> <calcOrTerm> <conditionSelection> → [<varCondition>] <conditionSelection> → <innerCS> <conditionOperator> → <target> <valueOperator> <target> <conditionOperator> → [<varCondition>] <conditionOperator> → and (<conditionOperator>, <conditionOperator><additionalCO>) <groupby> → <target><additionalTarget>| [<varCondition>] | ε <function> → <innerFunction><additionalFunction> | [<varCondition>] <aliasRelation> → as <varRelation> | ε <alias> → as <varRelation>.<letter><term> | ε <innerFunction> → sum(<target>) as <alias> | count(<target>) as <alias> | max(<target>) as <alias>| avg(<target>) as <alias>| min(<target>) as <alias>| <target> A.2. BEWEIS LL(1)-BEDINGUNG 73 <innerCS> → not (<conditionSelection>) <innerCS> → and (<conditionSelection>, <conditionSelection><additionalCS>) <innerCS> → or (<conditionSelection>, <conditionSelection><additionalCS>) <additionalCondition> → , <conditionProjection> | ε <additionalFunction> → , <function> | ε <additionalCO> → , <conditionOperator> | ε <additionalCS> → , <conditionSelection> | ε <additionalTarget> → , <target><additionalTarget> | ε <calcOrTerm> → <calcTarget> | ’<term>’ <calcTarget> → <calcOperand> <calculation> <calcOperand> → (<calcTarget>) | <numeric> | <target> <calculation> → <functionOperator> <calcOperand> | ε <target> → RELAT ION .AT T RIBU T E <tree> → SORT | INDEX <term> → 0<followTerm> | ... | 9<followTerm> | <followTerm> a<followTerm> | ... | Z<followTerm> | %<followTerm> <followTerm> → <term> | ε <operatorSet> → PRODUCT | UNION | INTERSECT | MINUS <operatorJoin> → JOIN | SEMIJOIN | ANTISEMIJOIN | LEFTOUTERJOIN | RIGHTOUTERJOIN | OUTERJOIN <letter> → a | ... | z <bigLetter> → A | ... | z <numeric> → 0<followNum> | ... | 9<followNum> <followNum> → <numeric> | ε <valueOperator> → >= | > | = | ! = | < | <= <functionOperator> → + | - | * | / } A.2 Beweis LL(1)-Bedingung 1. Bedingung • <start> First((<operation>)) = {(}, First([<varRelation>]) = {[} und First(RELAT ION ) müssen disjunkt sein. Aus den Produktionen geht hervor, dass der Name einer Relation nicht ’( ’ oder ’[ ’ sein darf. Daher sind die beiden Menge in jedem Fall disjunkt! • <operation> First(AGGREGATION <phySingle> (<groupby>#<function>) <start> <aliasRelation>) = {AGGREGATION}, 74 ANHANG A. RELATIONALER PARSER First(PROJECTION <phySingle> (<conditionProjection>) <start> <aliasRelation>) = {projection}, First(SELECTION <phySingle> (<conditionSelection>) <start> <aliasRelation>) = {selection}, First(<operatorJoin> <phyBinary> (<conditionOperator>) <start>, <start> <aliasRelation>) = { JOIN, SEMIJOIN, ANTISEMIJOIN, LEFTOUTERJOIN, RIGHTOUTERJOIN, OUTERJOIN }, First(<operatorSet> <phyBinary> <start>, <start> <aliasRelation>) = { product, union, intersect, minus } und First(<tree> (<target><additionalTarget>) <start>) = {SORT, INDEX} sind paarweise disjunkt! Damit auch First(RELAT ION <aliasRelation>) zu den anderen paarweise disjunkt ist, darf eine Relation nicht den Namen eines Operators oder ’[’ haben! • <phySingle> First(rel) = {rel}, First(ind) = {ind}, First(ind) = {sort}, First(ε) = {ε} sind paarweise disjunkt! • <phyBinary> First(rel-rel) = {rel-rel}, First(rel-ind) = {rel-ind}, First(ind-rel) = {ind-rel}, First(ind-ind) = {ind-ind}, First(sort-sort) = {sort-sort}, First(hash-hash) = {hash-hash}, First(ε) = {ε} sind paarweise disjunkt! • <conditionProjection> First(<calcTarget> <alias><additionalCondition>) = { (, a, ... , Z, 1, ... , 9 }, First([<varCondition>]) = {[} sind disjunkt! • <conditionSelection> First(<calcTarget> <valueOperator> <calcOrTerm>) = { (, a, ... , Z, 1, ... , 9 }, First(<innerCS>) = {and, or, not}, A.2. BEWEIS LL(1)-BEDINGUNG First([<varCondition>]) = {[} sind paarweise disjunkt! • <conditionOperator> First(<target> <valueOperator> <target>) = { A, ... , Z }, First(and (<conditionOperator>, <conditionOperator><additionalCO>)) = {and}, First([<varCondition>]) = {[} sind paarweise disjunkt! • <groupby> First(<target><additionalTarget>) = {A,... , Z}, First(ε) = {ε}, First([<varCondition>]) = {[} sind paarweise disjunkt! • <function> First(<innerFunction><additionalFunction>) = { sum, min, max, count, avg, A, ... , Z }, First([<varCondition>]) = {[} sind paarweise disjunkt! • <aliasRelation> First(as <varRelation>) = {as}, First(ε) = {ε} sind disjunkt! • <alias> First(as <varRelation>.<letter><term>) = {as}, First(ε) = {ε} sind disjunkt! • <innerFunktion> First(sum(<target> as <alias>)) = {sum}, First(count(<target> as <alias>)) = {count}, First(max(<target> as <alias>)) = {max}, First(min(<target> as <alias>)) = {min}, First(avg(<target> as <alias>)) = {avg}, First(<target>) = {A, ... , Z} sind paarweise disjunkt! 75 76 ANHANG A. RELATIONALER PARSER • <innerCS> First(not (<conditionSelection>)) = {not}, First(and (<conditionSelection>, <conditionSelection><additionalCS>)) = {and}, First(or (<conditionSelection>, <conditionSelection><additionalCS>)) = {or} sind paarweise disjunkt! • <additionalCondition> First(, <conditionProjection>) = {,}, First(ε) = {ε} sind disjunkt! • <additionalFunction> First(, <function>) = {,}, First(ε) = {ε} sind disjunkt! • <additionalCS> First(, <conditionSelection>) = {,}, First(ε) = {ε} sind disjunkt! • <additionalCO> First(, <conditionOperator>) = {,}, First(ε) = {ε} sind disjunkt! • <additionalTarget> First(, <target><additionalTarget>) = {,}, First(ε) = {ε} sind disjunkt! • <calcOrTerm> First(<calcTarget>) = {(, A, ... , Z, 0, ... , 9}, First(’<term>’) = {’} sind disjunkt! • <calcOperand> First((<calcTarget>)) = {(}, First(<numeric>) = {0, ... , 9}, First(<target>) = {a, ... , Z} sind paarweise disjunkt! A.2. BEWEIS LL(1)-BEDINGUNG 77 • <calculation> First(<functionOperator> <calcOperand>) = {+, -, *, /}, First(ε) = {ε} sind disjunkt! • <tree> First(SORT) = {SORT}, First(INDEX) = {INDEX} sind paarweise disjunkt! • <term> First(0<followTerm>) = {0}, ... , First(9<followTerm>) = {9}, First(a<followTerm>) = {a}, ... , First(Z<followTerm>) = {Z}, First( <followTerm>) = { }, First(%<followTerm>) = {%} sind paarweise disjunkt! • <followTerm> First(<term>) = {0, ... , 9, a, ... , Z, , %}, First(ε) = {ε} sind disjunkt! • <operatorSet> First(PRODUCT) = {PRODUCT}, First(UNION) = {UNION}, First(INTERSECT) = {INTERSECT}, First(MINUS) = {MINUS} sind paarweise disjunkt! • <operatorJoin> First(JOIN) = {JOIN}, First(SEMIJOIN) = {SEMIJOIN}, First(ANTISEMIJOIN) = {ANTISEMIJOIN}, First(LEFTOUTERJOIN) = {LEFTOUTERJOIN}, First(RIGHTOUTERJOIN) = {RIGHTOUTERJOIN}, First(OUTERJOIN) = {OUTERJOIN} sind paarweise disjunkt! • <numeric> First(0<followNum>) First(2<followNum>) First(4<followNum>) First(6<followNum>) First(8<followNum>) = = = = = sind paarweise disjunkt! {0}, {2}, {4}, {6}, {8}, First(1<followNum>) First(3<followNum>) First(5<followNum>) First(7<followNum>) First(9<followNum>) = = = = = {1}, {3}, {5}, {7}, {9} 78 ANHANG A. RELATIONALER PARSER • <followNum> First(<numeric>) = {0, ... , 9}, First(ε) = {ε} sind disjunkt! • <letter> First(a) = {a}, ... , First(z) = {z} sind paarweise disjunkt! • <bigLetter> First(A) = {A}, ... , First(Z) = {Z} sind paarweise disjunkt! • <valueOperator> First(>=) = {>=}, First(>) = {>}, First(=) = {=}, First(! =) = {! =}, First(<) = {<}, First(<=) = {<=} sind paarweise disjunkt! • <functionOperator> First(+) = {+}, First(-) = {-}, First(*) = {*}, First(/) = {/} sind paarweise disjunkt! 2. Bedingung • <phySingle> Follow(<phySingle>) = {(}, First(rel) = {rel}, First(ind) = {ind}, First(sort) = {sort} sind paarweise disjunkt! • <phyBinary> Follow(<phyBinary>) = {(, RELAT ION , [}, First(rel-rel) = {rel-rel}, First(rel-ind) = {rel-ind}, First(ind-rel) = {ind-rel}, First(ind-ind) = {ind-ind}, First(sort-sort) = {sort-sort}, First(hash-hash) = {hash-hash} sind paarweise disjunkt, falls keine Relation die Bezeichnung einer physischen Umsetzung hat! • <groupby> Follow(<groupby>) = {#}, A.2. BEWEIS LL(1)-BEDINGUNG First(<target><additionalTarget>) = {A, ... , Z} und First([varCondition]) = {[} sind paarweise disjunkt! • <aliasRelation> Follow(<aliasRelation>) = {), ,, $}, First(as <varRelation>.<bigLetter><term>) = {as} sind disjunkt! • <alias> Follow(<alias>) = {), ,}, First(as <varRelation>) = {as} sind disjunkt! • <additionalCondition> Follow(<additionalCondition>) = {)}, First(, <conditionProjection>) = {,} sind disjunkt! • <additionalTarget> Follow(<additionalTarget>) = {#, )}, First(, <target><additionalTarget>) = {,} sind disjunkt! • <additionalFunction> Follow(<additionalFunction>) = {)}, First(, <function>) = {,} sind disjunkt! • <additionalCO> Follow(<additionalCO>) = {)}, First(, <conditionOperator>) = {,} sind disjunkt! • <additionalCS> Follow(<additionalCS>) = {)}, First(, <conditionSelection>) = {,} sind disjunkt! • <calculation> Follow(<calculation>) = {)}, First(<functionOperator> <calcOperand>) = {+, -, *, /} sind disjunkt! 79 80 ANHANG A. RELATIONALER PARSER • <followTerm> Follow(<followTerm>) = {’, ,, +, -, *, /, )}, First(<term>) = {0, ... , 9, a, ... , Z, , %} sind disjunkt! • <followNum> Follow(<followNum>) = {), ,, +, -, *, /}, First(<numeric>) = {0, ... , 9} sind disjunkt! Damit erfüllt obere Grammatik die LL(1)-Bedingung. A.3 Beispiel für einen Ableitungsbaum Für die Anfrage SELECT ION (S.Semester =’5’) S ergibt sich folgender Ableitungsbaum: Abbildung A.1: Beispiel für einen Ableitungsbaum Anhang B Regelbedingungsparser B.1 B.1.1 Grammatik Nichtterminale Symbole N ={ <start>, <bedingung>, <mengenOperation>, <zuweisungsOperation>, <vergleichsOperation>, <kostenOperationt>, <boolscheOperation>, <element>, <berechnung>, <operator>, <variableBedingung>, <variableRelation> <variableAttributmenge>, <mengenOperator>, <vergleichsOperator>, <firstElement> <teilmengenOperator>, <zuweisungsOperator>, <funktion>, <verknüpfung>, <negation>, <verknüpfungAND>, <verknüpfungOR>, <verknüpfungMINUS>, <operatoren>, <operatorTyp1>, <weitereOperatoren>, <groessenOperator>, <multiplikator>, <kostenfunktion>, <baum>, <existenzfunktion>, <zahl> }, B.1.2 Terminale Symbole T ={ +, -, *, /, >=, > , =, ! =, <, <=, ], ]=, [, [=, 0, 1, ... , 9, a, b, ... , z, A, B, ... , Z, and, or, not, null, union, intersect, attr, sch, fetch condition, fetch joinCondition, fetch first, fetch rest, op, card, page, sel, cost, left, right, index supported, merge supported, sorted }, B.1.3 Startsymbol S={ <start> } 81 82 ANHANG B. REGELBEDINGUNGSPARSER B.1.4 Produktionen P ={ <start> → <bedingung> <bedingung> <bedingung> <bedingung> <bedingung> <bedingung> → → → → → <zuweisungsOperation> <vergleichsOperation> <mengenOperation> <kostenOperation> <boolscheOperation> <zuweisungsoperation> → <variableBedingung> <zuweisungsOperator> <funktion> <verknüpfung> <funktion> → <negation> <variableBedingung> | fetch condition(<variableBedingung>, <variableRelation>) | fetch first(<variableBedingung>) | fetch rest(<variableBedingung>) | fetch joinCondition(<variableBedingung>,<variableRelation>, <variableRelation>) <verknüpfung> → <verknüpfungAND> | <verknüpfungOR> | <verknüpfungMINUS> <verknüpfungAND> → AND <funktion> <verknüpfungAND> | ε <verknüpfungOR> → OR <funktion> <verknüpfungOR> | ε <verknüpfungMINUS> → − <funktion> <verknüpfungMINUS> | ε <negation> → NOT | ε <vergleichsOperation> → op(<variableBedingung>) <vergleichsOperator> {<operatoren>} <operatoren> → <operatorTyp1> <weitereOperatoren> <weitereOperatoren> → , <operatoren> | ε <operatorTyp1> → <groessenOperator> | <zuweisungsOperator> <mengenOperation> → <firstElement> <berechnung> <operator> <element> <berechnung> <firstElement> → attr(<variableBedingung>) | sch(<variableRelation>) <element> → attr(<variableBedingung>) | sch(<variableRelation>) | <variableAttributmenge> | null <berechnung> → <mengenOperator> <element> | ε <operator> → <vergleichsOperator> | <teilmengenOperator> | <zuweisungsOperator> <mengenOperator> → union | intersect <kostenOperation> → <multiplikator> <kostenfunktion> <operatorTyp1> <multiplikator> <kostenfunktion> <multiplikator> → <zahl> ∗ | ε <kostenfunktion> → card(<variableRelation>) | page(<variableRelation>) | sel(<variableBedingung>, <variableRelation>) | cost(<baum>) <baum> → left | right <boolscheOperation> → <negation> <existenzfunktion> <existenzfunktion> → sorted(<variableAttributmenge>, <variableRelation>) | index supported(<variableAttributmenge>, <variableRelation>) | merge supported(<variableBedingung>, <variableRelation>, <variableRelation>) B.2. BEWEIS LL(1)-BEDINGUNG <vergleichsOperator> → == | ! = <teilmengenOperator> → [= | [ | ] | ] = <groessenOperator> → >= | > | < | <= <zuweisungsOperator> → = <variableBedingung> → <buchstabe><term> <variableRelation> → <grossBuchstabe><term> <variableAttributmenge> → <buchstabe><term> <buchstabe> → a | ... | z <grossBuchstabe> → A | ... | z <term> → 0<followTerm> | ... | 9<followTerm> | a<followTerm> | ... | Z<followTerm> <followTerm> → <term> | ε <zahl> → 0<follow> | ... | 9<follow> <follow> → <zahl> | ε } B.2 Beweis LL(1)-Bedingung 1. Bedingung • <bedingung> First(<zuweisungsOperation>) = {a,... ,z}, First(<vergleichsOperation>) = {op}, First(<mengenOperation>) = {attr, sch, null}, First(<kostenOperation>) = {0,... ,9, sel, card, page, cost}, First(<boolscheOperation>) = { NOT, sorted, index supported, merge supported } sind paarweise disjunkt! • <funktion> First(<negation> <variableBedingung>) = {NOT, a,... ,z}, First(fetch condition(<variableBedingung>,<variableRelation>)) = {fetch condition}, First(fetch joinCondition(<variableBedingung>,<variableRelation>, <variableRelation>)) = {fetch joinCondition}, First(fetch first(<variableBedingung>)) = {fetch first}, First(fetch rest(<variableBedingung>)) = {fetch rest} sind paarweise disjunkt! • <verknüpfung> First(<verknüpfungAND>) = {AND, ε}, First(<verknüpfungOR>) = {OR, ε}, 83 84 ANHANG B. REGELBEDINGUNGSPARSER First(<verknüpfungMINUS>) = {-, ε} sind paarweise disjunkt! • <verknüpfungAND> First(AND <funktion> <verknüpfungAND>) = {AND}, First(ε) = {ε} sind disjunkt! • <verknüpfungOR> First(OR <funktion> <verknüpfungOR>) = {OR}, First(ε) = {ε} sind disjunkt! • <verknüpfungMINUS> First(- <funktion> <verknüpfungAND>) = {-}, First(ε) = {ε} sind disjunkt! • <negation> First(NOT) = {NOT}, First(ε) = {ε} sind disjunkt! • <weitereOperatoren> First(, <operator>) = {,}, First(ε) = {ε} sind disjunkt! • <operatorTyp1> First(<groessenOperator>) = {>=, >, <, <=}, First(<zuweisungsOperator>) = {=} sind disjunkt! • <firstElement> First(sch(<variableRelation>)) = {sch}, First(attr(<variableBedingung>)) = {attr} sind paarweise disjunkt! • <element> First(sch(<variableRelation>)) = {sch}, First(attr(<variableBedingung>)) = {attr}, First(<variableAttributmenge>) = {a,... ,z}, First(null) = {null} sind paarweise disjunkt! B.2. BEWEIS LL(1)-BEDINGUNG • <berechnung> First(<mengenOperator> <element>) = {union, intersect}, First(ε) = {ε} sind disjunkt! • <operator> First(<vergleichsOperator>) = {==, ! =}, First(<teilmengenOperator>) = {] =, ], [, [=}, First(<zuweisungsOperator>) = {=} sind paarweise disjunkt! • <mengenOperator> First(union) = {union}, First(intersect) = {intersect} sind disjunkt! • <multiplikator> First(<zahl> ∗) = {0,... ,9}, First(ε) = {ε} sind disjunkt! • <kostenfunktion> First(card(<variableRelation>)) = {card}, First(page(<variableRelation>)) = {page}, First(sel(<variableBedingung>, <variableRelation>)) = {sel}, First(cost(<baum>)) = {cost} sind paarweise disjunkt! • <baum> First(left) = {left}, First(right) = {right} sind paarweise disjunkt! • <existenzfunktion> First(sorted(<variableAttributmenge>,<variableRelation>)) = {sorted}, First(index supported(<variableAttributmenge>,<variableRelation>)) = {index supported}, First(merge supported(<variableAttributmenge>,<variableRelation>, <variableRelation>)) = {merge supported} sind paarweise disjunkt! • <vergleichsOperator> First(==) = {==}, First(! =) = {! =} 85 86 ANHANG B. REGELBEDINGUNGSPARSER sind disjunkt! • <teilmengenOperator> First(] =) = {] =}, First(]) = {]}, First([) = {[}, First([=) = {[=} sind paarweise disjunkt! • <groessenOperator> First(>=) = {>=}, First(>) = {>}, First(<) = {<}, First(<=) = {<=} sind paarweise disjunkt! • <buchstabe> First(a) = {a}, ... , First(z) = {z} sind paarweise disjunkt! • <grossBuchstabe> First(A) = {A}, ... , First(Z) = {Z} sind paarweise disjunkt! • <term> First(0<followTerm>) = {0}, ... , First(9<followTerm>) = {9}, First(a<followTerm>) = {a}, ... , First(Z<followTerm>) = {Z} sind paarweise disjunkt! • <followTerm> First(<term>) = {0, ... , 9, a, ... , Z}, First(ε) = {ε} sind disjunkt! • <zahl> First(0<follow>) = {0}, First(1<follow>) = {1}, First(2<follow>) = {2}, First(3<follow) = {3}, First(4<follow>) = {4}, First(5<follow>) = {5}, First(6<follow>) = {6}, First(7<follow>) = {7}, First(8<follow) = {8}, First(9<follow>) = {9} sind paarweise disjunkt! • <follow> First(<zahl>) = {0, ... , 9}, B.2. BEWEIS LL(1)-BEDINGUNG 87 First(ε) = {ε} sind disjunkt! 2. Bedingung • <verknüpfung> Follow(<verknüpfung>) = {$}, First(AND <funktion> <verknüpfungAND>) = {AND}, First(OR <funktion> <verknüpfungOR>) = {OR}, First(- <funktion> <verknüpfungMINUS>) = {-}, sind paarweise disjunkt! • <verknüpfungAND> Follow(<verknüpfungAND>) = {$}, First(AND <funktion> <verknüpfungAND>) = {AND} sind disjunkt! • <verknüpfungOR> Follow(<verknüpfungOR>) = {$}, First(OR <funktion> <verknüpfungOR>) = {OR} sind disjunkt! • <verknüpfungMINUS> Follow(<verknüpfungMINUS>) = {$}, First(- <funktion> <verknüpfungMINUS>) = {-} sind disjunkt! • <negation> Follow(<negation>) = {a,... , z, sorted, index supported, merge supported}, First(NOT) = {NOT} sind disjunkt! • <weitereOperatoren> Follow(<weitereOperatoren>) = {}}, First(, <operator>) = {,} sind disjunkt! • <berechnung> Follow(<berechnung>) = {==, ! =, =, ] =, ], [, [=, $}, First(<mengenOperator> <element>) = {union, intersect} sind disjunkt! • <multiplikator> Follow(<multiplikator>) = {card, page, sel, cost}, 88 ANHANG B. REGELBEDINGUNGSPARSER First(<zahl> ∗) = {0,... , 9} sind disjunkt! • <followTerm> Follow(<followTerm>) = {,, ), =, ==, !=, -, AND, OR, union, intersect, ]=, ], [, [=}, First(<term>) = {0, ... , 9, a, ... , Z} sind paarweise disjunkt! • <follow> Follow(<follow>) = {∗}, First(<zahl>) = {0, ... , 9} sind paarweise disjunkt! Abbildungsverzeichnis 1.1 1.2 1.3 1.4 Übersetzung einer Anfrage in einen Ausführungsplan Vereinfachter Aufbau eines Parsers . . . . . . . . . . Aufbau eines Einphasenoptimierungssystems . . . . Beispiel für eine Termersetzungsregel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 Darstellung einer Relation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.1 Termersetzungsregel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.1 4.2 4.3 4.4 Regelseiten und Ausführungsplan . Setzen der Variablen . . . . . . . . Verteilung der variablen Werte und Baumdurchlauf . . . . . . . . . . . 5.1 Attributwertanzahlen Selektion . . . . . . . . . . . . . . . . . . . . . . . . . . 42 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 Hauptfenster der Einphasenoptimierung . . . . . . . . . . . Laden einer Regel . . . . . . . . . . . . . . . . . . . . . . . . Erzeugen einer Regel . . . . . . . . . . . . . . . . . . . . . . Hilfedialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . Baumansicht . . . . . . . . . . . . . . . . . . . . . . . . . . Paketstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . Klassendiagramm dbs.unihannover.sopt.struc.general.tree Klassendiagramm arithmetische Terme . . . . . . . . . . . . Klassendiagramm Parametrisierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erzeugen eines neuen Baumes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 . 9 . 10 . 11 . . . . . . . . . . . . . 38 38 38 40 55 56 57 57 58 59 64 65 65 A.1 Beispiel für einen Ableitungsbaum . . . . . . . . . . . . . . . . . . . . . . . . 80 89 90 ABBILDUNGSVERZEICHNIS Tabellenverzeichnis 2.1 2.2 2.3 2.4 2.5 Bezeichnungen für Scanoperatoren . . Bezeichnungen für Mengenoperatoren Bezeichnungen für Verbundoperatoren Erzeugende Operatoren . . . . . . . . Vereinfachung von Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 14 15 17 22 4.1 4.2 4.3 4.4 4.5 4.6 4.7 Pipelining bei Projektionen und Selektionen . . . . . . Pipelining bei einer Aggregation . . . . . . . . . . . . Pipelining bei einem Join . . . . . . . . . . . . . . . . Pipelining bei Semijoins . . . . . . . . . . . . . . . . . Pipelining bei Outerjoins . . . . . . . . . . . . . . . . Äquivalente Darstellungsformen für Mengenoperatoren Pipelining bei Vereinigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 35 36 36 37 37 37 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17 5.18 5.19 5.20 Selektivität von Prädikaten . . . . . . . . . . . . . . . . Schätzungen für Attributwertanzahlen einer Selektion . Attributwertanzahlen Aggregation . . . . . . . . . . . . Attributwertanzahlen Outerjoins . . . . . . . . . . . . . Schätzungen für Attributwertanzahlen . . . . . . . . . . Darstellungen für Mengenoperatoren . . . . . . . . . . . Kardinalitäten und Pagegrößen . . . . . . . . . . . . . . Kardinalitäten und Pagegrößen von Outerjoins . . . . . Kardinalität und Pagegröße einer Aggregation . . . . . . Selektionskosten . . . . . . . . . . . . . . . . . . . . . . Projektionskosten . . . . . . . . . . . . . . . . . . . . . . Kosten einer Aggregation ohne Gruppierung . . . . . . . Kosten einer Aggregation mit Gruppierung . . . . . . . Kosten eines Joins . . . . . . . . . . . . . . . . . . . . . Kosten eines Anti-/Semijoins . . . . . . . . . . . . . . . Kosten einer Vereinigung . . . . . . . . . . . . . . . . . Kosten für die Erzeugung von Indexen und Sortierungen Geclusterte Indexe Teil 1 . . . . . . . . . . . . . . . . . Geclusterte Indexe Teil 2 . . . . . . . . . . . . . . . . . Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 42 43 43 44 44 45 46 46 47 47 48 48 49 50 51 51 51 52 52 91 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 TABELLENVERZEICHNIS Literaturverzeichnis [Mak03] M. E. Makoui: Heuristische Anfrageoptimierungen in Relationalen Datenbanken. Diplomarbeit, Institut für Informationssysteme, Universität Hannover, 2003 [War03] H. Warnecke: Erweiterung eines Simulators für relationale Anfrageoptimierungen. Bachelorarbeit, Institut für Informationssysteme, Universität Hannover, 2003 [Die03] M. Diehle: Erweiterung eines relationalen Anfragesimulators für eine regelbasierte Steuerung von physischen Optimierungsregeln. Bachelorarbeit, Institut für Informationssysteme, Universität Hannover, 2003 [Zle05] A. Zlenko: Implementierung von Termersetzungsregeln zur regelgesteuerten Anfragteoptimierung in relationalen Datenbanken. Studienarbeit, Institut für Informationssysteme, Universität Hannover, 2005 [Rud05] R. Rudnicki: Entwicklung eines bedingten Termersetzungssystems und Umsetzung von Optimierungsregeln für den Anfragesimulator RELOpt. Bachelorarbeit, Institut für Informationssysteme, Universität Hannover, 2005 [Mak05] M. E. Makoui: Anfrageoptimierung in objektrelationelen Datenbanken mit bedingten Termersetzungen. Grundlagen von Datenbanken 2005: 83-88 [Par04] R. Parchmann: Programmiersprachen und Übersetzer. Skript zur Vorlesung. Universität Hannover, 2004 [Lip04] U. Lipeck: Datenbanksysteme I. Skript zur Vorlesung. Universität Hannover, 2004 [Bart03] R. Barton: Verfahren der Technischen Informatik-Hilfeseiten zum Übungsbetrieb. Universität Hannover, 2003 93 94 LITERATURVERZEICHNIS