Informationssysteme Kernfach ETHZ D-INFK WS04/05 Prof. M. Norrie Michel Estermann 18. Februar 2005 Vorwort Diese Skript basiert auf der Vorlesung Information Systems Kernfach von Prof. M. Norrie an der ETH Zürich im Wintersemester 2004/05. Der Inhalt wurde aber mit Hilfe diverser Online Artikel und Bücher (siehe Literaturverzeichnis auf Seite 105) und im speziellen mit dem Buch “Fundamentals of DATABASE SYSTEMS” [22] ergänzt. Es handelt sich bei diesem Skript nicht um ein offizielles Skript der Vorlesung. Es ist zur Zeit weder Vollständig noch wird es frei von Fehlern sein. Für die Richtigkeit des Inhaltes wird keine Garantie übernommen. Michel Estermann, 18. Februar 2005 Inhaltsverzeichnis Vorwort I i XML Dokument Management 1 1 XML 3 1.1 Strukturierte, semi-strukturierte und unstrukturierte Daten . . . 3 1.2 XML Datenmodell . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 XML Dokumente, DTD, XML Schema . . . . . . . . . . . . . . . 6 1.3.1 wohl geformte und gültige XML Dokumente, XML DTD . 6 1.3.2 XML Schema . . . . . . . . . . . . . . . . . . . . . . . . . 6 2 Speichern von XML Dokumenten 2.1 XML und Datenbanken . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 9 9 XML DBMS Anforderungen . . . . . . . . . . . . . . . . . 10 2.2 XML DBMS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.3 Integral Storage Systems . . . . . . . . . . . . . . . . . . . . . . . 11 2.4 Dispersed Storage Systems . . . . . . . . . . . . . . . . . . . . . . 11 2.4.1 Custom Design . . . . . . . . . . . . . . . . . . . . . . . . 12 2.4.2 General Design . . . . . . . . . . . . . . . . . . . . . . . . 12 2.4.3 Element Reihenfolge . . . . . . . . . . . . . . . . . . . . . 13 2.4.4 ID/IDREF Mapping . . . . . . . . . . . . . . . . . . . . . . 16 2.4.5 Indexierung von XML . . . . . . . . . . . . . . . . . . . . 17 XML Support in RDBMS . . . . . . . . . . . . . . . . . . . . . . 17 2.5.1 SQL/XML . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.5.2 XML Support einiger kommerzieller Produkte . . . . . . . 20 Spezialisierte Systeme . . . . . . . . . . . . . . . . . . . . . . . . 21 2.5 2.6 iii iv INHALTSVERZEICHNIS 2.7 2.8 2.6.1 NeoCore XMS . . . . . . . . . . . . . . . . . . . . . . . . 21 2.6.2 Lore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Datenspeichermodelle für XML . . . . . . . . . . . . . . . . . . . 25 2.7.1 OEM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.7.2 Monet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Übersicht/Vergleich . . . . . . . . . . . . . . . . . . . . . . . . . 3 Querying von XML Dokumenten II 29 31 3.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.2 XQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.2.1 Daten Modell . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.2.2 XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.2.3 Node Test . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Database Application Programming 4 SQL in Programmierumgebungen 37 39 4.1 Datenbankapplikationen . . . . . . . . . . . . . . . . . . . . . . . 39 4.2 Impedance Mismatch . . . . . . . . . . . . . . . . . . . . . . . . . 40 4.3 Embedded SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4.3.1 Shared Variables . . . . . . . . . . . . . . . . . . . . . . . 40 4.3.2 Retrieving Data . . . . . . . . . . . . . . . . . . . . . . . 41 4.3.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . 42 Call Level Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.4.1 JDBC: SQL Funktionen für JAVA Programmierung . . . 43 Applikations Logik auf der Server Seite . . . . . . . . . . . . . . . 48 4.5.1 48 4.4 4.5 Stored Procedures . . . . . . . . . . . . . . . . . . . . . . 5 Datenbanken und Programmierung 53 5.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 5.2 Persistence in Programmiersprachen . . . . . . . . . . . . . . . . 54 5.2.1 Design Methoden . . . . . . . . . . . . . . . . . . . . . . . 55 5.2.2 Persistent Languages . . . . . . . . . . . . . . . . . . . . . 55 5.2.3 Persistence in Java . . . . . . . . . . . . . . . . . . . . . . 58 5.2.4 ObjectStore OODBMS . . . . . . . . . . . . . . . . . . . . 60 INHALTSVERZEICHNIS v 6 Objekt-Orientierte Datenbanken 6.1 6.2 III 63 ODMG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 6.1.1 ODMG Object Model . . . . . . . . . . . . . . . . . . . . 64 6.1.2 Object Definition Language (ODL) . . . . . . . . . . . . . 68 6.1.3 Object Query Language (OQL) . . . . . . . . . . . . . . . 68 Java Data Objects (JDO) . . . . . . . . . . . . . . . . . . . . . . 69 Architekturen und Technologien 71 7 Client/Server/Middleware Architekturen 73 7.1 Zentralisierte DBMS Architektur . . . . . . . . . . . . . . . . . . 73 7.2 Client/Server (File-Server) Architektur . . . . . . . . . . . . . . . 73 7.3 Two-Tier Client/Server Architektur . . . . . . . . . . . . . . . . 74 7.4 Three-Tier Client/Server Architektur . . . . . . . . . . . . . . . . 75 7.5 Transaction Processing Monitors . . . . . . . . . . . . . . . . . . 77 7.6 Peer-to Peer Systeme . . . . . . . . . . . . . . . . . . . . . . . . . 79 7.7 Verteilte Datenbanksysteme . . . . . . . . . . . . . . . . . . . . . 79 7.7.1 Zukünftige Trends . . . . . . . . . . . . . . . . . . . . . . 80 Distributed Query Processing . . . . . . . . . . . . . . . . . . . . 80 7.8.1 Query Processing . . . . . . . . . . . . . . . . . . . . . . . 81 7.8.2 Dynamische Programmierung für Query Optimierung . . 82 7.8.3 Kostenschätzung für Pläne . . . . . . . . . . . . . . . . . 83 7.8.4 Query Execution Techniken . . . . . . . . . . . . . . . . . 84 7.8.5 Ausnützen der Client Ressourcen . . . . . . . . . . . . . . 87 7.8.6 Query Optimierung . . . . . . . . . . . . . . . . . . . . . 88 7.8.7 Query Ausführung . . . . . . . . . . . . . . . . . . . . . . 89 7.8.8 Dynamische Datenplatzierung . . . . . . . . . . . . . . . . 90 7.8 8 Transaktionsmodelle und Systeme 8.1 8.2 Transaktionen und Concurrency 91 . . . . . . . . . . . . . . . . . . 91 8.1.1 Warum Concurrency Control? . . . . . . . . . . . . . . . . 91 8.1.2 Transaktions Eigenschaften . . . . . . . . . . . . . . . . . 95 8.1.3 Serialisierbarkeit . . . . . . . . . . . . . . . . . . . . . . . 95 Concurrency Control Techniken . . . . . . . . . . . . . . . . . . . 95 vi INHALTSVERZEICHNIS 8.3 8.2.1 Locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 8.2.2 Two Phase Locking (2PL) . . . . . . . . . . . . . . . . . . 96 8.2.3 Timestamp Ordering . . . . . . . . . . . . . . . . . . . . . 96 8.2.4 Multiversion Concurrency Control Techniken . . . . . . . 97 Locking Implementation . . . . . . . . . . . . . . . . . . . . . . . 98 8.3.1 Lock Manager . . . . . . . . . . . . . . . . . . . . . . . . 98 8.3.2 Lock Granularität . . . . . . . . . . . . . . . . . . . . . . 99 8.4 Multigranularity Locking 8.5 Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 8.5.1 8.6 Deadlock Detection . . . . . . . . . . . . . . . . . . . . . . 99 . . . . . . . . . . . . . . . . . . . . . 102 Performance Issues und Bottlenecks . . . . . . . . . . . . . . . . 102 8.6.1 Lock Trashing . . . . . . . . . . . . . . . . . . . . . . . . 102 8.6.2 Hot Spots . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 8.6.3 Query Update Problem . . . . . . . . . . . . . . . . . . . 103 Teil I XML Dokument Management 1 Kapitel 1 XML XML (Extensible Markup Language) ist ein standardisiertes Textformat für die Repräsentation von (semi)-strukturierten ((semi)-structured ) Informationen. Eine Markup Sprache ist ein Mechanismus zur Beschreibung von Strukturen in einem Dokument. Die XML Spezifikation definiert einen Standard zum Einfügen von Markups in ein Dokumentes. XML ist deshalb eine so genannte meta-markup language, d.h. es gibt im Gegensatz z.B. zu HTML nicht ein vordefiniertes Set von Tags, sondern die Tags können selber definiert werden und so Informationen zur Struktur und Bedeutung der Daten liefern. XML beschreibt also nicht die Formatierung von Daten sondern die Struktur und Bedeutung dessen. Im Zeitalter des Internet wird XML als Format für den Datenaustausch zwischen verschiedenen Systemen über das Web immer wichtiger, ausserdem gibt es viele Tools für die Arbeit mit XML und bereits sind viele Daten in XML veröffentlicht. Der XML Standard wird durch das World-Wide Web Konsortium (W3C) verabschiedet. Die Spezifikation von XML und andere Informationen finden sich unter www.w3.org/XML/. 1.1 Strukturierte, semi-strukturierte und unstrukturierte Daten Informationen die in einer Datenbank gespeichert werden sind strukturierte Daten (structured data) weil sie durch ein striktes Format repräsentiert werden. Alle Einträge (records) in einer Relationalen Datenbank Tabelle haben die selben Attribute, evtl. mit einem NULL Eintrag. Die Attribut-Kolonne muss aber vorhanden sein. Es gibt aber viele Daten die einer gewissen Struktur folgen, aber nicht alle gesammelten Informationen haben die selbe identische Struktur. Viele Attribute sind vielleicht bei allen Daten gleich, einige kommen aber nur in gewissen vor. Zudem können zu einem späteren Zeitpunkt neue Attribute hinzukommen. Solche Daten nennt man semi-strukturierte Daten. Oft werden solche Daten als Bäume oder Graphen dargestellt. 3 4 KAPITEL 1. XML <?xml version="1.0" standalone="yes"?> <newsfeed> <news> <title>Dell does it again</title> <href>http://www.zdii.com/</href> <abstract language="english"> 2:19 PM PT Direct PC Maker continues an a roll, earning $305 million in the quarter </abstract> <source>zdnet</source> </news> <news> <title> Judges sets date for Microdoft Justice department </title> <href>http://www.zdnet.com/</hfref> <source>zdnet</source> </news> </newsfeed> Abbildung 1.1: XML Beispiel Ein wichtiger Unterschied zwischen strukturierten und semi-strukturierten Daten ist, wie das Schemakonstrukt (Attributnamen, Abhängigkeiten, Datentypen) gehandhabt wird. In semi-strukturierten Daten ist die Schemainformation mit den Daten vermischt, weil jedes Datenobjekt verschiedene Attribute haben kann, die im voraus vielleicht noch nicht bekannt sind. Dieser Typ von Daten nennt man selbst beschreibende Daten (self-describing data). Zusätzlich zu den strukturierten und semi-strukturierten Daten gibt es die unstrukturierten Daten, mit nur wenigen oder keinen Informationen über die Art der Daten. Typische Beispiele sind Textdokumente oder HTML Seiten, welche zwar viele Informationen in Form von Text enthalten können, die aber kaum eine Struktur aufweisen. Einzig die Aufteilung in Kapitel, Unterkapitel usw. kann als eine gewisse Struktur aufgefasst werden, daraus sind aber kaum Informationen zu entnehmen. 1.2 XML Datenmodell Das Basisobjekt im XML Dokument ist xml. Die Hauptkonzepte zur Strukturierung sind Elemente (elements) und Attribute (attributes). Der Begriff Attribute in XML wird nicht gleich benutzt wie in der Datenbank Terminologie. Attribute in XML sind zusätzliche Informationen für Elemente. Abbildung 1.1 zeigt ein Beispiel eines XML Dokuments mit einem Komplexen Element (complex element) <newsfead> welches wiederum das complex element <news> enthält. Das Element <abstract> hat ein Attribute language. 1.2. XML DATENMODELL 5 newsfeed news title "Dell..." href "http://..." news abstract language source "2:19..." "english" "zdnet" title "Judge..." href "http://..." source "zdnet" Abbildung 1.2: “tree data model” des XML Beispiels Man kann dieses Repräsentation der Daten als XML Dokument leicht in eine Repräsentation als Baummodell (tree model ) umwandeln (Abbildung 1.2). Das XML Datenmodell nennt man deshalb auch Baummodell (tree model ) oder Hierarchisches Modell (hierarchical model ). Mathematisch kann man das XML Datenmodell folgendermassen definieren [43]: Definition 1. Ein XML Dokument ist ein gewurzelter Baum d=(V, E, r, labelE , labelA , rank) mit Knoten V und Kanten E ⊆ V × V und einem “ausgezeichneten” Knoten r ∈ V , dem root node. Die Funktion labelE : V → string ordnet Labels an Knoten, d.h. Elementen zu; labelA : V → string → string ordnet Paare von Strings, Attribute und ihre Werte, Knoten zu. Character Data (CDATA) sind als spezielle ‘string’ Attribute von cdata Knoten modelliert, rank : V → int etablierte eine Rangierung, um eine Ordnung unter Knoten mit dem selben Parent Knoten zu ermöglichen. Für Elemente ohne irgendein Attribute ist labelA gleich der leeren Menge. Im Allgemeinen ist es möglich drei Haupttypen von XML zu charakterisieren: • Daten-zentrierte XML Dokumente(data-centric): Diese Dokumente haben viele kleine Datenelemente welche einer spezifischen Struktur folgen, also die z.B. einer strukturierten Datenbank entnommen wurden. Sie sind als XML Dokument umgewandelt worden, um sie über das Web zu verbreiten oder darzustellen. • Dokument-zentrierte XML Dokumente (document-centric): Diese sind Dokumente mit viel Text, z.B. News Artikel oder Bücher. In diesen Dokumenten gibt es nur wenige oder keine strukturierten Datenelemente. • Hybride XML Dokumente (hybrid): Diese Dokumente enthalten Teile mit strukturierten Daten und andere Teile die vorwiegend unstrukturiert sind. 6 KAPITEL 1. XML <!-- Example DTD of News Information --> <!ELEMENT newsfeed (news)*> <!ELEMENT news (title, href, abstract?, source?> <!ELMENT title (#PCDATA)> <!ELEMENT href (#PCDATA)> <!ELEMENT abstract (#PCDATA)> <!ATTLIST abstract language CDATA #IMPLIED> <!ELEMENT source (#PCDATA)> Abbildung 1.3: DTD zum Beispiel Abbildung 1.1 1.3 1.3.1 XML Dokumente, DTD, XML Schema wohl geformte und gültige XML Dokumente, XML DTD Ein XML Dokument wie das Beispiel aus Abbildung 1.1 ist wohl geformt (wellformed ), wenn es mit der XML Deklaration (XML Version und andere relevanten Attribute) beginnt und syntaktisch den Richtlinien des Baummodelles folgt. D.h. es hat ein einzelnes root element und jedes Element ist durch Start- und End-Tags innerhalb der Start- und End-Tags seines parent element definiert. Ein wohl geformtes XML Dokument ist syntaktisch korrekt und kann von einem generischen Prozessor traversiert und als Baum repräsentiert werden. Ein Standard Set von API (application programming interface) Funktionen (DOM genannt) erlaubt Programmen diesen Baum zu manipulieren. Für die Benutzung von DOM muss das XML Dokument also zuerst geparst werden. Ein anders API, SAX, bietet eine andere Möglichkeit, es erlaubt das Verarbeiten eines XML Dokuments on the fly indem es dem Programm meldet wenn ein Start-Tag oder End-Tag gelesen wird. Ein wohl geformtes Dokument kann beliebige Tag-Namen für die Elemente haben. Dies ermöglicht dem Verfasser eine grösstmögliche Freiheit, reduziert aber die Möglichkeit zur automatischen Interpretation der Elemente. Ein stärkeres Kriterium ist, wenn ein XML Dokumente gültig (valid ) ist. In diesem Fall muss das Dokument wohl geformt sein und einer bestimmten Struktur folgen, die in einem XML DTD (Document Type Definition) oder XML Schema File definiert ist. Abbildung 1.3 zeigt die DTD zum Beispiel aus Abbildung 1.1. Um mehr Über DTD zu lernen gibt es ein Online Tutorial[52]. 1.3.2 XML Schema Die XML Schema Sprache ist ein Standard zur Spezifizierung der Struktur eines XML Dokuments. Im Gegensatz zu DTD benutzt sie aber den selben Syntax wie ein normales XML Dokument, ist also selbst ein XML Dokument und kann von den selben Prozessoren verarbeitet werden. Abbildung 1.4 zeigt das XML Schema zum Beispiel aus Abbildung 1.1. Mehr zu XML Schema findet man auf der Seite des w3c (www.w3.org/XML/Schema). 1.3. XML DOKUMENTE, DTD, XML SCHEMA <?xml version="1.0" encoding="UTF-8" ?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="newsfeed"> <xsd:complexType> <xsd:sequence> <xsd:element name="news" type="News" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="News"> <xsd:sequence> <xsd:element name="title" type="xsd:string"/> <xsd:element name="href" type="xsd:string"/> <xsd:element name="abstract" minOccurs="0" maxOccurs="1"> <xsd:complexType> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xs:attribute name="language" type="xsd:string" use="optional"/> </xsd:extension> </xsd:simpleContent> </xsd/complexType> </xsd:element> <xsd:element name="source" type="xsd:string" minOccurs="0" maxOccurs="1"/> </xsd:sequence> </xsd:complexType> </xsd:schema> Abbildung 1.4: XML Schema zum Beispiel Abbildung 1.1 7 8 KAPITEL 1. XML Kapitel 2 Speichern von XML Dokumenten 2.1 XML und Datenbanken Die Benutzung von XML Dokumente zur Verwaltung von Daten hat verschiedene Nachteile: • Hierarchische Ansicht der Daten • Nicht effizient im Sinne von Speicherplatz und Verarbeitung • beschränkte Kontrolle der Vollständigkeit der Daten Trotzdem ist es sinnvoll Informationen als XML Dokumente zu speichern, wenn die Informationsquellen XML Dokumente sind. Als XML für den Datenaustausch immer populärer wurde, kam deshalb auch schnell das Bedürfnis auf, die Inhalte der erhaltenen XML Dokumente sinnvoll in DBMS abzulegen. Zudem bietet sich das Speichern an, da die XML Notation Hersteller- und Platformunabhängig ist und komplexe Daten mit XML flexibler als mit Relationalen Datenbanken repräsentiert werden k”önnen. Und wenn man Daten schon als XML erhält oder ver”öffentlicht, warum soll man sie dann nicht auch als XML speichern und verwalten. Für verschiedene Anwendungen macht das Speichern und Verwalten von Informationen als XML Dokument Sinn: • Dokument Management Applikationen: speichern und Verwalten von Geschäfts Dokumenten, Publikationen usw. • Archiv Applikationen: Langzeit-Speicherung von Informationen. • Applikationen mit komplexen Daten: z.B. Geografische, Biochemische, Medizinische Applikationen. . . • Publizier Applikationen: Content Management System 9 10 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN 2.1.1 XML DBMS Anforderungen • XML Datenmodell – Das XML Datenmodell ist komplexer und weniger streng als das relationale Modell, zudem k”önnen die einzelnen Elemente Metadaten enthalten. – Eine neue Query-Sprache und andere Mechanismen zur Ausführung von Suchanfragen (Indexing, Optimierung) sind gefordert. – Das XML Datenmodell bezieht sich auf individuelle Dokumente und nicht auf Dokumentensammlungen. Externe Abhängigkeiten müssen gehandhabt werden k”önnen. • Update Policen – Verschiedene Update Policen für XML Dokumente, wie read-only, append-only und revision check-out, müssen unterstützt werden. • Schema Evolution – Dokumentenschemas entwickeln sich typischerweise mit der Zeit und oft m”öchte man mit verschiedene Schemaversionen und deren Dokumente arbeiten k”önnen. – Man braucht Suchanfragen, die über mehrere Versionen funktionieren. 2.2 XML DBMS XML Datenbanken k”önnen in zwei Gruppen unterteilt werden: 1. Native XML DBMS : DBMS die spezielle für die Speicherung von XML Dokumenten entwickelt wurden: z.B. NeoCore XMS, Excelon, InfonyteDB, Tamino 2. XML-enabled RDBMS : herk”ömmliche DBMS werden von der Herstellern erweitert um XML zu unterstützen: z.B. IBM DB2, Oracle9i, MS SQL Server 2000 Eine andere Unterteilung der DBMS kann durch die Art der Speicherung von XML Dokumenten vorgenommen werden: 1. Integral Storage Systems: Ein relationales oder objektorientiertes DBMS (database managment system) kann benutzt werden um das XML Dokument als Ganzes als BLOB/CLOB (binary large object/character large object) oder Referenz zu einem File innerhalb eines DBMS Record/Objekt zu speichern. Dieses Methode kann benutzt werden, wenn das DBMS spezielle Module zur Verarbeitung von Textdokumenten besitzt oder für dokumentzentrierte (siehe 1.2 auf Seite 5) und schemalose XML Dokumente. 2.3. INTEGRAL STORAGE SYSTEMS 11 2. Dispersed Storage Systems: Ein DBMS wird benutzt um den Dokumentinhalt als Datenelemente zu speichern. Ein XML Dokument wird geparst und der Inhalt in seperaten Records/Objects abgelegt. Diese Methode funktioniert zur Speicherung von verschieden Dokumenten die einem spezifischen Schema (XML DTD, XML Schema) folgen. Weil alle Dokumente die selbe Struktur aufweisen, kann eine relationale (oder objektorientierte) Datenbank zur Speicherung der Blattelemente (leaf-level data elements) innerhalb des XML Dokuments kreiert werden. Diese Methode erfordert Mapping Algorithmen um ein Datenbank Schema zu definieren, welches mit der Struktur eines XML Dokuments, wie sie im XML DTD oder XML Schema spezifiziert ist, kompatibel ist und die Wiederherstellung des XML Dokuments aus den gespeicherten Daten erlaubt. 3. Spezialisierte Systeme: Eine neuer Typ von Datenbank System auf der Basis des Hierarchischen Modells (hierachical (tree) model ) wird entwickelt und implementiert. Das System enthält spezialisierte Indexing und Querying Techniken und funktioniert für alle Arten von XML Dokumenten. 2.3 Integral Storage Systems Wenn die XML Dokumente wie normale Textdokumente, z.B als BLOB oder CLOB, in einer Datenbank gespeichert sind, werden typischerweise zusätzlich noch Textindizes oder Metadaten in der Datenbank abgelegt. Dazu sind besondere Tools wie Volltextsuche n”ötig. Für das Querying solcher Dokumente kann XQuery (siehe 3.2 auf Seite 31) verwendet werden. Kann auf Dokumente direkt zugegriffen werden, dann taucht das Problem der Integrität der Indexierung und der Konsistenz der Dokumentdateien auf. Dies kann verhindert werden, indem geschätzte Bereiche zu Sicherung verwendet und Zugriffsrechte verwaltet werden (z.B. nur noch Leserechte für weitere Zugriffe nach einer Anforderung eines Dokuments). Für die Speicherung und Verwaltung von Dokumenten allgemein und XML Dokumenten im speziellen gibt es verschiedene Document-/Content Management Systeme auf dem Markt (z.B. Content@XML). Oft wird in solchen Systemen eine Kombination mit einer daten-zentrierte L”ösung implementiert (siehe unten), d.h die Dokumente k”önnen in Elemente aufgeteilt werden, und diese Elemente werden abgespeichert. Ebenfalls unterstützen einige moderne DBMS wie Oracle9i das direkte Speichern von XML Elementen (siehe Abbildung 2.4). 2.4 Dispersed Storage Systems Wir wollen nun XML Dokumente daten-zentrierte (siehe 1.2 auf Seite 5) in einer relationalen Datenbank abspeichern. D.h. Daten werden als XML importiert und/oder als XML exportiert, evtl. wollen wir strukturierte Daten und Textdaten mischen. Wir müssen dazu die XML Daten Parsen, Speichern und XML Daten aus Datenbank Objekten generieren k”önnen. Zudem brauchen wir Mechanismen für 12 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN die Indexierung und das Suchen. Um XML Daten in einem relationalen DBMS abspeichern zu k”önnen muss das XML Dokument auf Relationen gemapt werden. Dabei müssen die Relationen die XML Dokument Typen Definitionen (DTDs oder XML Schemas) und das XML Dokument in Bezug seiner hierarchischen Struktur, der Reihenfolge der Elemente sowie dem Inhalt repräsentiert werden k”önnen. Es gibt dabei unverträglichen Unterschiede der beiden Datenmodelle zu überwinden (siehe Tabelle 2.1). XML Relational - Daten in einzelner hierarchischer Struktur - Nodes haben Element- und/oder Attributwerte - Elemente k”önnen andere Elemente enthalten - Elemente sind geordnet ↔ - Daten in mehreren Tabellen ↔ - Zellen haben einzelne atomic values ↔ - Elemente k”önnen rekursiv sein ↔ - Query mit XML Standard ↔ - Zeilen und Kolonnen sind ungeordnet - wenig Unterstützung für rekursive Elemente - SQL basiertes Querying Tabelle 2.1: Datenmodel Mismatch 2.4.1 Custom Design Ist die Struktur eines XML Dokuments bekannt, weil die DTD oder das XML Schema vorhanden ist, kann eine spezielle für diese Struktur angepasste L”ösung konstruiert werden, indem man die DTD oder das XML Schema Dokument parst und für jedes Elemente eine Tabelle kreiert. Die Tabelle hat dabei Kolonnen mit den Attributen und dem Wert des Elements und eine Referenz auf das parentElement (beim root-Element eine Referenz auf das Dokument). Bei vielen Dokumenten mit unterschiedlichen Strukturen entstehen dabei aber viele Tabellen mit wenigen Einträgen, was zu Ineffizienz beim Suchen und Speicherbedarf führen kann. Ausserdem ist die Position des Elementes innerhalb des Dokuments nicht ersichtlich, da aber die Reihenfolge der Elemente typischerweise eine Rolle spielt muss man um dieses Problem zu beheben eine Positionsfeld einfügen. 2.4.2 General Design Eine bessere M”öglichkeit ist die DTD (oder das XML Schema) und die korrespondierenden Elemente in je einer Tabelle zu repräsentieren (siehe die Beispiele aus den Abbildungen 2.1, 2.2). Verschiedene DTDs und deren Elemente werden 2.4. DISPERSED STORAGE SYSTEMS 13 durch eindeutige IDs bestimmt. Die Elemente haben eine eindeutige ID, welche auch ihre Position im Dokument bestimmt und referenzieren zum entsprechenden DTD Element (ID aus der DTD Tabelle). 2.4.3 Element Reihenfolge Wir haben oben gesehen, dass man zur Bestimmung der Reihenfolge der Elements im Dokument eine Kolonne mit den Positionen einführen kann, oder einen Identifier der die Position bestimmt benutzt. Es gibt aber noch andere Methoden. Es folgte eine Zusammenfassung von M”öglichkeiten: • Identifier Wir definieren die Position im Dokument mit Identifiers (IDs), wie im Beispiel oben (Abbildung 2.1, 2.2). Wird ein Node gel”öscht, gibts keine Probleme. Wird ein neues Element eingefügt, k”önnen die IDs des Vorgängers und des Nachfolgers bestimmt werden. Die neue ID wird als die Mitte zwischen den beiden neu berechnet. Wenn also z.B. in unserem Beispiel eine neue Telephonnummer phone zwischen die beiden bestehenden eingefügt werden soll, wird diese ID als N.4.1,5 (zwischen N.4.1 und N.4.2 ) bestimmt. • Relative Position Eine spezielle Tabelle parentchild mit den Feldern parent_node, child_node und position und mit einem PRIMARY KEY aus allen Drei wird benutzt. Das Feld position gibt dabei die Position des child_node innerhalb des parent_node an. Wenn ein Element gel”öscht wird, ist nichts zu machen. Wird ein Element eingefügt, müssen nur die Positionen der child_nodes mit dem selben parent_node auf den neuen Stand gebracht werden. • Absolute Position Ein Feld position bestimmt eindeutig die Position im Dokument. Man braucht keine spezielle Tabelle, sondern das Feld kann in der Node-Tabelle (node) eingefügt werden. Die Eindeutigkeit kann mit einem UNIQUE constraint ON node (position) gewährleistet werden. L”öschen eines Element ist wieder kein Problem. Beim Einfügen eines neuen Elements müssen aber alle Position angepasst werden, was bei vielen Daten mühsam und Zeitaufwendig sein kann. • Links Wir benutzen in der Node-Tabelle die Felder next_sibling und first_child und zusätzlich vielleicht noch ein previous_sibling, wobei damit Referenzen auf das nächste (vorherige) Kind-Element und eine Referenz auf das erste Kind-Element im aktuellen Element gemeint sind. Da es sich hierbei um eine linked list handelt, k”önnen bekannte Algorithmen zum L”öschen und Einfügen für diese Datenstruktur benutzt werden. Bei der doppelt verlinkten Liste (mit next und previous) sind diese Operationen bekanntlich einfacher zu handhaben. 14 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN Beispiel DTD <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT business_card name surname given other title address street city postcode contact phone (name, title, address, contact)> (surname, given, other?)> #PCDATA> #PCDATA> #PCDATA> #PCDATA> (street, city, postcode)> #PCDATA> #PCDATA> #PCDATA> (phone*)> #PCDATA> Dokument Hierarchie business_card N.1 N.2 name title N.1.1 N.1.3 surname other N N.3 N.4 address contact N.3.1 N.3.3 street postcode N.4.1 phone N.4.k phone N.3.2 N.1.2 city given DTD Representation NodeID N N.1 N.2 N.3 N.4 N.1.1 N.1.2 N.1.3 N.3.1 N.3.2 N.3.3 N.4.1 ElementType business card name title address contact surname given other street city postcode phone Datatype RootNode Structural Element #PCDATA Structural Element Structural Element #PCDATA #PCDATA #PCDATA #PCDATA #PCDATA #PCDATA #PCDATA Cardinality 1 1 1 1 1 1 1 0..1 1 1 1 1..k wobei N eine Integer ID für die DTD ist Abbildung 2.1: Beispiel einer relationalen Representation eines XML Dokuments 2.4. DISPERSED STORAGE SYSTEMS 15 Beispiel Instanz <business_card> <name> <surname>Smith</surname> <given>John</given> <other>K</other> </name> <title>Professor</title> <address> <street>53 Hight Street</street> <city>London</city> <postcode>SW11 3XY</postcode> </address> <contact> <phone>0181 546 3421</phone> <phone>079 452 345</phone> </contact> </business_card> Element Representation Doc Node M.K.1.1 M.K.1.2 M.K.1.3 M.K.2 M.K.3.1 M.K.3.2 M.K.3.3 M.K.4.1 M.K.4.2 M.(K+1).1.1 ... DTD Node N.1.1 N.1.2 N.1.3 N.2 N.3.1 N.3.2 N.3.3 N.4.1 N.4.2 N.1.1 ... Value Smith John K Professor 53 High Street London SW11 3XY 0181 546 3421 079 452 345 Jones ... wobei N wie vorher eine Integer ID für die DTD ist Für M nehmen wir eine ID für das Dokument und K für das business card Element innerhalb des Dokuments Abbildung 2.2: Fortsetzung Beispiel 16 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN node uid node_name node_data document uid root parent_node attribute uid attr_name attr_value attr_type parent_node ID_IDREF id idref doc_id Abbildung 2.3: Modellieren des ID/IDREF Konzepts 2.4.4 ID/IDREF Mapping In DTDs k”önnen Referenzen mit ID und IDREF definiert werden. Wie kann dieses Konzept mit einem relationale Modell modelliert werden? Eine einfache L”ösung ist, die ID/IDREFs als normale Attribute zu mappen. Das hat aber den Nachteil, dass Eigenschaften wie uniqueness der ID nicht automatisch vom relationalen Modell erzwungen werden. Eine besser M”öglichkeit besteht darin, die ID/IDREFs als primary/foreign keys zu mappen. Zu Beachten ist dabei, dass Probleme auftreten k”önnen, wenn man mehrere Dokumente abspeichern m”öchte, da eine ID nur innerhalb eines Dokumentes eindeutig sein muss. Zusätzlich k”önnen mit dieser Methode keine IDREFS (beachte die Mehrzahl) modelliert werden. Ein M”öglichkeit das Konzept der ID/IDREF(S) zu modellieren besteht darin eine Tabelle ID_IDREF (siehe Abbildung 2.3) zu verwenden. Die id Kolonne enthält Referenzen auf Attribute des Typen ID und idref enthält Referenzen auf Attribute mit dem Typ IDREF. Eine dritte Kolonne doc_id wird verwendet um mehrere Dokumente handhaben zu k”önnen. Für das Einfügen eines neuen Tupels in die ID_IDREF-Tabelle und um die Eindeutigkeit der Attribute vom Typ ID eines Dokument zu gewährleisten, müssen noch Trigger implementiert werden. Wenn ein neues Tupel in die ID_IDREFTabellen eingefügt wird, muss man sich versichern, dass id auf ein Attribut mit dem Typ ID und IDREF auf eines mit Typ IDREF verweist. Als Beispiel haben wir folgende DTD: <!ELEMENT drawing EMPTY> <!ATTLIST drawing drawingID ID #REQUIRED 2.5. XML SUPPORT IN RDBMS 17 desc CDATA #IMPLIED> <!ELEMENT shape (line|text)*> <!ATTLIST shape drawingIDREF IDREF #REQUIRED> ... Mit dem Beispiel XML Dokument: <drawing drawingID="drawing1"/> <shape drawingIDREF="drawing1"> ... </shape> Würde die Tabelle attribute dann folgendes Tupel enthalten: 0 1 drawingID drawingIDREF ”drawing1” ”drawing1” ID IDREF 5 6 Und die Tabelle ID_IDREF hätte dann das Tupel: 0 1 2.4.5 0 Indexierung von XML • Value Index – Index über atomic values: Elemente Inhalt oder Attribut-Values – Übliche DBMS Index Methoden wie B-Tree oder Hash Index • Full Text Index – Index über individuelle W”örter aus Tags oder Inhalt – Methoden aus dem Information Retrieval (Kapitel ??) werden benutzt, z.B. inverted index • Path Index – Index über die Dokument Struktur, d.h node values 2.5 XML Support in RDBMS Wie bereits unter 2.3 erwähnt, unterstützen moderne DBMS XML in verschiedener Hinsicht. Oracle9i unterstützt beispielsweise ein SQL Typ XMLType um XML Elementen direkt abzuspeichern (Abbildung 2.4). Das Oracle XML SQL Utility erlaubt das “zerreissen” (shredding von XML Dokumente, für das daten-zentrierte Abspeichern (dispersed storage: 2.4). Element Typen-Namen werden dabei auf Kolonnen-Namen in Tabellen gemappt. Elemente die nur Daten als Inhalt enthalten, werden auf skalare Kolonnen und Elemente mit Sub-Elemente auf Objekttypen gemappt. Eine Liste von Elemente werden auf Kollektionen (collections) gemappt. (Beispiel siehe Abbildung 2.5) 18 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN CREATE TABLE Claim( ClaimID NUMBER(3), Submitted DATE, DamageReport SYS.XMLType, Policy SYS.XMLType, Awards SYS.XMLType ); INSERT INTO Claim VALUES( 345123, sysdate(), SYS.XMLType.createXML(’ <DamageReport> Bathroom ceiling collapsed due to bath overflow in flat above. </DamageReport> ’) SYS.XMLType.createXML(’ ........ ’) ....... ); Abbildung 2.4: Speichern von XML Elementen in Oracle9i <ROWSET> <ROW num="1"> <ClaimID>143453</ClaimID> <Submitted>2002-10-05</Submitted> <CustomerID>6524</CustomerID> <Amount>350</Amount> </ROW> </ROWSET> ⇓ Oracle XML SQL Utility ⇓ INSERT INTO Claim (ClaimID, Submitted, CustomerID, Amount) VALUES(’143453’,’2002-10-05’,’6524’,’350’) Abbildung 2.5: Beispiel: Oracle XML SQL Utility 2.5. XML SUPPORT IN RDBMS 19 Oracle’s XSQL Oracle hat eine einfaches Vokabular um SQL Statements in XML Dokumenten zu repräsentieren. Beispiel 2.1: <?xml-stylesheet type="text/xsl" href="claim.xsl"?> <query connection="XMLdemo"> SELECT VALUE(c) as CLAIM FROM insurance_claim_view c WHERE c.ClaimPolicy,PrimaryInsured.LastName=‘Smith’ </query> ⇒ Das XML Dokument wird geparst und die Query wird durch das DBMS bearbeitet. Als Resultat wird das Claim Dokument (XML) für Smith zurükgegeben. Oracle’s XML Generator Funktionen Oracle9i bietet drei eingebaute Funktionen um XML zu generieren: • dbms_xmlgen – Generiert XML Dokumente aus den Resultaten von Queries • sys_xmlgen – Generiert XML Dokumente aus Values, Objekttypen und XMLType Instanzen • sys_xmlagg – Generiert XML Dokumente durch Zusammenfügen mehrere XML Dokumente 2.5.1 SQL/XML SQL/XML ist ein ANSI und ISO Standard welcher Unterstützung für die Benutzung von XML im Zusammenhang mit einem SQL Datenbank Systems anbietet.1 SQL/XML erm”öglicht XML Dokumente in einer SQL Datenbank zu speichern, diese Datenbank zu durchsuchen (durch Benutzung von XPath und XQuery) und existierende SQL Daten in Form von XML Dokumenten zu ver”öffentlichen. Mehrere Firmen haben sich zu einer offen Gruppe (SQLX Group) zusammengeschlossen um Vorschlage für diesen neuen Standard zu entwickeln. (siehe [20][21]) 1 Die 1. Ausgabe des SQL/XML Standards wurde 2003 von der International Organization for Standardization (ISO) als Teil 14 des SQL Standards ISO/IEC 9075-14:2003 ver”öffentlicht 20 2.5.2 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN XML Support einiger kommerzieller Produkte Es folgte eine tabellarische Zusammenfassung des XML Supports einiger beliebter kommerzieller Systeme. Oracle9i Produkte Info Oracle9i: www.oracle.com/technology/products/oracle9i/index.html XML Daten Typ Ja Speicherung Text oder Strukturiert Value Index Ja Fulltext Index Ja Path Index Ja Querying SQL Methoden mit XPath 1.0 Stärken Gute Unterstützung von Standards(XPath, SQL/XML); XML Views; Import/Export von XML Schemas; XML Processing (DOM,SAX); XSLT Processing; Schwächen kein einheitliches Vorgehen IBM DB2 Produkte Info IBM DB2: www-306.ibm.com/software/data/db2/ XML Daten Typ Ja Speicherung Text oder Benutzerdefinierte Struktur Value Index Ja Fulltext Index Mit TextExtender ja Path Index Mit TextExtender ja Querying SQL Methoden mit XPath Dialekt Stärken Schwächen schlechte Unterstützung der Standards; Schlechte Integration von Tools mit SQL; Nur teilweiser Support für SQL/XML[24]; Microsoft SQL Server 2000 Produkte Info MS SQL Server: www.microsoft.com/sql/default.mspx XML Daten Typ – Speicherung Textbasiert; Modellbasiert mit OPENXML; Benutzerdefinierte Struktur mit OPENXML STORED Queries Value Index Ja Fulltext Index Keine XML Spezifische Funktionen Path Index – Querying SQL Extension und XPath Dialekt Stärken Schwächen Keine XML Datentypen; kein Support für Updates durch XML Prozessoren; Kein Support für SQL/XML; 2.6. SPEZIALISIERTE SYSTEME 2.6 2.6.1 21 Spezialisierte Systeme NeoCore XMS Wie im White Paper von NeoCore [39] bzw. Xpriori [53] beschrieben, ist NeoCore XML Management System (XMS) ein XML information management system, im Gegensatz zu einem database management system (DBMS), weil es sowohl Metadaten als auch Daten (also Struktur und Inhalt) verwaltet. NeoCore XMS ist fähig die Struktur der Daten aus einem XML Dokument herzuleiten, wenn sie gesendet oder modifiziert werden. Das Produkt ist komplett Schemaunabhängig und Entwickler sind somit vom Datenbankdesign, der Tabellenerstellung und der Indexdefinition befreit. NeoCore XMS nimmt eine daten-zentrierte Sicht des XML Dokuments an. In NeoCore XMS werden Index Einträge in so genannte Icons konvertiert (eigenschafterhaltende Symbole fester Länge, welche den Text des Index Eintrag repräsentieren), wodurch es m”öglich ist viele Index Einträge zu speichern, da Icons weniger Platz brauchen und schneller zu verarbeiten sind. Dadurch wird es in NeoCore XMS m”öglich alles zu indexieren. Eine XML Repräsentation eines Elements wie z.B. folgendes: <Name> <Last>Brandin</Last> <First>Chris</First> </Name> Erzeugt folgende Index Einträge: • <Name> • <Name><Last> • <Name><First> • <Name><Last>Brandin • <Name><First>Chris • Brandin • Chris NeoCore XMS verwendet XPath Expressions für Queries. 2.6.2 Lore Lore (Lightweight Object Repository2 [37] ist ein speziell für das verwalten von semi-strukturierten Daten entwickeltes DBMS. Lore’s Daten Modell ist das OEM (siehe 2.7.1). Lore’s Query Language Lorel (siehe 2.6.2) ist eine Erweiterung von OQL (siehe 6.1.3). Lorel Query Sprache Lorel ist eine Erweiterung von OQL, die volle Spezifikation findet man in [1]. Der Grundbaustein von Lorel ist die simple path expression, welche aus einem Namen gefolgt von einer Sequenz von Labels gebildet wird. Eine simple path expression ist z.B. auctions.item.name. Die Semantik beinhaltet die Menge der Objekte die erreicht werden k”önnen, wenn man von auctions startet und einer Kante mit Label item und dann einer Kante mit Label name folgt. Pfadausdr cke k”önnen direkt in einem SQL Stil benutzt werden. Die folgende Query würde für unser Beispiel OEM Modell aus Abbildung 2.9, das Objekt &2 als Resultat zurückgeben: Beispiel 2.2: 2 Das Lore System ist auf zwei Arten leichtgewichtig: das von Lore unterstütze Objekt Modell ist leichtgewichtig und das System selber ist leichtgewichtig, weil es keine multiuser updates oder “schwergewichtige” DBMS Features bietet 22 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN QUERY select auctions.open_auctions.auction.object where auctions.openauctions.auction.bids.bid > 500000 RESULT object name "ring" description "large diamond ring" comment "very good condition" Ein Prinzip von Lorel ist es, dass man sich, um eine Query zu schreiben, weder um Unregelmässigkeiten in der Datenbank sorgen machen oder die genau Struktur der Objekte kennen sollte, noch sollte man sich um präzise Typen kümmern müssen. Die Query will keinen Run-time Error ausl”ösen, wenn bid einen string Wert hat oder komplex ist, oder wenn bid oder object single-valued, set-valued oder sogar für gewisse Gruppenmitglieder nicht vorhanden ist. Die Query aus dem Beispiel wird egal wie die aktuelle Struktur der Datenbank aussieht erfolgreich ausgeführt und eine angemessene Antwort liefern. Der Lore Query Prozessor schreibt Queries in eine etwas kompliziertere Query im Stil von OQL um. Die Query aus unserem obigen Beispiel wird folgendermassen umgeschrieben: QUERY select O from auctions.open_auctions.auction.bids B, auctions.open_auctions.auction.object O where exists P in B.bid : P > 500000 Abbildung 2.6 zeigt den Lore Query Plan für diese Query. Iterators und Objekt Assignments Für das Query processing wird ein rekursiver Iterator verwendet. Mit Iteratoren beginnt die Ausführung oben am Queryplan, wobei jeder Knoten im Plan pro mal ein Tupel von seinen Kindern anfragt und auf den Tupel Operationen durchführt. Wenn ein Knoten seine Operationen beendet hat, gibt er das resultierenden Tupel nach oben an sein Parent weiter. die Slots für die Beispiel Query zeigt Abbildung 2.7. Scan Operator Der Scan Operator ist in seiner Funktionalität ähnlich dem relationalen Scan. Es werden aber nicht die Menge der Tuples in einer Relation gescannt, sondern dieser Scan gibt alle OIDs (siehe 2.7.1) der Subobjekte von einem gegebenen Objekt, einem bestimmten Pfad folgend zurück. Der Scan Operator ist folgendermassen definiert: Scan ( StartingOASlot, Path_expression, TargetOASlot) Scan startet von der OID die im StartingOASlot gespeichert ist, und platziert bei jeder Iteration die OID des nächsten Subobjekts, das die Path_expression erfüllt in den TargetOASlot und das solange bis keine passenden Subobjekte mehr vorhanden sind. Query Ausführung In Lore gibt es verschiedene Strategien eine Query auszuführen: • Top Down: Scan basierte Query Plane führen einen top-down Traversierung durch (Abbildung 2.6). Für unser Beispiel heisst das: finde die Objekte entlang des Pfades auctions.open_auctions.auction.object dann schaue für jedes Element ob ein Pfad auction.bids.bid existiert der für bid einen Wert > 500000 hat. • Bottom Up: benutze einen Index um alle atomaren Objekte mit einem Wert > 500000 zu finden. Dann traversiere rückwärts entlang der umgekehrten Path_expression durch die Daten. Diese Methode braucht einen zusätzlichen Index um Parents von Knoten zu finden. • Hybrid: Evaluiere einen Teil der from Klausel um eine Menge von gültigen Objekten zu identifizieren. Dann benutze den Index um atomare Objekte die die where Klausel erfüllen zu finden und benutze dann eine andere Index Struktur um bis zum selben Punkt wie die Top Down Suche aufwärts zu steigen und kombiniere dann die traversierten Pfade um das Resultat zu erhalten. 2.6. SPEZIALISIERTE SYSTEME 23 Project (OA4) Join Select (OA6 = TRUE) Join Scan (OA2,"object",OA4) Join Scan (OA2,"bids",OA3) Join Join Scan (OA1,"auction",OA2) Aggr (Exists, OA5, OA6) Select (OA5 > 500000) Scan (OA3,"bid",OA5) Scan (OA0,"open_auctions",OA1) Scan (Root,"auctions",OA0) Abbildung 2.6: Lore Query Plan OA0 (auctions) OA1 OA2 OA3 (OA0.open auctions) (OA1.auctions) (OA2.bids) OA4 OA5 OA6 (OA2.object) (OA3.bid) (true/fals) Abbildung 2.7: Beispiel für Object Assignment 24 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN • Inside Out: identifiziere Bereiche des Graphen die den mittleren Teil der Path Expression erfüllen, dann traversiere aufwärts durch die Parents und abwärts durch die Children um den restlichen Teil der Path Expression zu evaluieren. Indexing In traditionelle relationale Datenbanken werden Indexe auf Attribute kreiert um Tuples mit einem bestimmten Attributwerte schnell zu finden. In Lore reicht ein solcher Value Index alleine nicht aus, weil der Pfad zu einem Objekt genauso wichtig wie der Wert des Objekts ist. In Lore gibt es deshalb zwei verschiedene Indexe: • Vindex oder Value Index um Objekte mit einem spezifischen Wert zu finden. Ein Vindex hat als Input ein Label, einen Operator und einen Wert. Er gibt alle atomaren Objekte die eine eingehende Kanten mit dem spezifizierten Label und einen Wert der den spezifizierten Operator Wert erfüllen (z.B. <500000) haben. Die gewünschten eingehenden Labels sind normalerweise zu Query Processing Zeit bekannt, weshalb Vindex nach Labels aufgeteilt werden. Ein Vindex wird über alle atomaren Objekte mit den Basistypen Integer, Real oder String gebildet. Administratoren ist es erlaubt selektiv Indexe über häufig gebrauchte Labels zu bilden. Da man in Lore Werte mit verschieden Typen vergleichen kann werden pro Label drei verschiedene Indexe gebildet. – String Vindex für String basierte atomare Werte (string,HTML,URL, usw.). – Real Vindex für alle numerisch basierten Werte (integer und real). – String-coerced-to-real Vindex für alle string Values die in Integer oder real umgewandelt werden k”önnen (werden im Index als Real gespeichert). Jeder Index ist als B+ Baum implementiert • Lindex oder Link (edge) Index welcher Parents lokalisiert. Ein Lindex nimmt als Input eine OID und ein Label und gibt alle OIDs der Parents via das spezifizierte Label zurück. Lindexe sind n”ötig um Parents zu lokalisieren, weil in OEM inverse Zeiger nicht unterstützt werden. Wenn kein Label spezifizert wird, werden alle Parents und deren Labels zurückgegeben. Es wird für die ganze Datenbank ein Lindex kreiert, der mittels erweitertem Hashing implementiert ist. Zwei andere Arten von Indexen wären für das OEM auch noch m”öglich3 : • Tindex oder Text Index lokalisiert atomare String Values die spezifische W”örter oder Wortgruppen enthalten. • Pindex oder Path Index erlaubt schnellen Zugriff auf alle erreichbaren Objekte via einem spezifizierten Pfad. Pindex gibt für einen Pfad p alle Objekte o zurück die über p erreichbar sind. Lore indexiert nur Pfade die an einem named Objekt beginnen und keine reguläre Ausdrücke enthalten. Lore: DataGuides Da eine Lore Datenbank kein explizites Schema hat, sind Query Formulierungen und Query Optimierungen eine besondere Herausforderung. Ohne einer gewissen Ahnung der Struktur der zugrunde liegenden Datenbank kann das Schreiben von aussagekräftigen Queries schwierig sein. Ein DataGuide ist eine kurze und genau Zusammenfassung der Struktur einer OEM Datenbank, selbst wieder als OEM Objekt gespeichert. Jeder m”ögliche Pfadausdruck der Datenbank ist genau einmal gespeichert und ein DataGuide hat keinen Pfadausdruck gespeichert, der nicht existiert. Im typischen Situation ist ein DataGuide wesentliche schmaler als die Originaldatenbank. Den DataGuide für unser Beispiel zeigt Abbildung 2.8. DataGuides werden dynamisch generiert und wenn die Struktur ändert dynamisch angepasst. Sie sollen dem Benutzer helfen eine Query zu schreiben und durch die Datenbank zu browsen um die Struktur der Datenbank zu untersuchen. Zudem unterstützen sie den Query Prozessor und helfen dem System Zugriffsstatistiken zu erstellen. 3 Wurden in der Vorlesung vorgestellt, finden aber im Paper [37] keine Erwähnung, sind also wahrscheinlich in Lore nicht implementiert 2.7. DATENSPEICHERMODELLE FÜR XML 25 open_auctions auctions auction item object initial name bids description bid comment time val Abbildung 2.8: Lore DataGuide für die Beispiel Datenbank aus Abbildung 2.9 2.7 2.7.1 Datenspeichermodelle für XML OEM Das Object Exchange Model (OEM) ist ein an der Stanford Universität entwickeltes Object Model, das spezielle für das Verwalten von semi-strukturierte Daten designt wurde[37]. Daten in diesem Modell können als beschrifteter gerichteter Graph (labeled directed graph) betrachtet werden. Die Knoten (vertices) im Graph sind Objects und jedes Objekt hat einen eindeutigen object identifier (oid). Atomic Objects haben kein Kanten die von ihnen wegführen (outgoing edges) und enthalten eine wert aus einem der Basis Typen integer, real, string, gif, java, audio, etc. Alle anderen Objekt haben eine beliebige Anzahl ausgehende Kanten und werden complex objects genannt. Ein Beispiel zeigt Abbildung 2.9. Objekt &2 ist z.B. ein komplexes Objekt mit den atomaren Subobjekten &5, &6 und &7. Das atomare Objekt &5 hat dabei den Wert “ring”. Names sind spezielle Labels die als Aliase für Objekte und als Einstiegspunkt in die Datenbank dienen. In unserem Beispiel ist auctions ein Name für das Objekt &1. Jedes Objekt, das nicht durch einen Pfad von einem Namen (Root Object) aus erreicht werden kann wird als gelöscht betrachtet. In einer OEM Datenbank gibt es keine fixe Schemanotation. Alle schematischen Informationen sind in den Labels enthalten und können dynamisch ändern. Eine OEM Datenbank ist also selbst beschreibend und es gibt keine auferlegte Ordnung. Das Model kann Unvollständigkeit von Daten ebenso handhaben wie Struktur- und Typen- Heterogenität. D.h. das Kanten mit einem gleichen Label auf Objekte mit verschiedenen Typen (verschiedene atomare Typen sowie komplexe Typen) zeigen. Da es sich beim Model um einen gerichtet Graphen handelt können natürlich auch Zyklen (cycles) vorkommen. Speicherung von OEM Objekten in Lore Lore arrangiert Objekte in physikalischen disk pages; jede page hat eine Anzahl slots mit einem einzelnen Objekt. da Objekte eine variable Länge haben platziert Lore Objekte in einem first-fit Algorithmus und bietet ein object-forwarding, falls ein Objekt zu gross für dessen Page werden sollte. Zusätzlich unterstützt Lore grosse Objekte, die sich über mehrere Pages ausbreiten können. Objekte werden in depth-first Art auf die Pages verteilt, in erster 26 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN auctions &1 item item open_auctions name &2 &3 object "ring" auction auction &5 &4 comment &6 "large diamond ring" &10 &9 &8 description name description "camera" &7 "very good condition" initial &12 bids bids initial &15 &14 &13 75 500’000 bid bid bid &18 &17 &16 550’000 time &19 [12:00 2/11/2004] val &20 85 Abbildung 2.9: OEM Daten Model &11 "compact digital camera" 2.7. DATENSPEICHERMODELLE FÜR XML <auktion> <artikel key="ID108"> <name>Ring</name> <beschreibung> Grosser Diamant </beschreibung> </artikel> <artikel key="ID203"> <name>Kamera</name> <beschreibung> Digitalkamera </beschreibung> <kommentar> fast neu </kommentar> </artikel> </auktion> 27 auktion, o1 "ID108" key artikel, o2 name, o3 cdata,o4 string artikel, o7 beschreibung, o5 name, o8 cdata,o6 cdata, o9 string "Ring" "Grosser Diamant" string key "ID203" beschreibung, o 10 kommentar, o 12 cdata,o11 cdata, o13 string "Kamera" "Digitalkamera" string "fast neu" Abbildung 2.10: XML Dokument Ausschnitt und der korrespondierende Syntax Baum Linie, weil die scan-basierten Pläne (scan Operator) die Datenbank depth-first traversiert. Es ist nicht immer möglich alle Objekte nahe zu ihren Parents zu platzieren, weil ein Objekt mehrere Parents haben kann, in diesem Fall wird das Objekt mit einem willkürlichen Parent gespeichert. Wenn ein Objekt nicht über einen Pfad von einem named Objekt aus erreicht werden kann, wird es vom garbage collector gelöscht. 2.7.2 Monet Begriffe Die Konzepte von associations und path summaries bildet die Basis für das Monet XML Model [43]. Definition 2. Ein Paar (o, ·) ∈ oid × (oid ∪ int ∪ string) nennt man eine association. Die verschiedenen Typen von Assoziationen beschreiben verschiedene Teile des Baumes: Assoziationen vom Typ oid × oid representieren Kanten, d.h. Parent-Child Abhängigkeiten. Attributwerte (inklusive CDATA, durch Vertices mit Label ‘string’, die von ‘cdata’ beschrifteten Knoten starten, repräsentiert) werden durch Assoziationen vom Typ oid × string modelliert, während Assoziationen vom Typ oid × int für die Erhaltung der Dokument Topologie benutzt werden. Definition 3. Für einen Knoten o im Syntax Baum, bezeichnen wir die Sequenz von Labels entlang des Pfades (Vertex und Edge Labels) von der Wurzel bis o mit path(o). e Als ein Beispiel betrachte in Abbildung 2.10 den Knoten mit OID o3 . Sein Pfad ist auktion → e e artikel → name. Der korrespondierende Character Data String “Ring” hat den Pfad auktion → e e a e a artikel → name → cdata → string. → bezeichnen dabei Kanten zu Elementen und → zu Attributen. Pfade beschreiben die Position des Elements im Graph relativ zum Root Knoten.Die menge aller Pfade in einem Dokument nennt man die path summary des Dokuments. Das Monet XML Model In Monet werden alle Assoziationen des selben Typs in der selben Binären Relation abgespeichert. Eine Relation die das Tupel (·, o) enthält wird path(o) genannt und umgekehrt ist 28 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN e auktion → artikel e e auktion → artikel → name e e e auktion → artikel → name → cdata e e e a auktion → artikel → name → cdata → string = {ho1 , o2 i, ho1 , o7 i} = {ho2 , o3 i, ho7 , o8 i} = {ho3 , o4 i, ho8 , o9 i} = {ho4 ,“Ring”i, ho9 ,“Kamera”i} e e auktion → artikel → beschreibung e e e auktion → artikel → beschreibung → cdata e e e a auktion → artikel → beschreibung → cdata → string = {ho2 , o5 i, ho7 , o10 i} = {ho5 , o6 i, ho10 , o11 i} = {ho6 ,“Grosser Diamant”i, ho11 ,“Digitalkamera”i} e e auktion → artikel → kommentar e e e auktion → artikel → kommentar → cdata e e e a auktion → artikel → kommentar → cdata → string e a auktion → artikel → key = {ho7 , o12 i} = {ho12 , o13 i} = {ho13 ,“fast neu”i} = {ho2 ,“ID108”i, ho7 ,“ID203”i} Abbildung 2.11: Monet Transformation Mt des Beispiels aus Abbildung 2.10 ein Tupel genau in einer Relation gespeichert. Monet mappt ein XML Dokument d in vier Strukturen mittels der Monet Transformation Mt : Definition 4. Ist ein XML Dokument d gegeben, dann ist die Monet Transformation ein Quadrupel Mt (d) = (r, R, A, T) wobei R die Menge der binären Relationen ist, welche alle Assoziationen zwischen Knoten enthält (Parent-Child); A die Menge der binären Relationen ist, welche alle Assoziationen zwischen Knoten und deren Attributwerten enthält(inklusive Character Data); T die Menge der binären Relationen ist, welche alle Paare von Knoten und deren Ordnung ( ranking) enthält. r ist die Wurzel (root) des Dokuments. Abbildung 2.11 zeigt die Monet Transformierung des Beispiels aus Abbildung 2.10. Strong und Weak Associations Da XML Dokumente semi-strukturierte Daten sind, kann man nicht erwarten, dass alle Instanzen eines Typs die selbe Struktur haben. In unserem Beispiel hat das zweite artikel Element eine kommentar Element, während das erste keines hat. In Monet unterscheidet man deshalb zwischen zwei Arten von Assoziationen: (strong) associations und weak Associations. Strong associations bilden den strukturierten Teil von XML (d.h. sie sind in allen Instanzen eines Typs vorhanden); weak associations sind für den semi-strukturierten Teil (sie erscheinen nicht in allen Instanzen). Ausführung Interessant ist die Frage wie eine OQL ähnliche Query übersetzt wird. Mit folgender Beispiel Query wollen wir den Artikel mit den Namen “Kamera” der noch “fast neu” ist finden: 2.8. ÜBERSICHT/VERGLEICH 29 select a from auktion -> artikel a, a -> name -> cdata -> n, a -> kommentar -> cdata -> k where n="Ring" and k like "fast neu"; Zuerst wird eine Assoziation zwischen dem ersten und dem letzten Element im Pfad etabliert, indem alle Pfadausdrücke die nicht in der Datenbank enthalten sind durch joining der binären Relationen entlang der Pfad Spezifikation fallen gelassen werden. Dann wird die Schnittmenge der der spezifizierten Elemente genommen. Am Schluss werden die relevanten Elemente selektiert. Die from Klausel spezifiziert für unser Beispiel folgende Elemente: a = {o2 , o7 } assoc(a → n) = {(o2 ,“Ring”), (o7 ,“Kamera”)} assoc(a → k) = {o7 ,“fast neu”)} Fehlende Features für XML Das Monet Speichermodell stellt die Eindeutigkeit von ID und die Konsistenz der ID/IDREFs nicht sicher. Zudem wurde zur Vereinfachung nicht zwischen CDATA und PCDATA unterschieden. Um das ID/IDREF Konzept zu unterstützen müsste die Unterstützung von rich data types ID und IDREF unterstütz werden. Um IDREFS speichern zu können müssten multi-valued Attribute unterstützt werden. Ausserdem müsste die binäre Relation A in der Monet Transformation Mt erweitert werde um die Einzigartigkeit der ID und die Integrität mit den ID/IDREFS kodieren zu können. Die Unterstützung von ID/IDREF könnte erreicht werden, indem man zwei Subsets A spezifiziert: AID und AIDREF . Diese zwei Subsets würden dann die Paare von Knoten/Attributwerte in A enthalten die ein ID oder IDREF sind. Das System müsste dann diese Information auf dem laufenden und Konsistent halten. 2.8 Übersicht/Vergleich Es folgt eine kleiner Vergleich zwischen den verschiedenen Speichermodellen. Einerseits die unter Abschnitt 2.4 vorgestellten Methoden Custom mit der Ausnützung des Schemas und das General Design, welches auf der Basis von DOM ein XML Dokument speichert, sowie die unter Abschnitt 2.7 vorgestellten Datenmodelle für semi-strukturierte Daten Lore (OEM) und Monet: 30 KAPITEL 2. SPEICHERN VON XML DOKUMENTEN • höhere Performance • Beim Querying der Datenbank sind weniger TabellenScans involviert Custom Design • Statisches Modell: jede kleine Änderung der Dokument Struktur kann grosse Änderungen in der Tabellen Struktur hervorrufen • Optionale Attribute werden entweder in separaten Tabellen modelliert oder durch die Verwendung von NULLs • Die Struktur der Queries ist sehr nahe der Struktur des Schemas (einfach zu lesen) • Generelles Model für jede Art von XML Dokumenten • Einfache und reguläre Struktur (wenige Tabellen) • Speichert keine Informationen über die gespeicherten Dokumente DOM basiert • Um die Struktur eines Dokuments zu erfahren, muss das ganze Dokument gescannt werden und die Struktur extrahiert werden • Die Queries sind länger, schwieriger zu lesen und kann das scannen von grossen Tabellen bedeuten. • Generelle Lösung für semi-strukturierte Dokumente • DataGuides bieten Informationen über die Struktur eines Dokuments Lore (OEM) • Direkte Kontrolle der Objektspeicherung führt zu einem effizienten Layout auf der Disk (Clustering in depth-first Ordnung) • Um die Struktur eines Dokuments zu erfahren, muss das ganze Dokument gescannt werden und die Struktur extrahiert werden • Wenn keine angemessener Index vorhanden ist, sind ineffiziente Queries möglich, da mehrere Disk Scans nötig sind • Generelle Speicherung von semi-strukturierte Dokumente Monet • Gute Performance durch Aufteilung der Daten in mehrere Tabellen • Das Handling von optionalen (unstrukturierten) Daten ist möglicherweise ineffizient (viele Tabellen mit wenigen Zeilen) • Lose Transformation von XML Dokumenten Kapitel 3 Querying von XML Dokumenten 3.1 Einführung Wir brauchen eine Query Language um XML Daten verarbeiten zu können. Die logische bzw. physikalische Datenunabhängigkeit sollte dabei bewahrt werden, d.h. die Daten sollen als Abstraktes Modell beschrieben werden und nicht als physikalischer Datenspeicher. Wir wollen zudem eine deklarative Programmiersprache, die das “was” und nicht das “wie” beschreibt. SQL ist dabei nicht ausreichend, weil man die Eigenheiten wie z.B. hierarchische, geordnete Struktur, textuelle Repräsentation und mögliche Schemalosigkeit des XML Modells handhaben können muss. XML Daten unterscheiden sich von relationalen Daten in verschiedenen bedeutenden Aspekten, die das Design einer Query Language stark beeinflussen. Relationale Daten tendieren zu einer regulären Struktur, was erlaubt, die beschreibenden Metadaten in einem separaten Katalog zu speichern. XML Daten dagegen sind oft heterogen und die Metadaten sind direkt im Dokument enthalten. XML Dokumente enthalten oft viele Ebenen von ineinander geschachtelten Elementen. XML Dokumenten haben eine innere Reihenfolge, während relationale Daten grundsätzlich ungeordnet sind, ausser wenn eine Ordnung aus den Daten abgeleitet werden kann. Ein weiteres Problem kommt dazu, weil fehlende Informationen durch die Abwesenheit eines Elements und im allgemeinen nicht wie beim relationalen Modell durch NULL repräsentiert werden. Die existierenden Query Sprachen sind deshalb für XML Dokumente nicht geeignet. 3.2 XQuery XQuery ist eine funktionale Sprache, welche mehrere Arten von Ausdrücke umfasst, die mit voller Allgemeinheit verschachtelt und zusammengestellt werden können. Sie basiert auf dem Typsystem von XML Schema und wurde so designt, dass sie zu anderen mit XML nahen Standards kompatibel ist[16]. XML Query wird vom der XML Query Working Group des World-Wide Web Consortium (W3C) entwickelt und ist noch nicht vollständig abgeschlossen1 . XQuery hat sich aus einem Sprachvorschlag namens Quilt entwickelt und ist stark von existierenden W3C Standards wie XML Schema, XSLT, XPATH und XML selber beeinflusst. Speziell XPath ist so wichtig und eng mit XQery verbunden, dass XQuery als ein Superset von XPath definiert ist. Das ursprüngliche Design konzentriert sich nur auf die Gewinnung 1 Berichte unter www.w3.org/XML/Query 31 32 KAPITEL 3. QUERYING VON XML DOKUMENTEN von Informationen aus XML Dokumenten und stellt keine Möglichkeit zum ändern von Informationen (update) bereit. XQuery umfasst zwei Syntax: eine maschinenlesbare XML Form machine-readable form (XQueryX ) und eine für die Lesbarkeit für Menschen optimierte Form human-readable form (XQuery). 3.2.1 Daten Modell Das query data model wie es in [51] beschrieben ist, bietet eine abstrakte Repräsentation eines oder mehrerer XML Dokumente oder Dokumentfragmente. Das Daten Modell basiert auf der Notation einer Sequenz. Eine sequence ist eine geordnete Kollektion von Null oder mehr items. Ein item kann ein node oder ein atomic value sein. Ein atomic value ist eine Instanz von einem der built-in Datentypen, die durch XML Schema definiert sind, also z.B. string, integer, decimal und date. Ein node entspricht einem der sieben Arten von Knoten, element, attribute, text, document, comment, processing instruction und namespace node. Ein node kann andere Knoten als Kinder haben. Unter all den Knoten in einer Hierarchie gibt es eine totale Ordnung, die man document order nennt. Beispiel 3.1: XML <book year="2000 2004"> <title>The Social Life of Information</title> <author>Brown</author> <author>Duguid</author> <publisher>Harvard Business School press</publisher> <price>11.87</price> <review><em>Very</em>readable</review> </book> XQuery element book{ attribute year{2000, 2004}, element title {"The Social Life of Information"}, element author {"Brown"}, element author {"Duguid"}, element publisher {"Harvard Business School press"}, element price {11.87}, element review {element em {"Very"},"readable"} } 3.2.2 XPath Wie in [50] beschrieben, besteht der Hauptzweck der vom W3C entwickelten Sprache darin Teile eines XML Dokuments zu adressieren. Sie bietet auch grundsätzliche Möglichkeiten um Strings, Zahlen und Boolean zu manipulieren. XPath operiert auf der abstrakten, logischen Struktur eines XML Dokuments und nicht dem XML Text. XPath hat seinen Namen wegen der Benutzung von Pfadnotationen wie in URLs um durch die hierarchische Struktur eines XML Dokuments zu navigieren. Beispiel 3.2: XML Dokument: <bib> <book year="2003"> <title>XML Data Management</title> <author>Chaudhri</author> 3.2. XQUERY 33 <author>Rashid</author> <author>Zicari</author> <publisher>Addison-Wesley</publisher> <price>34.99</price> <review>Comprehensive guide to XML and databases</review> </book> <book year="2000 2004"> <title>The Social Life of Information</title> <author>Brown</author> <author>Duguid</author> <publisher>Harvard Business School press</publisher> <price>11.87</price> <review><em>Very</em>readable</review> </book> </bib> XPath Expression: doc("books.xml")/bib/book Verarbeitung : • öffne books.xml mit der eingebauten Funktion doc() und gib den Dokumentknoten zurück • selektiere das Element bib am Dokumentanfang mit /bib • selektiere die book Elemente innerhalb des bib Elements mit /book äquivalent dazu könnte man auch doc("books.xml")//book schreiben. Ein Pfadausdruck (auch location path) besteht aus einer Reihe von Schritten, location steps, die durch ein / (“slash”) getrennt sind. Die Ausdrücke werden von links nach rechts ausgewertet und das Resultat jedes Schrittes ist eine Sequenz von Knoten. Der Wert der path expression ist die Sequenz von Knoten, die aus dem letzten Schritt resultiert. Jeder Schritt wird im Kontext von einem bestimmten Knoten, dem Kontextknoten (context node) ausgewertet. Im Allgemeinen kann ein Schritte ein Ausdruck sein, der ein Sequenz von Knoten zurück gibt. Eine wichtige Art von Schritt, ein axis step beginnt am context node und bewegt sich in einer bestimmten Richtung, der axis durch die Knotenhierarchie. Während der axis step sich entlang dieser Richtung bewegt, selektiert er Knoten die ein Selektionskriterium erfüllen. Diese Selektionskriterium kann einen Knoten basierend auf seinem Namen, seiner Position relativ zum context node oder einem Prädikat basierend auf dem Wert des Knoten auswählen. Der ungekürzte Syntax besteht aus einer axis und einem Selektionskriterium, die durch zwei Doppelpunkte getrennt sind (siehe Abbildung 3.1). Es gibt aber auch einen gekürzten Syntax: child:: attribute:: /descendant-or-self::node()/ self::node() parent::node() Dieser Locationpfad wird als default benutzt, wenn keine Axis angegeben wird. Kann also auch weggelassen werden. Diese Axis kann als @ abgekürzt werden. Wird als // abgekürzt der context node kann mit . abgekürzt werden. der context node parent kann mit .. abgekürzt werden. 34 KAPITEL 3. QUERYING VON XML DOKUMENTEN • child::para selektiert die para Element die Kinder des context node sind • child::* selektiert alle Kinder des context node • child::text() selektiert alle text node Kinder des context node • child::node() selektiert alle Kinder des context node, unabhängig vom Typ • attribute::name selektiere das name Attribute des context node • attribute::* selektiert alle Attribute des context node • descendant::para selektiert die para Element die Nachkommen des context node sind • ancestor::div selektiert alle div Vorfahren des context node • ancestor-or-self::div selektiere die div Vorfahren des context node und wenn der context nod ein div Element ist, auch den context node selbst • descendant-or-self::para selektiere die div Nachkommen des context node und wenn der context nod ein div Element ist, auch den context node selbst self::para selektiere den context node, wenn er ein para Element ist, sonst selektiere nichts • child::chapter/descendant::para selektiere die para Elemente die Nachkommen des chapter Kindelements des context node sind • child::*/child::para selektiert alle para grandchildren des context node • / selektiere die document root (welche immer der Vater (parent) des document element ist) • /descendant::para selektiert alle para Elemente im selben Dokument wie der context node • /descendant::olist/child::item selektiert alle item Elemente, welche eine olist Vater haben und im selben Dokument wie der context node sind • child::para[position()=1] selektiere das erste para Kind des context node • child::para[position()=last()] selektiere das letzte para Kind des context node • child::para[position()=last()-1] selektiere das vorletzte para Kind des context node • child::para[position()>1] selektiert alle Kinder des context node ausser dem ersten • following-sibling::chapter[position()=1] selektiere den nächsten chapter sibling des context node • preceding-sibling::chapter[position()=1] selektiere den vorherigen chapter sibling des context node • child::para[attribute::type="warning"] selektiert alle para Kinder des context node, welche ein type Attribute mit dem Wert warning haben • child::para[attribute::type=’warning’][position()=5] selektiere das fünfte para Kind des context node welches ein type Attribute mit dem Wert warning hat • child::para[position()=5][attribute::type="warning"] selektiere das fünfte para Kind des context node, wenn es ein type Attribute mit dem Wert warning hat • child::chapter[child::title=’Introduction’] selektiere die chapter Kinder des context node, welche eines oder mehr title Kinder mit den String Wert Introduction haben • child::chapter[child::title] selektiere die chapter Kinder des context node, welche eines oder mehr title Kinder haben • child::*[self::chapter or self::appendix] selektiere die chapter und appendix Kinder des context node • child::*[self::chapter or self::appendix][position()=last()] selektiere das letzte chapter oder appendix Kind des context node Abbildung 3.1: XPath Axis Beispiele mit ungekürzter Syntax 3.2. XQUERY 3.2.3 35 Node Test Wie man schon im Beispiel aus Abbildung 3.1 sieht, können verschiedene Knoten(-typen) angesprochen werden: * node() text() comment() processing-instruction() node name selektiert alle Knoten (mit dem selben prinzipiellen Knotentyp) selektiert alle Knoten unabhängig von deren Typ selektiert alle Text-Knoten selektiert alle Kommentar-Knoten selektiert alle processing-instruction-Knoten selektiert Knoten mit dem spezifischen Knotennamen 36 KAPITEL 3. QUERYING VON XML DOKUMENTEN Teil II Database Application Programming 37 Kapitel 4 SQL in Programmierumgebungen 4.1 Datenbankapplikationen Um auf eine Datenbank zuzugreifen haben die meisten DBMS ein interaktives Interface in welches SQL Kommandos direkt eingetippt und ausgeführt werden können. Dieses Interface genügt meist, für das Erstellen einer Datenbank und ihren Tabellen oder einzelnen einfachen Queries. Die meisten Interaktionen mit einer Datenbank werden in der Praxis aber durch Applikationen ausgeführt. Eine andere übliche Methode auf eine Datenbank zuzugreifen ist über ein Webinterface um z.B. Bücher zu bestellen oder Flugtickets zu reservieren. Es existieren verschiedene Techniken um Datenbank Interaktionen in Applikationen einzubinden: 1. eingebettete Datenbankkommandos in einer allgemeinen Programmiersprache (Embedded SQL): Es werden Datenbank Statements in eine Host Programmiersprache eingebettet embedded . Ein Precompiler oder Preprocessor scannt zuerst den Programmcode und extrahiert Datenbankstatments für die Verarbeitung durch das DBMS. Im Programmcode werden sie durch function calls zum DBMS-generierten Code ersetzt. Abschnitt 4.3 2. Benutze eine Library mit Datenbank Funktionen(Call Level Interface): Ein Library wird von der Hostsprache benutzt um Datenbankinteraktionen durchzuführen. Es gibt z.B. Funktionen um eine Verbindung zur Datenbank herzustellen, um eine Query auszuführen usw. Diese Methoden bietet ein so genanntes Application Programming Interface (API) um von einer Applikation auf eine Datenbank zuzugreifen. Abschnitt 4.4 3. Entwickle eine neue Sprache: Eine database programming language wird von Anfang an neu entwickelt um kompatibel mit dem Datenbank Modell und der Query Sprache zu sein. Zusätzliche Programmierstrukturen wie Schleifen (loop) und bedingte Statements werden einer Datenbanksprache hinzugefügt, um sie zu einer vollumfänglichen Programmiersprache zu machen. Abschnitt 4.5 Es findet zwischen dem Client mit der Applikation und dem Datenbankserver eine Aufgabentrennung statt. Der Client präsentiert die Daten und der Server verwaltet sie. Was man sich bei der Entwicklung einer Datenbankapplikation überlegen muss, ist wo die Logik der Applikation ausgeführt wird. Soll sie auf dem Server ausgeführt werden (SQL/PSM, Stored Procedures, Triggers) oder doch besser auf dem Client (ESQL, SQL/CLI, ODBC, JDBC) ? Also die Frage ob wir einen Thin Client oder einen Thick Client wollen. 39 40 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN 4.2 Impedance Mismatch Impedance Mismatch ist die Bezeichnung für das Problem, welches wegen den Unterschieden zwischen dem Datenbankmodell und dem Programiersprachenmodell auftaucht. Das erste Problem das auftauchen kann, ist dass sich die Datentypen der Programmiersprache von den Attributtypen im Datenbankmodell unterscheiden. Man braucht also für jede Hostprogrammiersprache ein binding welches für jeden Attributtype den kompatiblen Datentyp der Programmiersprache spezifiziert. Ein anderes Problem tritt auf, weil das Resultat der meisten Queries ein set oder bag von Tuples zurück gibt und jedes Tupel besteht aus einer Sequenz von Attributen. In einem Programm muss man oft auf individuelle Daten innerhalb der Tuples zugreifen können. Man muss also eine query result data structure, eine Tabelle, auf eine entsprechende Datenstruktur der Programmiersprache mappen. Ausserdem braucht es Mechanismen um über die Tupels eines Resultats zu iterieren. Wir können zum Programmieren von Applikationen nicht Standard SQL benutzen, weil wir keine Pointers, Loops und Branches haben (Das Standard SQL ist nicht Turing complete). 4.3 Embedded SQL Die meisten SQL Statements, inklusive Daten oder Constraint Definitionen, Queries, Updates oder View Definitionen, können in eine Programmiersprache (host language) eingebettet werden. Ein eingebettetes SQL Statement wird durch den Präfix EXEC SQL von Programmiersprachen Statements unterschieden, damit der preprocessor (oder precompiler ) die SQL Statements vom restlichen Programmcode trennen und an dessen Stelle Function Pointers einfügen kann. Der preprocessor übernimmt auch den Typ, die Syntax und die Semantik Überprüfung sowie die Fehlerbehandlung der SQL Statements. Im folgenden wird C als host language benutzt 4.3.1 Shared Variables Innerhalb eines eingebetteten SQL Kommandos wollen wir vielleicht auf eine spezielle deklarierte Variable zugreifen können. Diese shared variables werden in einer declare section deklariert und innerhalb eines SQL Statements mit einem Doppelpunkt (:) als Präfix geschrieben. Beispiel 4.1: void getStudio() { EXEC SQL BEGIN DECLARE SECTION; char studioName[50], studioAddr[256]; char SQLSTATE[6]; EXEC SQL END DECLARE SECTION; printf("Studiu Name: "); scanf("%s", studioName); printf("Studio Address:"); scanf("%s", studioAddr); EXEC SQL INSERT INTO Studio(name, address) VALUES (:studioName, :studioAddr); } SQLSTATE enthält einen fünfstelligen Errorcode (siehe Tabelle 4.1). 4.3. EMBEDDED SQL SQLSTATE 01001 01003 01004 02000 07002 22001 40001 41 Beschreibung Cursor update or delete failure due to optimistic concurrency check failure. Elimination of null values by set operator. Select or fetch into host variable that is too short. No data found. Number of columns does not match number of host variables. String data truncated (on right) on insert or update. Transaction rollback due to deadlock. Tabelle 4.1: SQLSTATE Error Codes 4.3.2 Retrieving Data SELECT FROM WHERE Queries sind wegen dem Impedance Mismatch nicht direkt embeddable. Man kann einen cursor als einen Pointer betrachten, der auf ein einzelnes Tupel im Resultat einer Query zeigt. Der Cursor wird deklariert, wenn die SQL Query im Programm deklariert wird. Später im Programm holt das OPEN CURSOR Kommando das Queryresultat von der Datenbank und setzt den Cursor auf eine Position vor der ersten Reihe im Resultat. FETCH Kommandos bewegen den Cursor zur nächsten Reihe im Queryresultat und kopiert den Attributwert in die host language Variable die im FETCH Kommando spezifiziert wurde mit der INTO Klausel. Mit CLOSE wird ein Cursor wieder geschlossen. Beispiel 4.2: void getAllMoviesOfStudio(char *studio) { EXEC SQL BEGIN DECLARE SECTION; char movie[50]; int year; char SQLSTATE[6]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE mcursor CURSOR FOR SELECT title, year FROM Movies JOIN Studios ON studio = name WHERE name = :studio; EXEC SQL OPEN mcursor; do { EXEC SQL FETCH FROM mcursor INTO :movie, :year; if (strcmp(SQLSTATE, "00000")) { printf("Found Movie: %s (%d).\n", movie, year); } else { break; } } while (1); EXEC SQL CLOSE mcursor; } Cursor können auch in SQL UPDATE oder DELETE Statements benutzt werden indem die WHERE Klausel erweitert wird: • DELETE FROM table WHERE CURRENT OF cursor 42 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN • UPDATE table SET column = value WHERE CURRENT OF cursor Scrolling cursor können benutzt werden um sich nicht sequentiell durch ein Relation zu bewegen. Der Syntax für die Deklaration eines solchen Cursors lautet: EXEC SQL DECLARE name SCROLL CURSOR FOR query Um mit einem scrolling cursor Tuples zu holen lautet der Syntax: • EXEC SQL FETCH (NEXT | PRIOR) INTO variable • EXEC SQL FETCH (FIRST | LAST) INTO variable • EXEC SQL FETCH RELATIVE offset INTO variable • EXEC SQL FETCH ABSOLUTE index INTO variable Um konkurrierende Updates handhaben zu können kann ein Cursor ein Set von Tuples lock en während er offen ist: EXEC SQL DECLARE name INSENSITIVE CURSOR FOR query Ein anderer Cursor kann aber trotzdem read-only auf ein solches gesperrtes Set zugreifen: EXEC SQL DECLARE name SCROLL CURSOR FOR query FOR READ ONLY Oft sind Queries erst zur Compilezeit unbekannt und werden erst während der Programmausführung generiert. Um Queries während der Laufzeit zu parsen und auszuführen benutzt man das so genannte Dynamic SQL. Man deklariert dazu eine Variable (z.B. sqlstring) in die man die Query schreibt, dann wird die Query erzeugt und ausgeführt. Beispiel 4.3: void doUpdate() { EXEC SQL BEGIN DECLARE SECTION; char sqlstring[256]; EXEC SQL END DECLARE SECTION; printf("Enter the Update Command: "); scanf("%s", sqlstring); EXEC SQL PREPARE sqlcommand FROM :sqlstring; EXEC SQL EXECUTE sqlcommand; } Man kann die letzten zwei Schritte (PREPARE und EXECUTE) auch kombinieren, wenn eine Query nur einmal ausgeführt werden muss: EXEC SQL EXECUTE IMMEDIATE sqlstring 4.3.3 Zusammenfassung Die Konzepte, welche mit ESQL (embedded SQL) eingeführt wurden sind auch für die meisten database connectivity libraries (4.4) üblich. Es gibt für die meisten üblichen Programmiersprachen, wie Ada, C, C++, Cobol, Fortran, Pascal, Eiffel, PL/1 Implementationen. Für Java gab es eine Implementation namens SQLJ. Die DMBMS Hersteller plannen aber nicht den ESQL Support weiterzuführen sondern forcieren den Umstieg auf Call Level Interfaces (siehe Abschnitt 4.4). 4.4. CALL LEVEL INTERFACE 4.4 43 Call Level Interface Embedded SQL (Abschnitt 4.3) wird manchmal als statische Methode der Datenbank Programmierung bezeichnet, weil der Querytext innerhalb des Programmcode geschrieben ist und nicht geändert werden kann ohne den Code neu zu compilieren und neu zu verarbeiten (ein Ausnahme ist dynamic SQL, Abschnitt 4.3.2 Seite 42). Die Benutzung von function calls ist eine dynamischere Methode der Datenbankprogrammierung als ESQL und wird heute vorgezogen. Eine Library von Funktionen, auch als application programming interface (API) bekannt, wird für den Zugriff auf eine Datenbank benutzt. Obwohl diese Methode flexibler ist, da keine preprocessor benötigt wird, hat diese Methode den Nachteil, dass der Syntax und andere Checks zur Laufzeit ausgeführt werden müssen. Ein anderer Nachteil ist, dass manchmal eine komplexere Programmierung nötig ist um auf Queryresultate zuzugreifen, da der Typ und die Anzahl der Attribute des Resultates vielleicht im Voraus noch nicht bekannt ist. Es existieren verschiedene Implementationen: • Open Database Connectivity (ODBC) • SQL/CLI (Call Level Interface), ein Nachfolger von ODBC, als Teil des SQL Standards. Wird z.B. für C benutzt. • JavaTM Database Connectivity1 (JDBC) Abschnitt 4.4.1 R • ActiveX Data Objects(ADO) 4.4.1 JDBC: SQL Funktionen für JAVA Programmierung Das JDBC API bietet Java Applikationen einen mid-level Zugriff auf die meisten Datenbanksysteme, mittels SQL[23]. JDBC ist Suns Versuch ein platform-neutrales Interface zwischen Java und Datenbanken zu kreieren. Mit JDBC kann ein Programmierer auf ein Standardset von Datenbankzugriffsmerkmalen und (normalerweise) einer Untermenge von SQL (SQL-92) zählen. Das JDBC API definiert eine Menge von Interfaces, welche die Hauptfunktionalitäten einer Datenbank kapseln. Ein Datenbank Hersteller (oder ein Drittentwickler) schreibt einen JDBC driver für ein bestimmtes Datenbanksystem. Die Grundfunktionalitäten von JDBC für J2SE werden durch die JDBC class libraries java.sql.* geboten. Eine Extension für J2EE bieten die javax.sql.* Libraries. JDBC Driver Ein JDBC driver ist eine Menge von Klassen welche die JDBC Interfaces für ein bestimmtes Datenbanksystem implementieren. JDBC wurde so designt, dass es einem einzelnen Java Programm erlaubt ist zu verschiedenen Datenbanken zu verbinden. Man nennt diese Datenbanken manchmal data sources. Diese data sources können in verschiedenen DBMS von unterschiedlichen Herstellern auf verschiedenen Maschinen gespeichert sein. D.h. Zugriff auf verschiedene data sources innerhalb eines Java Programms benötigt vielleicht JDBC driver von verschiedenen Herstellern. Um diese Flexibilität zu erreichen wird eine spezielle Klasse, der driver manager benutzt. Ein Treiber sollte beim driver manager registriert werden bevor er benutzt wird. Um einen Treiber explizit zu laden kann die generische Java Funktion zum Laden von Klassen benutzt werden, dadurch wird der Treiber geladen, beim Manager registriert und für das Programm verfügbar gemacht. Abbildung 4.1 zeigt ein einfaches Beispiel mit einer Verbindung auf eine Microsoft SQL Server und einem einfachen Abfrage Statement. Statements und wie Resultate behandelt werde folgt weiter unten. 1 Laut Sun ist JDBC nicht ein Akronym für Java Database Connectivity ([23] Kapitel 2), da aber die meisten Leute das so annehmen und auch in der Vorlesung (und den Folien) dieses Akronym benutzt wurde, erwähne ich es hier 44 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN import java.sql.*; import java.lang.*; public class JDBCExample { public static void main(String[] args) { try { // load Microsoft SQL Server driver Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); } catch (ClassNotFoundException e) { System.out.println("Unable to load Driver Class"); return; } try { // connect to database, specifying database, username and password Connection con = DriverManager.getConnection( "jdbc:microsoft:sqlserver://grant.inf.ethz.ch:1433","user","pwd"); // create and execute SQL Statement Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT name FROM address"); //display SQL Results while(rs.next()) { System.out.println(rs.getString("name")); } // release databse resources and close connection rs.close(); stmt.close(); con.close(); } catch (SQLExeption sqle) { System.out.println("SQL Exception: "+sqle.getMessage()); } } } Abbildung 4.1: Einfaches Beispiel einer Datenbankabfrage mit Java JDBC 4.4. CALL LEVEL INTERFACE 45 JDBC URLs Ein JDBC driver benutzt eine URL um eine bestimmte Datenbank zu identifizieren und eine Verbindung zu ihr herzustellen. Diese URLs haben im allgemeinen folgende Form: jdbc:driver :databasename Statements In JDBC gibt es drei Arten von Statements: Statement PreparedStatement CallableStatement Repräsentiert ein einfaches, allgemeines Statement Repräsentiert ein vorkompiliertes SQL Statement erlaubt den Zugriff auf stored procedures innerhalb der Datenbank selbst Um ein Statement auszuführen haben wir zwei Möglichkeiten: executeQuery executeUpdate Führt ein Query aus und gibt ein ResultSet zurück Für Queries bei denen kein Resultat erwartet wird (gibt nichts zurück) Wie ein einfaches Statement mit Statement gebildet wird zeigt das Beispiel von Abbildung 4.1 Die PreparedStatement Objekte machen grob gesagt das gleiche wie normale Statements, sie führen ein SQL Statement aus. PreparedStatement erlaubt aber ein SQL Statement vorzukompilieren und es mehrmals laufen zu lassen und dabei die Parameter wenn nötig anzupassen. Wie mit Statement kreiert man ein PreparedStatement Objekt von einem Connection Objekt, indem man die prepareStatement Methode von Connection benutzt. Da die Hauptidee von PreparedStatement darin besteht das Statement wiederholt mit anderen Parametern auszuführen, spezifizieren wir keine Werte im Statement sondern benutzten ein Fragezeichen (?) als Platzhalter und spezifizieren die Parameter vor dem Ausführen des Statements. Tabelle 4.2 zeigt die verschieden SQL Typen, den entsprechenden Java Typ und die setXXX Methoden zum setzen. Abbildung 4.2 zeigt ein Beispiel mit PreparedStatement. Resultate JDBC benutzt das java.sql.ResultSet Interface um das Query Resultat abzukappseln. Man kann ResultSet als ein Objekt betrachten, welches die darunterliegende Tabelle der Query Resultate representiert und indem man Methoden benutzt um durch die Reihen zu navigieren und einzelne Kolonnenwerte auszulesen. Wenn man mit einem ResultSet beginnt ist der Cursor vor der ersten Reihe positioniert. Mit der next() Methode des ResultSet Objekts bewegt man den Cursor um eine Zeile weiter. Den Wert eines Koloneneintrages in der entsprechen Zeile kriegt man mit einer getXXX("columnname") Methode, XXX steht dabei für den Typ der zurückgegeben wird (gleich wie in den setXXX Methoden in Tabelle 4.2). Der Cursor kann mit verschiedenen Methoden durch das ResultSet navigiert werden: • next() • previous() • absolute(int row) • relative(int row) • first() • last() 46 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN import java.sql.*; import java.lang.*; public class JDBCExample { public static void main(String[] args) { try { // load Microsoft SQL Server driver Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); } catch (ClassNotFoundException e) { System.out.println("Unable to load Driver Class"); return; } try { // connect to database, specifying database, username and password Connection con = DriverManager.getConnection( "jdbc:microsoft:sqlserver://grant.inf.ethz.ch:1433","user","pwd"); // create and execute SQL Statement PreparedStatement pstmt = con.prepareStatement( "INSERT INTO address (name,year,phone) VALUES (?,?,?)"); // clear old parameters pstmt.clearParameters(); // set parameters pstmt.setString(1, "Peter Meier"); pstmt.setInt(2, 1975); pstmt.setString(3, "01 304 45 67"); // execute the statement pstmt.executeUpdate(); // release databse resources and close connection pstmt.close(); con.close(); } catch (SQLExeption sqle) { System.out.println("SQL Exception: "+sqle.getMessage()); } } } Abbildung 4.2: PreparedStatement Beispiel einer Datenbankabfrage mit Java JDBC 4.4. CALL LEVEL INTERFACE SQL Daten Typ CHAR VARCHAR LONGVARCHAR NUMERIC DECIMAL BIT TINYINT SMALLINT INTEGER BIGINT REAL FLOAT DOUBLE BINARY VARBINARY LONGVARBINARY DATE TIME TIMESTAMP CLOB BLOB Java Typ String String String java.math.BigDecimal java.math.BigDecimal Boolean (boolean) Integer (byte) Integer (short) Integer (int) Long (long) Float (float) Double (double) Integer (byte) byte[] byte[] byte[] java.sql.Date java.sql.Time java.sql.Timestamp java.sql.Clob java.sql.Blob 47 setXXX Methode setString(int index, String x) setString(int index, String x) setString(int index, String x) setBigDecimal(int index, java.math.BigDecimal x) setBigDecimal(int index, java.math.BigDecimal x) setBoolean(int index, boolean x) setByte(int index, byte x) setShort(int index, short x) setInt(int index, int x) setLong(int index, long x) setFloat(int index, float x) setDouble(int index, double x) setByte(int index, byte x) setBytes(int index, byte[] x) setBytes(int index, byte[] x) setBytes(int index, byte[] x) setDate(int index, java.sql.Date x) setTime(int index, java.sql.Time x) setDate(int index, java.sql.Timestamp x) setClob(int i, java.sql.Clob x) setBlob(int i, java.sql.Blob x) Tabelle 4.2: SQl Datentypen, Java Typen und setXXX Methoden NULL Werte Manchmal enthalten Datenbankkolonnen null Werte. Auf Grund der Art der Implementierung diverser Datenbank APIs ist es für JDBC unmögliche eine Methode zur Verfügung zu stellen, die entscheidet ob eine Kolonne null ist oder nicht. Methoden die nicht ein Objekt einer bestimmten Sorte zurückgeben sind besonders anfällig. Die Methode getInt() gibt z.B. einen Wert 0 zurück, wenn das Resultat leer ist. Wie soll man jetzt aber entscheiden, ob der Wert einer Kolonne “0” ist oder eben vielleicht leer (“null”)? JDBC handhabt dieses Problem mit einer wasNull() Methode, welche angibt, ob von der zuletzt gelesenen Kolonne null gelesen wurde oder nicht. Beispiel 4.4: int numberInStock = res.getInt("STOCK"); if (rs.wasNull()) System.out.println(" Result was null"); else System.out.println("In Stock: "+ numberInStock); Als Alternative kann man getObject() verwenden und auf null testen2 . Beispiel 4.5: Object numberInStock = res.getObject("STOCK"); if (numberInStock == null) System.out.println(" Result was null"); 2 Einige Treiber unterstützen dieses Verhalten nicht ganz richtig 48 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN Datum und Zeit JDBC definiert drei Klassen für das Speichern von Datums und Zeit Informationen: java.sql.Date, java.sql.Time und java.sql.Timestamp. Diese korrespondieren zu den SQL Typen DATE, TIME und TIMESTAMP. Die java.util.Date Klasse passt zu keiner der SQL Typen, deshalb definiert JDBC eine neue Menge von wrapper -Klassen, die die Standard Klasse Date erweitern (oder limitieren). Der Syntax für Date, Time und Timestamp sind: java.sql.Date ‘yyyy-mm-dd’ java.sql.Time ‘hh:mm:ss’ java.sql.Timestamp ‘yyyy-mm-dd hh:mm:ss.ms.microseconds.ns’ Zusammenfassung Die Konzepte von ESQL und JDBC sind sich ähnlich. JDBC ist die state-of-art Datenbank Programmierung für Java. JDBC mit java.sql 3 und ihre Extension javax.sql 4 unterstützt noch viele weitere Konzepte und Methoden, wie Transaktionen, SQL Fehlerbehandlung, connection pooling usw., welche hier nicht behandelt werden sollen. Für mehr Infos siehe die JDBC Dokumentation von Sun [45] oder “Java Enterprise in a Nutshell” [23]. 4.5 4.5.1 Applikations Logik auf der Server Seite Stored Procedures Bis jetzt gingen wir davon aus, dass das Datenbankapplikationsprogramm auf dem Client ausgeführt wird. Obwohl dies für die meisten Anwendungen angebracht ist, ist es manchmal sinnvoll Datenbankmodule (Prozeduren oder Funktionen) zu kreieren, die im DBMS gespeichert sind und ausgeführt werden. Man nennt diese Module database stored procedure (obwohl es sich um Funktionen oder Prozeduren handeln kann). Im SQL Standard wird der Begriff persistent stored modules (PSM) für stored procedures benutzt, weil diese Programme, wie die Daten persistent im DBMS gespeichert sind. Stored procedures sind unter den folgenden Umständen sinnvoll: • Wenn ein Datenbankprogramm von verschiedenen Applikationen gebraucht wird, kann man sie auf dem Server speichern und von jeder Applikation ausführen. Das reduziert den Arbeitsaufwand für duplikaten Code und erhöht die Softwaremodularität. • Ein Programm serverseitig auszuführen kann den Datentransfer und somit in gewissen Situationen die Kosten für die Datenkommunikation zwischen Client und Server reduzieren. • Die Prozeduren können die Macht der Modellierung, wie sie von Views geboten werden, erweitern, indem sie komplexere Datentypen erlauben und für den Datenbankbenutzer zur Verfügung stellen. Zudem können komplexere Zwangsbedingungen (constraints) überprüft werden, als dies mit Trigger möglich ist. Im Allgemeinen erlauben viele Kommerzielle DBMSs das Schreiben von stored procedures in allgemeinen Programmiersprachen (general-purpose programming language). Als alternative können stored procedures aus einfachen SQL Kommandos bestehen. Verschiedene Hersteller haben ihre eigene Sprache zur Implementation (z.B PL/SQL (Programming Language/SQL) von Oracle, Transact-SQL von MS SQL-Server). Wie bereits erwähnt sind stored procedures in SQL-99 als SQL/PSM standardisiert. 3 Package Dokumentation: java.sun.com/j2se/1.4.2/docs/api/java/sql/package-summary.html 4 Package Dokumentation: java.sun.com/j2se/1.4.2/docs/api/javax/sql/package-summary.html 4.5. APPLIKATIONS LOGIK AUF DER SERVER SEITE 49 SQL/PSM SQL/PSM ist der Teil des SQL Standards, der spezifiziert wie persistent stored modules geschrieben werden. Er beinhaltet Statements zum kreieren von Funktionen und Prozeduren und zusätzliche Programmierkonstrukte um die Möglichkeiten von SQL so zu erweitern, dass Programmcode für Prozeduren und Funktionen geschrieben werden können. Prozeduren werden folgendermassen kreiert (Beispiel 4.6): CREATE PROCEDURE procedure name ( parameters ) local declaration procedure body ; Für Funktion gilt folgender Syntax (Beispiel 4.7): CREATE FUNCTION procedure name ( parameters ) RETURNS returntype local declaration function body ; SQL/PSM • IN • OUT • INOUT definiert drei Mode von Parametern: nur Input nur Output Input und Output IN ist der Default-Mode, wenn nichts deklariert wird. Parameter von Prozeduren können irgendeinen Mode haben, die Parameter von Funktionen aber nur den IN Mode. Beispiel 4.6: CREATE PROCEDURE MoveStudio(IN oldAddress VARCHAR(255), IN newAddress VARCHAR(255)) UPDATE Studius SET address = newAddress WHERE address = oldAddress; Beispiel 4.7: CREATE FUNCTION AverageMovieLengthOfStudio(IN name CHAR(30)) RETURNS INTEGER DECLARE result INTEGER; SELECT AVG(length) INTO result FROM Movies WHERE studio = name; RETURN result; Statements Prozeduraufrufe: CALL procedure name ( arguments ) Funktionen können nicht aufgerufen werden, sondern werden direkt als Teil einer expression ausgeführt. Der RETURN Ausdruck gibt einen Wert zurück, beendet aber nicht die Funktion. Der Rückgabewert kann sich nach dem Return Statement immer noch ändern. 50 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN Deklaration lokaler Variablen: DECLARE name type Assignments: SET variable = expression NULL ist ein zulässiger Wert für eine Zuordnung. Verzweigung: IF condition THEN statements ELSEIF condition THEN statements ELSE statements ENDIF Schleifen: Basic: LOOP statements END LOOP Der Loop kann mit LEAVE label verlassen werden (label identifiziert dabei den Loop der beendet werden soll). While: WHILE condition DO statements END WHILE Repeat: REPEAT statements UNTIL condition END REPEAT For: FOR name AS cursor CURSOr FOR query DO statements END FOR Exceptions: Exceptions werden mit DECLARE (CONTINUE | EXIT | UNDO) HANDLER FOR conditions statement deklariert. Die Kontollfluss Angaben bedeuten folgendes: CONTINUE springe zum Statement nach dem Statement, dass die Exception ausgelöst hat EXIT verlasse den compound (BEGIN statements END) UNDO wie EXIT mache aber alle Änderungen rückgängig Beispiel 4.8: 4.5. APPLIKATIONS LOGIK AUF DER SERVER SEITE 51 CREATE FUNCTION GetYear( in name CHAR(30), ) RETURNS INTEGER DECLARE NotFound CONDITION FOR SQLSTATE ’02000’; DECLARE Too Many CONDITION FOR SQLSTATE ’210001; BEGIN DECLARE EXIT HANDLER FOR NotFound, TooMany; RETURN (SELECT year FROM Movies WHERE title = name); END; Transaktionen Im generic SQL Interface ist ein Statement gleich einer Transaktion. In programmierbarem SQL machen mehrere Statements eine Transaktion, die von einer Applikation initiert werden kann. Transaktion können committed oder aborted werden. Um eine Transaktion zu initieren benutzt man START TRANSACTION Um eine Transaktion zu beenden, benutzt man entweder COMMIT wenn man die Transaktion bestätigen will, oder ROLLBACK um die ganze Transaktion bei einem Fehler rückgängig zu machen. Transaktion können verschiedene Zugriffsmode haben SET TRANSACTION READ ONLY SET TRANSACTION READ WRITE Verschiedene read-only Transaktionen können dabei parallel laufen. Für den Zugriff auf Daten definiert SQL vier Isolationslevel für Transaktionen: read uncommitted dirty reads sind möglich read committed read-only committed values repeatable read jedes lesen gibt den selben Wert serializable enspricht einer seriellen Ausführung 52 KAPITEL 4. SQL IN PROGRAMMIERUMGEBUNGEN Kapitel 5 Integrierung von Datenbanken und Programmierung 5.1 Motivation Für einige Applikationssyteme brauchen wir fortgeschrittene Datenbanksyteme mit Anforderungen die herkömmliche RDBMS (relationale Datenbank Management Systeme) nur schwer oder gar nicht erfüllen können. Beispiele sind: • Computer-Aided Design (CAD): Daten im Zusammenhang mit mechanischem und elektrischem Design haben eine grosse Anzahl verschiedener Typen die meist nur wenige Instanzen besitzen. Ein Design kann sehr gross sein und aus Tausende von Teilen und voneinander abhängigen Teildesigns zusammengesetzt sein. Zudem entwickelt sich ein Design im Verlauf der Zeit und die Änderungen müssen verbreitet werden. Für die einzelnen Komponenten eines Designs müssen evtl. noch Alternativen in Betracht gezogen werden. Vielleicht arbeiten hunderte von Mitarbeitern am selben Design und die gleichzeitige gemeinsame Arbeit an einem Design sollte von einer Applikation unterstützt werden. • Computer-Aided Manufacturing (CAM): Die Daten sind ähnlich zu den Daten im CAD. Dazu kommen noch Daten die im Zusammenhang mit der Produktion stehen. Applikationen zeigen den aktuellen Stand des Systems in der laufenden Produktion an. Andere Applikationen kontrollieren physikalische Prozesse und müssen in realtime reagiere. Die Applikationen können in einer Hierarchie organisiert und spezialisierte Regeln können mit Standardalgorithmen kombiniert sein. • Computer-Aided Software Design: Daten, die im Zusammenhang mit dem Softwareentwicklungs Zyklus stehen sind gespeichert (z.B. Planung, Anforderungen, Implementierung, Test, Dokumentation). Auch hier kann das Design extrem gross sein und wie bei anderen Designs muss die gemeinsame Arbeit mehrerer Benutzer am selben Projekt unterstützt sein. Die Abhängigkeiten zwischen verschieden Komponenten sollten festgehalten werden um bei Änderungen zu unterstützen. Die typischen Merkmale solcher Applikationen sind: • Verwaltung von grossen, komplexen Objekten • Unterstützung von Varianten, z.B. können für einen Knoten in einem Produktebaum mehrere Varianten möglich sein 53 54 KAPITEL 5. DATENBANKEN UND PROGRAMMIERUNG • komplexe Abhängigkeiten und Zwangsbedingungen, z.B. limitiert die Wahl einer Variante A in der Stufe k der Produktehierarchie die Auswahl in der Stufe n • Unterstützung von Design Prozessen wie z.B. Kooperation unter Teams, Versionen und lange Transaktionen Herkömmliche RDBMS weisen einige Schwächen auf: • Objekte der “realen” Welt können nur schlecht repräsentiert werden. • Relationen sind ein semantisch stark überladenes Konstrukt, alle unterschiedlichen Arten von Informationen (Datenobjekte, Abhängigkeiten, Spezialisierungen usw.) werden auf die selbe Art modelliert. • Relationen sind für homogene Daten designt, d.h. es wird angenommen, dass alle Daten die selbe Struktur haben. • Rekursive Queries bereiten Schwierigkeiten. • Der impedance mismatch macht das Mischen von Datenmodellen und Programmierparadigmen schwierig. Traditionelle Programmiersprachen bieten die Möglichkeit Daten zu manipulieren, deren Lebenszeit die Aktivität des Programms nicht überdauern. Wenn Daten die Aktivierungszeit eines Programms überdauern müssen wird ein File I/O- oder DBMS- Interface benutzt, d.h. Daten sind entweder short term Daten und werden mit den Möglichkeiten die die Programmiersprache bietet manipuliert oder sie sind long term Daten und werden von einem Filesystem oder DBMS gehandhabt. Das mapping zwischen den zwei Typen findet normalerweise einerseits im Filesystem oder DMBS statt und anderseits mit explizitem Übersetzungscode im Programm. Das es zwei verschiedene Arten gibt Daten zu betrachten hat einige Nachteile. Erstens ist in einem Programm ein beachtlicher Anteil von Code (typischerweise 30% [25]) für den Transfer von Daten von/zu Dateien in DBMS. Viel Platz und Zeit wird zudem benötigt um Daten vom Programmformat in das Format für die Langzeitspeicherung zu übersetzen. Der zweite grosse Nachteil ist, dass der Datentypenschutz, welcher die Programmiersprache bietet, oft durch das Mapping verloren geht. 5.2 Persistence in Programmiersprachen Für Applikationen wie die oben erwähnten, muss man die Programme mit den Eigenschaften von DBMS, wie Langlebigkeit der Daten ausstatten und auf grosse, umfangreiche Daten und Code konkurrierend zugreifen können. Das Ziel von persistent programming ist es die Konstruktion solcher Applikationen, oft auch Persistent Applications Systems (PASs) genannt1 , zu unterstützen [11]. Die Technologie die dem Bau von PASs zugrunde liegt, baut auf ganz ganz unterschiedlichen Mechanismen und philosophischen Annahmen auf. dazu gehören Betriebssysteme, Kommunikationssysteme, Datenbanksysteme, Benutzerinterface-Systeme, Kommandosprachen, Editoren, Dateisysteme, Compiler usw. Programmierer müssen die Variationen, wie verschiedene Typen, Bindingschema, Naming, verstehen und damit umgehen können. Die Datenbank und Programmiersprachen Gemeinschaften forschten und entwickelten unabhängig von einander Produkte, obwohl sie viele ähnliche Service anbieten müssen. Der Fokus der DB Gemeinschaft ist dabei auf die verlässliche Speicherung von grossen Datenmengen und die Unterstützung von effizienten Operationen auf diesen Daten gerichtet. Der Fokus der Programmiersprachen Gemeinschaft ist mehr darauf gerichtet dem Programmierer zu helfen präzisen verständlichen Code zu schreiben. Intellektuelle und softwaremässige Investitionen beider Gemeinschaften gehen gegen die Adaption von Ideen der anderen Seite. Separate Entwicklungen und konsequente Inkonsistenzen tendieren dazu sich festzusetzen und anzuwachsen. Es gibt aber auch Ideen und Ansätze die beiden zusammenzubringen, was uns zum persistent programming führt. 1 heute stösst man vermehrt auf den Begriff enterprise applications für solche Applikationen 5.2. PERSISTENCE IN PROGRAMMIERSPRACHEN 55 Wir definiere persistence als eine Eigenschaft von Daten, welche die Zeitperiode angibt in welcher Daten existieren und benutzbar sind. Man das Spektrum von persistence folgendermassen kategorisieren: 1. flüchtige (transient) Resultate in Berechnungen (expression evaluation). 2. lokale Variablen in Prozedur Aktivierungen. 3. Globale Variablen und Heapeinträge die auch ausserhalb ihrer Scope bestehen. 4. Daten welche für die ganze Programmdauer gelten. 5. Daten die über mehrere Ausführungen von mehreren Programmen gelten. 6. Daten die solange gelten, wie ein Programm benutzt wird 7. Daten die verschiedene Versionen eines Programms überdauern 8. Daten die verschiedene Versionen des Langzeitspeichersystems (persistent support system) überdauern. Die ersten vier Kategorien werden normalerweise von Programmiersprachen unterstützt, die anderen vier von File- und DBM Systemen. 5.2.1 Design Methoden M. P. Atkinson et al. stellen in [7] und [8], für einen ersten Versuch ein System zu produzieren welches persistence unterstützt, die Hypothese auf, dass es möglich sein muss Langlebigkeit mit einem Minimum an Änderungen an einer Sprache in die Sprache zu integrieren. Sie verwendeten dazu die Sprache S-algol (eine höhere Programmiersprache die an der University of St Andrews für Lehrzwecke entwickelt wurde). Zusätzlich zu der Hypothese erkannten sie noch einige Prinzipien für langlebige (persistent) Daten: 1. persitence independence: Die Persistence von Datenobjekten ist Unabhängig davon wie das Programm das Datenobjekt manipuliert. 2. Orthogonality: Allen Daten soll die volle Bandbreite an Persistence erlaubt sein und alle Daten werden unabhängig von Langlebigkeit, Grösse und Type uniform behandelt. 3. Identification: Die Wahl wie man persistence auf Sprachlevel bietet und identifiziert ist unabhängig von der Wahl der Daten Objekte in der Sprache. Es wurden verschiedene Methoden untersucht um Persistence von Daten zu identifizieren. Einige assoziieren Persitence mit den Variablennamen, andere mit dem Typ in der Deklaration. Unter dem Gesichtspunkt des Prinzips der Unabhängigkeit persitence independence sind diese Methoden nicht erlaubt. Die Wahl der Datenelemente welche die Lebenszeit eines Programms überdauern sollen ist ein nächster wichtiger Punkt. Viele moderne Sprachen haben Garbage Collection und die Erreichbarkeit von Objekten, wie in Garbage Collection, ist eine sinnvolle Wahl um persistent Objekte zu identifizieren. 5.2.2 Persistent Languages Es wurden verschiedene Persistent Languages entwickelt: • PS-algol (Atkinson et al.): Die Initial-Version wurde 1983 veröffentlicht [7][8]. PS-algol identifiziert Objekte mit der Erreichbarkeit, wie oben beschrieben. PS-Algol Beispiele zeigen Abbildung 5.1 und 5.2. Spätere Versionen erlaubten die Referenzierung von Prozeduren in Structures und machte sie so persistent. Es wurden einige Applikationen in PS-Algol programmiert: RAQUEL [32][19], eine relationale Algebra Sprache für Querying von relationales Datenbanken [17]; EFDM (Extended Functional Data Model ist eine Implementation des Funktionalen Daten Modells (FDM) wie in [42] beschrieben [17]; PROTEUS Knoten eines heterogenen verteilten Datenbanksystems. • Napier88 (Atkinson, Morris) [38] • PJama (Atkinson, Jordan) [12][26][27][47]: Persistent Java siehe Abschnitt 5.2.3 56 KAPITEL 5. DATENBANKEN UND PROGRAMMIERUNG structure person(string name, phone.no; pntr addr, other) structure address (int no; string street, town; pntr next.addr) let db = open.database("Address.list","mypass","write") if db is error.record then write "Can’t open database" else begin write "Name:" ; let this.name = read.a.line write "Phone number:" ; let this.phone = read.a.line write "House number:" ; let this.house ) readi write "Street:" ; let this.street = read.a.line write "Town:" ; let this.toen = read.a.line let p = person(this.name, this.phone, address(this.house, this.street, this.town, nil), nil) let addr.list = s.lookup("addr.list.by.name", db) s.enter(this.name, addr.list, p) commit end Abbildung 5.1: Ein PS-algol Programm um eine neue Person einer Adressliste in einer Datenbank hinzuzufügen • E (Carey) [41]: Effektiv ein persistent C++, das als Teil des Exodus Projekts an der University of Wisconsin entwickelt wurde. Das Projekt führte mit der Entwicklung eines Toolkit für die Entwicklung von DBMS das Konzept des “database engineer” ein. E führte persistence durch spezielle Datenbanktypen ein. Z.B. sind int und dint Basistypen. Benutzerdefinierte Typen werden als persistent types deklariert, wenn die Instanz persistence Möglichkeiten hat. • Galileo (Albano) [4][5] • Poly (Matthews) [35]2 • Amber (Cardelli) • BOYAZ (Zamulin) • Pascal/R (Schmidt): Beispiel einer Sprache die versucht eine allgemeine Programmiersprache so konsistent wie möglich mit einem Datenmodell zu kombinieren. Das Relationale Modell ist in Pascal durch die Identifikation von Tuples und Records einem Datenbank-Konstruktor und der Einführung von einem “Relation of” als Konstruktor ähnlich zum “Set of” integriert. Ein Pascal/R Beispiel zeigt Abbildung 5.3. Pascal/R kann mit verschiedenen Datenbanken des selben Datenbanktyp laufen. Relationen können Argumente von Prozeduren sein. Relational-Operatoren und ein “for each” Konstruktor um durch die Relationen zu iterieren3 wurden eingeführt. • Modula-R (Schmidt): Nachfolger von Pascal/R von Schmidt. • DBPL (Schmidt): Die Weiterführung von Pascal/R und Modula-R. • Tycoon/Tycoon 2 (Matthes, Schmidt) [33][34][36]: Neuste Sprache aus der Gruppe um Schmidt. • Persistent Prolog • Persistent Oberon • ...... 2 Weitere Dokumente unter http://www.lfcs.inf.ed.ac.uk/reports/95/ECS-LFCS-95335/index.html 3 ist für andere Strukturen wie files, array nicht verfügbar 5.2. PERSISTENCE IN PROGRAMMIERSPRACHEN 57 structure person(string name, phone.no; pntr addr, other) let db = open.database("Address.list","myotherpass","read") if db is error.record do {write "Can’t open database"; abort} let addr.list = s.lookup("addr.list.by.name", db) write "Name:"; let this.name = read.a.line let this.person 0 s.lookup(this.name, addr.list) if this.person = nil then write "Person not known" else write "Phone number is:"; this.person(phone.no) Abbildung 5.2: Ein PS-algol Programm das nach einer Telefonnummer einer Person in der Adressliste sucht {records destined to become tuple types} PartRec = record Pno: Pnum; Name: string end; {relation types} PartRel = relation <Pno> of PartRec; {database type} PartsDB = database Part: PartRel; .... end; Abbildung 5.3: Pascal/R Beispiel 58 KAPITEL 5. DATENBANKEN UND PROGRAMMIERUNG 5.2.3 Persistence in Java Wie wir bereits in Abschnitt 4.4.1 gesehen haben, kann man in Java mittels JDBC relativ konfortabel auf Datenbanken zugreifen. Wir haben aber ein grosses Impedance Mismatch (Abschnitt 4.2) Problem. Java bietet dem Programmierer ein einfaches aber mächtiges Object Model, ein strenges Typensystem (Strong Typing), automatisches Speicher Management und unterstützt Concurrency. Leider können aber diese Eigenschaften ausserhalb der einzelnen Ausführung der Java Virtual Machine nicht gehandhabt werden und Programmierer müssen sich explizit um die Speicherung des Applikationsstatus kümmern, z.B. mit Files Input/Outputs oder mit Datenbankverbindungen. Java bietet dabei mit Java Object Serialization (JOS) die Möglichkeit auch Java Objekte als bitestream in einen beliebigen Stream zu schreiben, also z.b. um Objekte in/von Files zuschreiben/lesen oder über eine Netzwerkverbindung zu senden/empfangen. JOS ist eine Art “lightweight persistence” und eigentlich der Standard persistence Mechanismus für die Java Plattform, er wird zum Beispiel im JavaBeansTM Framework intensiv genutzt. In JOS werden mit den Daten auch Typinformationen gespeichert, es muss der ganze Objektgraph gelesen und gleichzeitig geschrieben werden. Ein Problem das bei der Spezialisierung von Objekten beachtet werden muss, ist dass die Objektidentität nicht erhalten werden. Wird ein Objekt wieder zurück geladen, wird ein neues Objekt mit dem selben Typ und Status wie das Original kreiert. Werden also z.B. zwei Objekte serialisiert, die beide auf ein gemeinsames Objekt referenzieren, wird für beide Objekte das referenzierte Objekt serialisiert, beim Wiederherstellen referenzieren die beiden Objekte also nicht mehr auf das selbe sondern nur ein identisches Objekt. Ein weiterer Nachteil ist die Langsamkeit bei einer grossen Menge von Daten. Ausserdem ist es nicht zuverlässig, wenn das System während dem lesen oder schreiben crasht, ist das File verloren. Abbildung 5.4 zeigt ein Beispielcode der Spezialisierung zum Abspeichern eines Objekts in ein File. PJama Sich den Limitationen der traditionellen Methoden persitence zu erreichen bewusst, imitierte Sun Microsystems Laboratories in Zusammenarbeit mit der Persistence and Distribution Group um Malcolm Atkinson der Glasgow University das Forest Projekt um Java das Prinzip der orthogonal persistence [11] hinzuzufügen. Das Projekt PJama4 begann kurz nach dem ersten öffentlichen Release der Java Plattform im Mai 1995. PJama ist eine Prototyp Implementierung einer persistent Programmierumgebung für Java, welche orthogonal persistence bietet. Wie bereits im Abschnitt 5.2.1 erwähnt, ist orthogonal Persitence durch drei Prinzipien definiert: • Type Orthogonality : alle Daten unabhängig vom Typ können persistence gemacht werden. • Transitive Persistence: die Lebensdauer aller Objekte wird durch die Erreichbarkeit von einer bestimmten Menge von Root Objekten aus ermittelt. • Persistence Independence: es ist ununterscheidbar, ob ein Code auf transient oder persistent Daten operiert. PJama Applikationen werden mit einem store assoziiert, welcher dem Programmierer als eine Instanz der PJavaStore Klasse repräsentiert wird. Eine Applikation wird initiiert indem man den PJama Interpreter mit einem Store und einer aufzurufenden Klasse verbindet. Objekte kommen als Kandidaten für persitence in Frage, wenn sie direkt oder indirekt von explizit beim PJavaStore registrierten Root-Objekten erreichbar sind. Standardmässig werden beim erfolgreichen Beenden einer Applikation alle erreichbaren Objekte atomically und dauerhaft im Store gespeichert (updated). Applikationen die nicht regulär beendet werden, sondern eine exception werfen ändern nichts am stabilen store. Es ist möglich PJavaStore.stabilizeAll aufzurufen um explizit eine globale Stabilisierung hervorzurufen. Ein Beispiel wie mit PJama persistent Daten kreiert werden können zeigt Abbildung 5.5. Abbildung 5.6 zeigt einen Beispielcode, wie auf persistent objects zugegriffen werden kann. 4 Das Projekt hiess ursprünglich “PJava”, dieser Ausdruck ist aber heute für Personal JavaTM reserviert. 5.2. PERSISTENCE IN PROGRAMMIERSPRACHEN 59 include java.io.*; \\ Serializable class public class Person implements Serializable{ .... } public void savePerson(Person p, File f){ try{ // Serialize the Object (Person) ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); oos.writeObject(p); oos.close(); } catch (IOException e) { /* Handle input/ouput exception */} } public Person getPerson(File f){ try{ // read the Object (Person) from file ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f)); Object temp = ois.readObject(); ois.close(); return (Person)temp; } catch (IOException e) { /* Handle input/ouput exception */} catch (ClassNotFoundException cnfe) {/* readObject() cann throw this */} } Abbildung 5.4: Beispiel: Serialisierung von Objekten zur Speicherung in ein File 60 KAPITEL 5. DATENBANKEN UND PROGRAMMIERUNG public class Department{ .... public static void main(String[] args){ // PJama implicity starts a transaction when // methode main is activated Cource c = new Course("OODB"); Person p = new Person("Fred"); try{ // obtain a persistent store PJavaStore pjs = PJavaStore.getStore(); // make a persistent root pjs.newRott("OODB",c); } catch (PJSException e){/*handle store exception */} } // implicit commit } Abbildung 5.5: Kreieren einer persisten Datenstruktur mit PJama .... try{ PJavaStore pjs = PJavaStore.getStore(); Hastable courses = (Hashtable)pjs.getPRoot("courses"); } catch (PJSException e){/*handle store exception */} .... Course oodb = (Course) courses.get("Object Oriented Databases"); oodb.display(); .... Abbildung 5.6: Zugriff auf ein persistent object mit PJama Für die Typensicherheit werden die Klasseninformation der persistent Objekte im store persistent gespeichert. Von den benannten Root-Objekten wird auf die Objekte direkt oder indirekt zugegriffen und dann wird das Matching des erwarteten Typs auf den aktuellen Typ vorgenommen. PJama wurde ohne eine Änderung an der Java Sprache, den Java Klassen (core classes) oder am Java Compiler implementiert. Der persistent Mechanismus wird durch eine zusätzliche API, die hauptsächliche Methoden der PJavaStore Klasse enthält, angeboten. Ein für diesen Zweck gebauter store behält Java Objekte auf der Disk und managend space allocation, recovery und transactional updates. Die automatische Object-Fehlerbehandlung (im Falle eines Crash bevor die Applikation beendet ist), die “Persitence Ernennung” und wiederherstellbaren, transaktionale Operationen werden durch Modifizierung der Java virtual machine (JVM) erreicht. Die Hauptänderung besteht im hinzufügen eines Objekt Cache, der für den Rest der VM ziemlich ähnlich wie der (garbage collected) Heap der JVM aussieht. Neben der globalen Stabilisierung, ermöglicht PJama, verschiedene Transaktionsmodelle als Spezialisierung der TransactionShell Klasse. 5.2.4 ObjectStore OODBMS Die ObjectStore Produkte Familie5 umfasst die Produkte ObjectStore Personal Storage edition (PSE) Pro6 für grosse Singleuser Datenbanken und ObjectStore7 für High-Performance 5 www.objectstore.com 6 www.objectstore.com/products/pse pro/index.ssp 7 www.objectstore.com/products/objectstore/index.ssp 5.2. PERSISTENCE IN PROGRAMMIERSPRACHEN 61 Multiuser Datenbanken. ObjectStore PSE Pro für Java ist eine reine Java-basierte Objekt Datenbank für embedded database und mobile computing Applikationen [40]. PSE Pro unterstützt Queries und Indexing, sowie einen persistent Garbage Collector. PSE Pro benutzt die Einfachheit der Objekt Serialisation und die Zuverlässigkeit von DBMS zusammen mit einer in-memory-like Performance für den Zugriff auf persistent Objekte. Um persisten Objekte zu speichern und auf sie zuzugreifen müssen alle ObjectStore Applikationen folgende Grundoperationen implementieren: • Kreieren und join eine Session. Signatur:public static Session create(String host, Java.util.Properties properties ); Session session=Session.create(null,null); session.join(); • Kreiere oder öffne eine Datenbank. db=Database.create(dbName, ObjectStore.ALL_READ);, db=Database.open(sbName, ObjectStore.UPDATE); • Starte und commit (oder abort) eine Transaktion. Transaction tr=Transaction.begin(ObjectStore.READONLY);, tr.commit();, tr.abort(); • Kreiere oder hole eine Datenbank root. Persitence wird durch reachability erreicht. db.createRoot(ällUsers", allUsers=new OSHashMap());, allUsers = (Map) db.getRoot(ällUsers"); • Beende eine Session. session.terminate() Zusätzlich müssen noch die Klassen die man in der Datenbank speichern will modifizieren (oder annotate) damit sie persistent capable (oder persistent aware für Klassen die auf persistent Objekte Zugreifen müssen). Man macht dies indem man nach dem Kompilieren des Source Code einen Postprocessor über die Klassen laufen lässt: osjcfp -dest . -inplace *.class gibt die annotierten class-Files. Die CLASSPATH Umgebungsvariable muss dann noch angepasst werden, damit sie auf die annotierten class-Files zeigt. Persistent Capable Classes • java.lang.String • Wrapper Klassen vom java.lang Packet : Boolean, Byte, Character, Double, Float, Integer, Long, Short • Arrays von primitive types (integer,boolean,. . . ) und von jedem persistent fähigen Type. • Von der Applikation definierte persistent capable classes • Spezielle ObjectStore collections 62 KAPITEL 5. DATENBANKEN UND PROGRAMMIERUNG Kapitel 6 Objekt-Orientierte Datenbanken Traditionelle Datenmodelle und System, wie das relationale und hierarchische haben gewisse Mängel, wenn es darum geht komplexere Datenbank Applikation zu designen und implementieren, z.B. Datenbanken für engineering design und manufacturing (CAD/CAM und CIM1 , Geographische Informationssysteme (GIS) (siehe ??) und Multimedia. Diese neueren Applikationen haben Anforderungen und Charakteristiken die sich von denen traditioneller Business Applikationen unterscheiden, weil z.B. komplexere Strukturen für Objekte, länger andauernde Transaktionen, neue Datentypen für das Speichern von Bildern oder grossen Textelementen und die Notwendigkeit für applikations-spezifische nicht-standard Operationen. Object-orientierte Datenbanken Systeme (OODBMS) wurden vorgeschlagen um den Bedürfnisse dieser komplexeren Applikationen gerecht zu werden. Die Objekt-Orientierte Vorgehensweise offeriert die Flexibilität mit einigen dieser Anforderungen umzugehen ohne auf die in traditionellen DBMS vorhandene Datentypen und Query Sprachen begrenzt zu sein. Ein Hauptmerkmal von Objekt-Orientierten Datenbanken ist die Möglichkeiten die dem Entwickler gegeben wird, beides, die Struktur von komplexen Objekten und die Operationen die auf diesen Objekten ausgeführt werden können zu spezifizieren. Ein anderer Grund für die Entwicklung von Objekt-Orientierten Datenbanken besteht in der stark zu genommen Nutzung von Objekt-Orientierten Programmiersprachen für die Entwicklung von Applikationen. Datenbanken werden heute fundamentalen Komponenten in vielen Software Systemen und traditionelle Datenbanken waren mit Objekt-orientierten Programmiersprachen wie C++, SMALLTALK oder JAVA schwierig zu nutzen (siehe Kapitel 4). Objekt-Orientierte Datenbanken sind so designt, dass sie direkt, oder nahtlos, mit Software integriert werden können, die mit Objekt-Orientierten Programmiersprachen entwickelt wurden. Obwohl viele experimentelle Prototypen und kommerzielle OODBMS kreiert wurden, fanden sie bis jetzt noch nicht weite Verbreitung, wohl wegen der grossen Popularität der traditionellen relationalen oder objekt-relationalen2 Datenbank Systemen. 6.1 ODMG Als langsam kommerzielle OODBMSs erhältlich wurden, erkannte man schnell die Notwendigkeit für ein Standardmodell und eine Standardsprache um eine grösste mögliche Portability 1 Computer-Aided Design/Computer-Aided Manufacturing und Computer-Integrated Manufacturing 2 auf die Mischform in der viele objekt-orientierte Eigenschaften in traditionelle relationale DBMS integriert wurden, wird in diesem Skript nicht eingegangen. Die meisten Hersteller von relationalen DBMS erweiterten ihre Systeme zu objekt-relationalen Systemen 63 64 KAPITEL 6. OBJEKT-ORIENTIERTE DATENBANKEN und Interoperability für OODBM Systeme zu bieten. Portability ist generell, als die Möglichkeit ein bestimmtes Applikations Programm auf verschiedenen Systemen mit minimaler Modifikation des Programms selbst auszuführen, definiert. Das langjährige Fehlen eines Standard für OODBMSs hat wahrscheinliche viele potentielle Benutzer davon abgehalten auf diese neue Technologie umzusteigen. Ein Konsortium von OODBMS Herstellern und Nutzern, ODMG3 genannt, arbeitete deshalb einen Standard aus der als ODMG-93 (oder ODMG 1.0) Standard bekannt ist4 . Dieser wurde später zum ODMG 2.0 Standard überarbeitet. Der Standard besteht aus verschieden Teilen: • Dem Object Model (Abschnitt 6.1.1) • Der Object Definition Language (ODL) (Abschnitt 6.1.2) • Der Object Query Language (OQL) (Abschnitt 6.1.3) • und den Bindings zu objekt-orientierten Programmiersprachen (C++, SMALLTALK, JAVA) 6.1.1 ODMG Object Model Das ODMG Objekt Modell ist das Datenmodell auf dem die ODL und die OQL basieren. ODMG benutzt das OMG Objekt Modell als Basis für das ODMG Objekt Modell. Dieses Model bietet die Datentypen, Typen Konstruktors und andere Konzepte die in ODL benutzt werden können um ein Datenbankschema zu spezifizieren. Objects und Literals Objects und Literals sind die Basisbausteine des Objekt Modells. Der Hauptunterschied zwischen den zwei besteht darin, dass Objekte einen Object Identifier und einen State (oder aktueller Wert, Value) besitzen, während Literals nur ein Value aber keinen Object Identifier haben. In beiden Fällen kann der Value eine komplexe Struktur haben. Ein Literal ist im Grunde genommen ein konstanter Value, möglicherweise mit einer komplexen Struktur, der sich nicht ändert. Ein Objekt wird durch vier Charakteristiken beschrieben: • Object Identifier : Ein eindeutiger systemweiter Identifier (oder OBJECT_ID. • Name: Zusätzlich zur OBJECT_ID kann man Objekten optional einen innerhalb einer Datenbank eindeutigen Namen geben. Meist haben nicht alle Objekte einen Namen sondern hauptsächlich die welche Kollektionen von Objekten eines bestimmten Objekttypen enthalten. Diese Namen werden als entry points zur Datenbank benutzt. • lifetime: Die Lifetime eines Objekts spezifiziert ob ein Objekt ein persistent Object oder ein transient Object ist. • Structur : Die Struktur eines Objekts spezifiziert durch die Benutzung eines Konstruktors wie ein Objekt konstruiert wird. Sie spezifiziert ob ein Objekt atomic 5 oder collection objects sind. Es besteht ein grosser Unterschied zwischen atomic objects und atomic Literals. Im ODMG Model ist ein atomic object jedes Objekt, das nicht eine Collection ist. Im ODMG Model ist ein Literals ein Value der keinen Object Identifier hat. Trotzdem kann der Value eine einfache oder komplexe Struktur haben. Es gibt drei Arten von Literals: • atomic: Atomic Literals korrespondieren zu den Values von Basistypen und sind vordefiniert. Die Basistypen des Objekt Modells umfasst in ODL Notation unter anderen: – Integer Zahlentypen: long, short, unsigned long, unsigned short 3 Object Database Management Group (www.odmg.org) Standard wurde 1991 von Rick Catell von SunSoft initiiert und umfasst am Anfang 5 Leute der OODBMS Haupthändler 5 Im ODMG Model, korrespondieren atomic objects nicht zu Objekten deren Values Basistypen sind. Alle Basistypen (integer, reals, usw.) sind im ODMG Model Literals 4 der 6.1. ODMG 65 – Floating-Point Zahlen: float, double – boolean – Zeichen und Zeichenketten: char, string – Aufzählungstyp: enum • structured : Structured Literals komplexe Typen ähnlich den struct in Programmiersprachen (z.B. C). Als eingebaute Strukturen gibts: – date, interval, time und timestamp In ODL werden Structs mit dem Keyword Struct kreiert. • collection: Collection Literals spezifizieren ein Value das eine Kollektion von Objekten oder Values ist, aber selber keine OBJECT_ID haben. Die Collections im Objekt Model sind: – set<t>: Eine ungeordnete Menge von Objekten vom Typ t (unikate) – bag<t>: Eine ungeordnete Menge von Objekten vom Typ t (duplikate erlaubt) – list<t>: Eine Liste von Objekten mit Typ t in der die Reihenfolge wichtig ist (geordnet). – array<t>: Ähnlich einer Liste, nur hat ein Array eine fixe Länge. – dictionary<k,v>: Erlaubt die Kreation einer Kollektion von assoziierten Paaren in denen alle k (key) Values einzigartig (unique) sind. Atomic Objects werden in ODL mit dem Keyword class spezifiziert. Ein benutzerdefiniertes Objekt wird mit class definiert indem man die Eigenschaften (Properties) und die Operationen (Methods) spezifiziert. Die Properties definieren den State eines Objekts und werden weiter in attributes und relationships unterteilt. Ein Beispiel für zwei Objektdefinition Employee und Department zeigt Abbildung ??. Interfaces, Classes und Inheritance Im ODMG Objekt Model existieren zwei Konzepte um Objekttypen zu spezifizieren: Interfaces und Classes. Dazu gibt es auch zwei Typen von Inheritance Beziehungen. Der ODMG Terminologie folgend wird im folgenden das Wort Behavior für Operationen und das Wort State für die Eigenschaften (Properties, Attribute und Relationships) benutzt. Ein interface ist die Spezifikation des abstrakten Behavior eines Objekttypen. Obwohl ein Interface state Eigenschaften haben kann, können diese nicht vom Interface vererbt werden. Ausserdem ist ein Interface nicht instantiierbar (noninstantiable). Eine class spezifiziert sowohl das abstrakte Behavior als auch den abstrakten State eines Objekts und ist instantiierbar. Da Interfaces nicht instantiierbar sind, werden sie hauptsächlich benutzt um abstrakte Operationen die von classes oder Interfaces geerbt werden können zu spezifizieren. Man nennt dies Behavior Inheritance und es wird durch das Symbol “:” spezifiziert. Ein Beispiel zeigt Abbildung 6.2. Eine andere Inheritance Beziehung EXTENDS genannt und mit dem Keyword extends spezifiziert wird benutzt um sowohl Behavior als auch State unter Klassen zu vererben. In einem EXTENDS müssen sowohl der Supertyp als auch der Subtyp eine class sein (siehe Abbildung 6.2). Extents, Keys Im ODMG Objekt Modell kann ein Datenbank Designer ein extent für jeden Objekttypen der mit einer class Deklaration definiert ist deklarieren. Dem Extent wird ein Namen gegeben und er wird alle persistent Objekte dieser Klasse enthalten, d.h. er ist ein Set Objekt der alle persistent Objekte der Klasse enthält. Es ist zu Bemerken, dass in Programmiersprachen normalerweise keine extent Konzept vorkommt, man hat Typen, welche die Menge aller möglichen Werte definieren und Variablen welche zu individuellen Values gebunden sind. Manchmal wird der Begriff “activ domain” für extents benutzt. Eine Klasse mit einem Extent kann einen oder mehr keys haben. Ein key besteht aus einem oder mehr Properties, deren Values für jedes Objekt im extent einzigartig (unique) sein müssen. 66 KAPITEL 6. OBJEKT-ORIENTIERTE DATENBANKEN class Employee ( extent all_employees key ssn ) { attribute string name; attribute string ssn; attribute date birthdate; attribute enum Gender{M, F} sex; attribute short age; relationship Department works_for inverse Department::has_emps; void reassign_emp(in string new_dname) raises(dname_not_valid); }; class Department ( extent all_departments key dname. dnumber ) { attribute string dname; attribute string sdnumber; attribute struct Dept_Mgr{Employee manager, date startdate} mgr; attribute set<string> locations; attribute struct Projs{string projname, time weekly_hours} projs; relationship set<Employee> has_emps inverse Employee::works_for; void add_emp(in string new_ename) raises(ename_not_valid); void change_manager(in string new_mgr_name; in date startdate); }; Abbildung 6.1: Attribute, Relationships und Operationen in Klassendefinitionen 6.1. ODMG 67 interface HumanBeeing{ short age(); } class Person:HumanBeeing ( extent persons key ssn ) { attribute struct Pname{string fname, string mname, string lname} name; attribute string ssn; attribute date birthdate; attribute enum Gender{M, F} sex; attribute struct Address {short no, string street, short aptno, string city, short plz} address; }; class Student extends Person ( extent students ) { attribute string class; relationship Department majors_in inverse Department::has_majors; relationship set<Grade> completed_sections inverse Grade::student; void change_major(in string dname) raises(dname_not_valid); }; class Grade ( extent grades ) { attribute enum GradeValues{1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6} grade; relationship Student student inverse Student::completed_sections; }; class Department ( extent departments key dname ) { attribute string dname; attribute string dphone; relationship set<Student> has_majors inverse Student::majors_in; }; Abbildung 6.2: Beispiel eines kleinen ODL Schema mit den zwei verschiedenen Inheritance Beziehungen. Die graphische ODMG Notation dazu zeigt Abbildung 6.3 68 KAPITEL 6. OBJEKT-ORIENTIERTE DATENBANKEN Interface HumanBeeing HumanBeeing Class Person Person Interface (is−a) Inheritence (using ":") has_majors Department major_in Class Inheritance (using extends) Student completed_sections section 1:1 Grade 1:N M:N Relationships Abbildung 6.3: Eine Graphisches Scheme in ODMG Notation für das Beispiel aus Abbildung 6.2 Collections Die im ODMG Model eingebauten Collection (-Interfaces) haben wir bereits bei den Literal im Abschnitt 6.1.1 gesehen. Für die Interface Deklarationen (oder zumindest ein Teil davon) siehe die Vorlesungsfolien oder [22], Seiten 668-670. Hier nur einige Bemerkungen: • Die Containment Relation “Subset” ist nur für sets definiert. • Union, Intersection und Difference sind nur für Sets und Bags definiert. • Es gibt keine Constraints für Collections 6.1.2 Object Definition Language (ODL) Die ODMG Object Definition Language ODL basiert auf der OMG Interface Definition Language IDL. ODL ist designt um die semantischen Konstrukte des ODMG Objekt Modells zu unterstützen und ist unabhängig von irgendeiner bestimmten Programmiersprache. Ihr Hauptzweck ist es Objekt Spezifikationen zu kreieren. ODL ist nicht eine richtige Programmiersprache. Ein Benutzer kann ein Datenbankschema in ODL ganz unabhängig von einer Programmiersprache definieren und dann die spezifischen Sprach-Bindings benutzen um zu spezifizieren wie ODL Konstrukte auf Konstrukte in spezifischen Programmiersprachen, wie C++, SMALLTALK oder JAVA gemappt werden können. ODL haben wir bereits in den Beispielen in den Abbildungen 6.1 und 6.2 benutzt. 6.1.3 Object Query Language (OQL) Die Object Query Language OQL basiert auf dem ODMG Objekt Model und SQL-92. OQL ist wie SQL nicht compuational complete. Es gibt in OQL keinen expliziten Update Operator aber man kann Operationen auf Objekten aufrufen. Ein einfaches Beispiel zeigt Abbildung 6.4. Path Expressions, Results Wenn o ein Objekt bezeichnet, dann ist 6.2. JAVA DATA OBJECTS (JDO) 69 select c.address from Persons p, p.children c where p.address.street="Main Street" and count(p.child)>=2 and c.address.city !=p.address.city Abbildung 6.4: Eine einfache OQL Query select distinct s from Professors p, p.supervises s where p.dept.name="Computer Science" order by s.name Abbildung 6.5: Eine einfache OQL Query mit einem geordneten Set als Resultat • wenn p ein Attribute bezeichnet, o.p der Wert (Value) des Attributes in o • wenn p eine Relationship bezeichnet, o.p das Objekt oder die Kollektion von Objekten vewandt mit o von p • wenn p eine Methode bezeichnet, o.p(...) das Resultat von p in o ausgeführt (möglicherweise mit parameter. Ähnlich zu SQL werden normalerweise Duplikate in Kollektionen nicht eliminiert, d.h. es werden Bags als Resultate zurückgegeben. Um Duplikate zu eliminieren benutzt man das Keyword distinct. Ebenfalls kann man das Set mit order by ordnen (siehe Beispiel in Abbildung 6.5). Elemente in der select Klausel kann irgendeine Ausdruck sein, auch Ausdrucke mit Typconstructors (Abbildung ??). Komplexe Queries können gebildet werden, indem man SubQueries in der from Klausel integriert (Abbildung 6.7). 6.2 Java Data Objects (JDO) Die Abkürzung JDO steht für Java Data Objects, und die unter dem JSR-12 geführte Spezifikation beschreibt ein herstellerunabhängiges Framework zur persistenten Speicherung von Java-Objekten in transaktionalen Datenspeichern. Die Spezifikation wurde im Mai 2001 von bekannten Firmen wie Sun, IBM und Apple formuliert. JDO definiert eine einheitliche Schnittstelle für den Zugriff auf persistente Daten, wobei die physikalische Speicherung ziemlich egal ist. Die Objektinformationen können in Dateien, Datenbanken oder sonstigen Systemen abgespeichert werden. Mit Hilfe von JDO kann der Programmierer Datenobjekte ohne Kenntnis der Speichermechanismen bearbeiten. Dies ist für die Entwicklung grosser Systeme ein deutlicher Vorteil, denn die Entwickler müssen sich nicht näher mit den Interna von Datenbanken herum ärgern, sie können sich auf die reine Applikationslogik konzentrieren. Die einzelnen Hersteller, JDO-Vendor genannt, implementieren eine Speichermöglichkeit für ihr System[46]. select distinct Struct(proj1: p1, proj2: p2, leader:p1) from Projects p1, Projects p2 where p1.leader=p2.leader and p1.leader.worksfor="Computer Science" Abbildung 6.6: Eine OQL Query mit einem Typeconstructor in der select Klausel 70 KAPITEL 6. OBJEKT-ORIENTIERTE DATENBANKEN select distinct p.title from ( select s from student s where s.supervisedBy.name="John Smith" ) s1, s1.projects p Abbildung 6.7: Eine OQL Query mit Subquery Die Sun JDO Architektur bietet also Programmierern eine transparent java-zentrierte Sicht auf Persistente Informationen. Jede Klasse die durch eine JDO Implementationen gemanagt werden soll muss das PersistenceCapable Interface implementieren. Wie das gemacht wird ist durch den Hersteller, der eine JDO Implementation anbietet bestimmt. Es kann entweder durch einen pre-processor der Java Source Code generiert geschehen, oder durch mit einem post-processor der den Bytecode modifiziert. Ein Hersteller (Händler) bietet einen PersistentManager an, der der Kontaktpunkt zwischen der Applikation und der JDO Implementation darstellt. Ein Entwickler programmiert in Java und persistente und transiente Daten werden mehr oder weniger uniform gehandhabt. Wenn ein Hersteller einen Post-Processor benutzt um Klassen persistentfähig zu machen, dann schreibt und kompiliert man zuerst den Java Code, danach kreiert man eine XML JDO Descriptor File, welches die Klassen beschreibt, die persistent gemacht werden sollen. Danach lässt man den JDOEnhancer (Post-Processor) laufen, der das JDO Descripter File benutzt und setzt danach mit der Ausgabe des JDOEnhancer eine Datenbank auf (z.B. könnten das SQL Statements für Oracle sein). Teil III Architekturen und Technologien 71 Kapitel 7 Client/Server/Middleware Architekturen 7.1 Zentralisierte DBMS Architektur Architekturen für DBM-Systeme folgten ähnlichen Trends wie die Architekturen für allgemeine Computersysteme. Frühe Multiuser-Systeme benutzten Mainframe Computer für die Ausführung aller Funktionen des Systems und daran angeschlossene “dumme” Terminals , welche nur Anzeigemöglichkeiten und keine eigene Prozessorpower boten für die Benutzer. In solchen Teleprocessing Systemen wurden alle Prozess remotely auf dem Mainframe ausgeführt und nur Display Informationen und Steuerbefehle wurden an die Display-Terminals gesendet. Als die Preise für Hardware mit der Zeit sanken ersetzten viele Benutzer ihre Terminals durch PCs und Workstations. In Datenbanksystemen wurden diese Computer zuerst nachwievor wie die alten Displayterminals benutzt, so dass das DBMS selbst immer noch ein zentralisiertes System darstellte, in welchem alle DBMS Funktionalitäten, Applikationsprogramm Ausführungen und Benutzerschnittstellen Verarbeitungen auf einer Maschine ausgeführt wurden. Mit der Zeit begannen DBMS die vorhanden Prozessorpower auf der Benutzerseite zu nutzen und es entstanden die Client/Server Architektur. 7.2 Client/Server (File-Server) Architektur Die Client/Server Architektur wurde entwickelt um mit einer Computerumgebung, die aus einer grossen Anzahl PCs, Workstations, Fileserver, Drucker, Datenbankserver, Webserver und anderen Geräten die zu einem Netzwerk zusammengeschlossen sind umzugehen. Ein solches Netzwerk besteht aus spezialisiertem Server die eine bestimmte Ressource zur Verfügung stellen. Client Maschinen bieten dem Benutzer ein Interface um die Ressourcen solcher Server benutzen zu können, sowie lokale Prozessorpower um lokale Applikation laufen zu lassen. Ein Fileserver speichert z.B. Dateien einer Applikation und ist mit einer Datenbank verbunden. Auf den Client Maschinen liefen die Applikationen und DBMS und wenn nötig fordern die Workstations Dateien vom Server an. Der Fileserver dient also als gemeinsam genutzte Harddisk (“shared hard disk drive ”). Der Fileserver an sich besitzt kein DBMS welches direkt auf den Daten operiert, sondern liefert nur die angefragten Dateien an die Clients und diese operieren mit ihren DBMS auf den Daten. Ein solches Vorgehen kann starken Netzwerkverkehr hervorrufen, was zu Performance Problemen führen kann. Eine vollständiges DBMS ist auf jedem angeschlossenen Client nötig um Daten zu verarbeiten, die Concurrency, Recovery und Integrity Control wird dadurch komplex, weil mehrere DBMS gleichzeitig auf die selben Daten zugreifen können (wollen). Zwei Haupttypen von DBMS Architekturen sind auf diesem Client/Server Framework entstanden: two-tier und three-tier 73 74 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN 7.3 Two-Tier Client/Server Architektur SQL als Standardsprache für RDBMS ermöglichte eine klare logische Trennung von Client und Server Aufgaben. Die Query und Transaktion Funktionalitäten bleiben auf der Serverseite und je nach Anwendung befindet sich auf einem Client mehr (thick client) oder weniger (thin client) Logik, z.B. eine Applikation welche Daten weiterverarbeiten kann oder nur eine einfacher Browser, der die erhaltenen Resultate darstellt. Client und Server müssen nicht unbedingt örtlich getrennt sein, befinden sich aber meist auf verschiedenen Rechnern in einem Netzwerk. Wenn ein DBMS Zugriff nötig ist, baut die Applikation über ein Netzwerk oder IPS (Client, Server auf der selben Maschine) eine Verbindung zum DBMS (auf der Serverseite) auf und kann nach erfolgreicher Verbindung mit dem DBMS kommunizieren. Dazu verwendet eine Applikation spezielle Protokolle, ODBC, JDB oder herstellerspezifische wie Net8 bei Oracle, wie in Kapitel 4 auf Seite 39 beschrieben. Es gibt verschiedene mögliche Topologien: • single client, single server • multiple clients, single server • multiple clients, multiple servers Die Aufgaben in einer Two-Tier Architektur sind klar aufgeteilt: First Tier Tasks • Benutzerschnittstelle Client • Haupt Business und • Datenverarbeitungs Logik - verwaltet die Benutzerschnittstelle - akzeptiere und Checke die Syntax der Benutzereingabe - behandle die Applikationslogik - generiere Datenbankanfragen und sende sie dem Server - liefere die Antwort an den Benutzer zurück Second Tier Datenbank Server Tasks • serverseitige Validation • Datenbankzugriff - akzeptiere und verarbeite die Datenbankanfragen von Clients - checke Authorisierung - garantiere, dass Integrity Constraints nicht verletzt werden - führe Query/Update Prozessing durch und sende die Antwort zum Client - Unterhalte den System Katalog - Biete konkurenzierenden Datenbankzugriff an - Biete Recovery Control an Vor und Nachteile der Two-Tier Architektur • Vorteile – erhöht die Sicherheit durch die saubere Trennung von Client und Server Aufgaben und deren Adressräumen. – erhöht die Performance da gewisse Aufgaben parallel erledigt werden können und der Server auf Datenbankprozesse getunt werden kann. – reduziert die Kommunikationskosten, da nur Anfragen und selektierte Daten über das Netzwerk übertragen werden. – erlaubt erweiterter Zugriff auf existierende Datenbanken und mappt genügend gut auf offene System Architekturen 7.4. THREE-TIER CLIENT/SERVER ARCHITEKTUR 75 Database Server Monolithic ein DBS Prozess Multiple Server mehrere DBS Prozesse Symmetrisch ein DBS Prozess pro Applikationsprozess Asymmetrisch dynamische Allocation von DBS Prozessen zu Applikationsprozessen durch einen Dispatcher Abbildung 7.1: Klassifizierung von Server • Nachteile – limitiert in Bezug der Unternehmens Skalierbarkeit (enterprise scalability) Server in einer Client/Server Architektur können in Bezug auf Prozesse Klassifiziert werden (Abbildung 7.1): • Single Server Prozess – Ein Server Prozess für viele Clients – Synchronisierter Zugriff auf System Buffers und zentrale Systemtabellen – Server Multi-Threading (ablaufinvarianter code) – DBMS übernimmt die Aufgabe des Resource Managements und dupliziert OS Funktionalitäten, z.B. Sybase, MS SQL Server • Multiple Server Prozesse – Kommunikation über gemeinsamen Speicher – benutzt OS/Netzwerk Software für die Kommunikation zwischen Client/Server/Dispatcher Prozessen und das scheduling – Symmetrisch (Abbildung 7.2) oder Asymmetrisch (Abbildung 7.3) Das Auftauchen des World Wide Web änderte die Rolle von Client und Server was zur ThreeTier Architektur führte. 7.4 Three-Tier Client/Server Architektur Mitte der 1990er wurden Applikationen immer komplexer und konnten potentiell an hunderte oder sogar tausende von Endbenutzer verbreitet werden. Die traditionellen Two-Tier Architektur hatte zwei Probleme, welche wahre Skalierbarkeit verhinderte: • “thick clients” erforderten beträchtliche Ressourcen auf der Client Maschine • signifikanter Administrations Overhead auf der Client Seite 76 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN Clients Datenbank Instanz Servers Abbildung 7.2: Symmetrischer Multiple Server Dispatcher Clients Shared Server Datenbank Instanz Abbildung 7.3: Asymmetrischer Multiple Server 7.5. TRANSACTION PROCESSING MONITORS 77 Die Three-Tier Architektur fügt einen zusätzlichen Layer zwischen Client und Datenbankserver. Dieser Applikationsserver oder auch “Middleware” ist für die Business Logik und die Datenverarbeitung zuständig. Er akzeptiert Requests von den Clients, verarbeitet die Anfragen, sendet Datenbankkommandos an den Datenbankserver und agiert dann als Kanal um die verarbeiteten Daten vom Datenbankserver an den Client weiterzuleiten. Die Client besitzen eine einfache Benutzerschnittstelle zum Applikationsserver, z.B. ein normaler Web Browser. First Tier Tasks • Benutzerschnittstelle (GUI) Client • einfache Eingabe Validierung Second Tier Applikations Server “Middleware” Third Tier Datenbank Server Tasks • Business Logik • Datenverarbeitungs Logik Tasks • Daten Validierung • Datenbankzugriff Die Three-Architekur hat verschiedene Vorteile: - braucht weniger teure Hardware weil der Client “thin” ist. - Der Unterhalt der Applikation bleibt Zentral auf einem einzigen Server, was das Problem der Software Distribution des Two-Tier Modells beseitigt. - erhöhte Modularität macht die Ersetzung eines Tier ohne die anderen zu beeinflussen einfacher. - load balancing ist durch die Trennung von Business und Datenbank Funktionen einfacher. Zusammengefasst kann man sagen der middle Tier erhöht die Performance, Flexability, Maintainability, Reusability und Scalability durch zentralisierte Prozesslogik. zentralisierte Prozesslogik macht die Administration und Change Management einfacher indem System Funktionalitäten lokalisiert sind, damit Änderungen nur einmal vorgenommen und auf dem Middle Tier platziert werden müssen. Web Informations Systeme Die Three-Tier Architektur fügt sich natürlich in die Webumgebung ein. Das Modell kann auf weitere Tier erweitert werden, z.B. einen separaten Web- und Applikationsserver (Abbildung 7.4). Für Unternehmen reduzieren sich die Kosten in Bezug auf die benötigten Softwarelizenzen und die Distribution von Client Software durch die Tendenz in Richtung Web Browser als Client Software drastisch. 7.5 Transaction Processing Monitors Eine Transaction Processing (TP) Applikation ist ein Programm das eine administrative Funktion ausübt indem es für einen Online Benutzer auf eine shared Datenbank zugreift [13]. Ein TP System ist eine integrierte Menge von Produkten welche TP Applikationen unterstützen. Diese Produkte sind z.B. Hardware, wie Prozessor, Speicher, Disks und Kommunikations Controllers und Software, wie Betriebssystem (OS), DBMSs, Computernetzwerk und TP Monitore. Ein Grossteil der Integration dieser Produkte wird durch TP Monitors ermöglicht. Ein 78 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN HTTP Request Web Browser HTTP Server Application Server HTTP Response (HTTP + HTML) Internet Abbildung 7.4: Web Informations System Abbildung 7.5: Transaction Processing Monitor TP Monitor ist eine Middleware Komponente, welche den Zugriff auf die Service von verschiedenen Ressourcen Managern ermöglicht. Sie bieten ein uniformes Interface für Entwickler von Transaktions Software (Abbildung 7.5). Eine Transaktion ist eine Arbeitseinheit die genau einmal ausgeführt wird und ein permanentes Resultat liefert (siehe Kapitel 8.1). Eine Transaktion sollte folgende Eigenschaften haben: • serializable: eine System sollte aussehen als ob die Transaktionen seriell ausgeführt werden. • all-or-nothing: entweder wird eine Transaktion als ganzes ausgeführt oder sie hat keinen Effekt. • persistent: das Resultat einer Transaktion sollte resistent gegen Ausfälle sein. Der grösste Teil der Systemunterstützung für die oben genannten Anforderungen bietet das DBMS . Wenn von einer einzelnen Transaktion aber auf mehrere DBMS zugegriffen wird, braucht es eine zusätzliche Koordination der DBMS, was meist über TP Monitore (TPM) geschieht. Die Hauptaufgabe eines TPM ist es den Fluss der Transaktionsanfragen zwischen den Terminals welche Requests senden und den TP Applikationen, welche die Requests verarbeiten, zu koordinieren. Ein TPM kann folgende Aufgaben erfüllen: • Transaction Routing – leitet Transaktionen an ein spezifisches DBMS weiter und erhöht dadurch die Skalierbarkeit. • Distributed Transaction Management – Transaktionen können Zugriff auf verschiedene DBMS benötigen. 7.6. PEER-TO PEER SYSTEME 79 Abbildung 7.6: Peer-to Peer System • Load Balancing – Verteile die Anfragen über mehrere DBMS, indem der Client an das am wenigsten belastete DBMS weitergeleitet wird. Funneling – Etabliere Verbindungen zu den DBMS und trichtere (funnel) die Benutzeranfragen durch diese Verbindungen und reduziere dadurch die Anzahl der nötigen Verbindungen. • Increase Reliability – Wenn ein DBMS ausfällt, kann der TPM die Anfrage zu einem anderen DBMS weiterleiten oder die Transaktion zurückhalten bis das DBMS wieder verfügbar wird und dann erneut senden. 7.6 Peer-to Peer Systeme Abbildung 7.6 zeigt ein Peer-to Peer Netzwerk, indem sich Systeme in einer dynamischen Client/Server Beziehung miteinander verbinden können, um Service und Daten zu teilen. Die Peer-to Peer Architektur ist die allgemeinste Architekur. Jede Site kann als Server, der Teile einer Datenbank speichert, oder als Client, der Applikationen ausführt und Queries initiiert, agieren. 7.7 Verteilte Datenbanksysteme Forscher und Anwender sind schon seit den 1970er Jahren an Verteilten Datenbanksystemen (distributed database systems) interessiert. In dieser Zeit war der Schwerpunkt die Unterstützung von verteiltem Datenmanagement für grosse Firmen und Organisationen, die ihre Daten auf verschiedenen Büros oder Filialen verteilt hatten. Obwohl das Bedürfnis und viele gute Ideen und Prototypen vorhanden waren, waren die frühen verteilten Datenbanksysteme in einigen Aspekten ihrer Zeit voraus und waren nie kommerziell erfolgreiche. Die Fortschritte in der darunter liegenden Technologie, vor allem in der Kommunikationstechnik, machen verteilte Datenbanksysteme heute möglich und durch die veränderte Geschäftsanforderungen auch nötig. Besonders Firmen benötigen aus verschieden Gründen verteilte Datenbanken [28]: 80 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN • Kosten und Skalierbarkeit: Heute sind tausend PC Prozessoren günstiger und leistungsst rker als ein grosser Mainframe Computer. Es macht also ökonomisch Sinn Mainframe Server durch PC Clusters zu ersetzen. Ausserdem ist es schwierig und teuer einen Mainframe Computer aufzurüsten, wenn eine Firma wächst, aber ziemlich einfach neue PCs dem Netzwerk hinzuzufügen. • Integration von verschiedenen Software Modulen: Es stellt sich heraus das kein einzelnes Softwarepaket alle Anforderungen einer Firma erfüllen kann. Firmen müssen deshalb verschiedene Applikationen installieren, möglicherweise jede mit ihrer eigen Datenbank und das Resultat ist ein verteiltes Datenbanksystem. • Integration von bereits existierenden Datenbanken: Die Integration der älteren Systeme ist ein Beispiel dafür, wie Firmen dazu gezwungen werden auf verteilte Datenverarbeitung zu setzen, in denen die alten Systeme mit den neuen modernen Systemen koexistieren müssen. • Verteilte Applikationen: Es gibt eine Vielzahl neuer Applikationen die stark auf verteilter Datenbank Technologie basieren. Beispiele dafür sind computer-supported collaborative work (CSCW), Workflow Management, E-Commerce usw. • Druck des Marktes: Firmen werden gezwungen ihr Business neu zu organisieren und state-of-the-art Technologien zu verwenden um Konkurrenzfähig zu bleiben. Beispiele sind Service und Präsenz im Internet. Hinzu kommen die vermehrte Globalisierung und grosse Fusionen. 7.7.1 Zukünftige Trends Es zeichnen sich verschiedene Trends für verteilte Systeme ab: • Mobile Informations Systeme: Der Benutzer möchte beim Zugriff auf Informationen nicht an einen festen Standort gebunden sein: – Tourist Information Systeme – Kundendaten für einen Verkaufsagenten der Unterwegs ist – Notfall Service – Wissenschaftliche Feldarbeiten – ... Die Entwicklungen in Mobilen Geräten und Netzwerk Technologien machen Mobile Informations Systeme zu einer Option. • Information Environments Vielmehr als einen einzelnen point of access und einen einzelnen request/response Interaktions Modus zu bieten, reagieren Informationssystem auf Wechsel im Environment und liefern ihren Output über eine Vielzahl unterschiedlicher Output Kanäle. 7.8 Distributed Query Processing Man nimmt an, dass Benutzer und Applikationen ein Query veranlassen indem sie eine deklarative Sprache wie SQL oder OQL dafür verwenden und dabei nicht wissen wo und in welchem Format die Daten in einem verteilten Datenbanksystem abgelegt sind. Das Ziel ist es eine solche Query so effizient wie möglich auszuführen um die Wartezeit eines Benutzers oder einer Applikation so kurz wie möglich zu halten. Zu den üblichen Techniken der Query Optimierung in traditionellen DBMS kommen bei verteilten DBMS noch neue hinzu. 7.8. DISTRIBUTED QUERY PROCESSING 81 Query Parser Result internal repr. Query Rewriter internal repr. Query Optimizer plan Plan Refinement/ Code Gen. Catalog (Meta Data) exec plan Query Execution Engine Base Data Abbildung 7.7: Query Processing Phasen 7.8.1 Query Processing Abbildung 7.7 zeigt eine klassische Query Processing Architektur. Diese Architektur kann für alle Arten von DBMS benutzt werden. Der Query Prozessor erhält eine SQL oder OQL Query als Eingabe, übersetzt und optimiert dies Query in verschieden Phasen in einen ausführbaren Plan und führt diesen Plan aus um ein das Resultat der Query zu erhalten. Parser In der ersten Phase wird die Query geparst und und in eine interne Repräsentation übersetzt, die später von den anderen Phasen einfach weiterverarbeitet werden kann. Ein Parser für SQL oder OQL kann gleich wie für andere Sprachen entwickelt werden, siehe dazu [2]. Für verteilte System kann der selbe Parser wie für zentrale Systeme benutzt werden. Query Rewriter Ein Query Rewriter führt Optimierungen durch, die unabhängig vom physikalischen Zustand des Systems (z.B. Grösse der Tabellen, Ort von Tabellenkopien, Vorhandene Indizes, Maschinengeschwindigkeit, usw.) gut sind. Typische Transformationen sind das Eliminieren von redundanten Prädikaten, Vereinfachung von Ausdrücken und das Entflechten von Subqueries und Views. In einem verteilten System muss der Query Rewriter auch die Partition der Tabelle auswählen die zum Beantworten der Query in Betracht gezogen wird. Query Optimizer Die Komponente führt Optimierungen durch, welche vom physikalischen Zustand des Systems abhängig sind. Der Optimizer entscheidet welche Indizes verwendet werden sollen um die Query auszuführen, welche Methoden (Hashing oder Sortieren) benutzt werden um die Operation der Query (z.B. joins und group-bys) auszuführen und in welcher Reihenfolge die Operationen ausgeführt werden soll. Der Query Optimizer entscheidet auch wieviel Arbeitsspeicher für jede Operation alloziert werden soll. In einem verteilten System muss der Optimizer zusätzlich noch entscheiden an welchem Standort (site) jede Operation ausgeführt werden soll. Um dies zu entscheiden zählt der Optimizer alternative Pläne auf und wählt durch eine Kostenschätzung den besten Plan aus. Nahezu alle Query Optimizer basieren auf dynamischer Programmierung (siehe Unterabschnitt 7.8.2) um Pläne effizient aufzuzählen. Plan Ein Plan spezifiziert präzise, wie eine Query ausgeführt werden soll. Pläne werden in wahrscheinlich allen Datenbanksytemen als Bäume repräsentiert. Die Knoten der Bäume sind Operatoren und jeder Operator führt eine bestimmte Operation durch (z.B. join, group-by, sort, scan, usw.). An den Knoten eines Plans wird kommentiert wo der Operator ausgeführt wird. Abbildung 7.8 zeigt ein Beispiel für eine Query in welcher eine Tabelle A und eine Tabelle B benutzt werden, die sich an verschiedenen Standorten, Site 1 bzw. Site 2, befinden. Zum lesen von A wird ein Index benutzt, für B keiner. A und B werden an den Standort Site 0 gesendet und dort mit join zusammengefügt. Die send und receive Operatoren kapseln alle Kommunikationsaktivitäten ab, so dass alle anderen Operatoren wie in einem herkömmlichen zentralen System implementiert und benutzt werden können. Plan Refinement/Code Generation Diese Komponente transformiert den vom Optimizer produzierten Plan in einen ausführbaren Plan (z.B. einen assembler-ähnlichen Code um Evaluationen und Prädikate effizient auszuführen). 82 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN site 0 join receive site 1 receive site 2 send send idxscan(A) scan(B) Abbildung 7.8: Beispiel eines Query Evaluations Plans in einem verteilten DBMS Query Execution Engine Diese Komponente bietet eine generische Implementation aller Operatoren. State-of-the-art Query Execution Engines basieren auf einem Iterator Modell, d.h. Operatoren sind als Iteratoren implementiert und alle Iteratoren haben das selbe Interface, damit beliebige zwei Iteratoren mit einander verbunden werden können. Catalog Der Katalog speichert alle Informationen die für das Parsen, Rewriten und Optimieren gebraucht werden. Er unterhält das Schema der Datenbank (Definitionen der Tabellen, Views, Benutzerdefinierte Typen und Funktionen, Constraints usw.), das Partitioning Schema (Informationen darüber, welche globalen Tabellen aufgeteilt wurden und wie sie wieder rekonstruiert werden können) und physikalische Informationen wie den Standort der Kopien von partitionierten Tabellen, Informationen über Indizes und Statistiken die zur Kostenschätzung eines Plans benutzt werden. In einem verteilten System stellt sich die Frage wo dieser Katalog gespeichert werden soll. Die einfachste Möglichkeit besteht darin den Katalog an einem zentralen Standort zu speichern. In einem wide-area Netzwerk macht es Sinn, den Katalog an verschiedenen Standorten nachzubilden um Kommunikationskosten einzusparen. Es ist auch möglich die Katalog Informationen an Standorten im wide-area Netzwerk zu Cachen. Beide Varianten sind sehr effektiv, weil Kataloge normalerweise recht klein sind und Katalog Informationen in den meisten Umgebungen kaum verändert werden. Trotzdem kann in einigen wenigen Umgebungen der Katalog sehr gross werden und muss immer mal wieder geändert werden. In solchen Umgebungen macht es Sinn den Katalog aufzuteilen und die Katalogdaten dort abzuspeichern wo sie am meisten gebraucht werden. 7.8.2 Dynamische Programmierung für Query Optimierung Für die Auflistung der Pläne in der Query Optimierung wird in nahezu allen kommerziellen Datenbank Produkten das Dynamische Programmieren benutzt. Der Vorteil der Dynamischen Programmierung ist, dass sie den bestmöglichen Plan liefert, wenn das Kostenmodell genau genug ist. Der Nachteil dieses Algorithmus ist seine exponentielle Komplexität, was ihn für komplexe Queries untauglich macht. Speziell in verteilten Systemen ist die Komplexität des Algorithmus für viele Queries unerschwinglich. Als iterative dynamic programming Algorithmus 7.8. DISTRIBUTED QUERY PROCESSING 83 Input: SPJ Query q auf den Relationen R1 , R2 , . . . , Rn Output: Query Plan für q 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. for i = 1 to n do { optP lan({Ri }) = accessP lans(Ri ) pruneP lans(optP lan({Ri })) } for i = 2 to n do { for all S ⊆ {R1 , . . . , Rn } such that |S| = i do { optP lan(S) = ∅ for all O ⊂ S do { optP lan(S) = optP lan(S) ∪ joinP lans(optP lan(O), optP lan(S − O)) pruneP lans(optP lan(S)) } } } return optP lan({R1 , . . . , Rn }) Abbildung 7.9: Algorithmus der Dynamische Programmierung für Query Optimierung ist eine Erweiterung des dynamic programming Algorithmus bekannt[29], welcher für einfache Queries genau so gut funktioniert und für Queries die mit dynamischer Programmierung nicht gehandhabt werden können, gute Pläne liefert. Abbildung 7.9 zeigt den Algorithmus der dynamischen Programmierung für die Queryoptimierung. Er funktioniert bottom-up, indem er komplexere (Sub-) Pläne aus einfacheren (Sub-) Plänen bildet. Als erster Schritt bildet der Algorithmus einen access plan für jede Tabelle die in die Query involviert ist (Zeilen 1 bis 4). Wenn z.B. ein Replikat der Tabelle A in Site S1 und S2 vorkommt, listet der Algorithmus scan(A, S1 ) und scan(A, S2 ) als alternativen access plans auf. Im zweiten Schritt listet der Algorithmus alle two-way join plans auf indem er die access plans als Bausteine benutzt (Zeilen 5 bis 13). Als nächstes werden alle three-way join plans durch das Benutzen der two-way plans und der access plans als Bausteine. Der Algorithmus fährt so fort, bis er alle n-way join plans aufgelistet hat. Das schöne an diesem Algorithmus ist es, dass schwächere Pläne so früh wie möglich weggeworfen (pruned) werden können (Zeilen 3 und 10). Ein Plan kann weggeschnitten (prune) werden, wenn ein alternativer Plan existiert der die selbe oder mehr Arbeit mit geringeren Kosten erledigt. Pruning reduziert die Komplexität der Query Optimierung beachtlich, je früher eine Plan weggeschnitten werden kann um so besser, da keine komplexeren Pläne aus diesen gebildet werden. In verteilten System ist das Prunning aber schwieriger, es kann z.B. weder scan(A, S1 ) noch scan(A, S2 ) weggelassen werden, denn selbst wenn z.B. scan(A, S1 ) billiger ist als scan(A, S2 ) müssen beide behalten werden, den scan(A, S2 ) kann ein Baustein des besten Overall Plans sein, z.B. wenn das Resultat bei S2 präsentiert werden soll. Nur wenn die Kosten von scan(A, S1 ) plus die Kosten für das verschieben von A von S1 nach S2 billiger ist als scan(A, S2 ) kann scan(A, S1 ) wirklich weggeworfen werden. Im allgemeinen kann ein plan P1 weggeworfen werden, wenn ein Plan P2 existiert, der die selbe oder mehr Arbeit verrichtet und folgendes gilt: ∀i ∈ interestingSites(P1 ) : cost(ship(P1 , i)) ≥ cost(ship(P2 , i)) wobei interestingSites die Menge der sites bezeichnet, die potentiell in der Verarbeitung der Query involviert sind. 7.8.3 Kostenschätzung für Pläne Das klassische cost estimation Model besteht darin die Kosten für jede einzelne Operation des Plans zu schätzen und diese aufsummieren. Die Kosten eines Plans sind definiert als der totale Ressourcenverbrauch des Plans. In einem zentralisierten System bestehen die Kosten eines 84 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN Site 0 join receive Site 0 join join A Site 1 send Site 2 send join join join B C D Minimum Resource Comsumption receive A B C D Minimum Response Time Abbildung 7.10: Beispiele Pläne totale resource consumption vs. response time Operators aus den CPU Kosten plus Disk I/O Kosten. In verteilten System müssen zusätzlich noch die Kommunikationskosten berücksichtigt werden. Diese Kosten bestehen aus fixen Kosten pro Nachricht, per-byte Kosten für den Datentransfer und CPU Kosten um Nachrichten an der Sende- bzw Empfangs-Site zu verpacken bzw. entpacken. Die Kosten können gewichtet werden um den Einfluss von unterschiedlichen Geschwindigkeiten der verschieden Maschinen und Netzwerke zu modellieren. Ebenso müssen evtl. die Auslastungen (loads) der verschiedenen Maschinen berücksichtigt werden. Als Resultat wird der Optimizer Pläne, die Operationen auf schnellen, wenig belasteten Maschinen ausführen, bevorzugen und teure Kommunikations Links zu vermeiden versuchen. Das klassische Kosten Modell, das der totale Ressourcenverbrauch (resource consumption) einer Query schätzt, ist nützlich um den overall throughput eines Systems zu optimieren: wenn alle Queries so wenig Ressourcen wie möglich verwenden und stark belastete Maschinen vermeiden, dann können so viel Queries wie möglich parallel ausgeführt werden. Das klassische Modell berücksichtigt aber nicht intraquery Parallelität und somit findet ein Optimizer der auf diesem Modell basiert im Falle einer schnellen Kommunikation und wenig belastetet Maschinen nicht unbedingt den Plan mit der niedrigsten response Zeit für eine Query (Abbildung 7.10). 7.8.4 Query Execution Techniken In verteilten Systemen gibt es alternative Möglichkeiten Queries auszuführen. Um das Beste aus diesen Techniken herauszuholen, muss der Query Optimizer des Systems erweitert werden, um entscheiden zu könne ob und wie diese Techniken für eine Query eingesetzt werden. Anders gesagt bedeutet eine Integration dieser Techniken in ein verteiltes Datenbanksystem die Erweiterung der accessP lans und joinP lans Funktionen in einem dynamic-programming-based Optimizer, um alternative Plane aufzählen zu können, die diese Techniken nutzen. Ebenso müssen Kostenberechnungsformeln angegeben werden, damit Kosten und/oder Response Time von solchen Plänen geschätzt werden können. Row Blocking Kommunikation ist typischerweise mit send und receive Operatoren implementiert, die typischerweise auf TCP(IP, UDP oder einem anderen Netzwerkprotokoll basieren. Um die Zahl der Nachrichten und somit Overhead zu reduzieren, verwenden nahezu alle Datenbank Systeme eine row blocking genannte Technik. Die Idee besteht darin, Tuples, anstatt einzel, blockweise zu übertragen. Die Grösse der Blocks ist ein auf der Nachrichtengrösse des Netzwerks basierter Parameter der send und receive Operatoren. 7.8. DISTRIBUTED QUERY PROCESSING site 0 85 union receive site 1 receive site 2 send scan(A1) send scan(A2) receive site 1 send scan(A3) Abbildung 7.11: 1. Beispiel für Multithreaded Query Execution Der receive Operator besitzt ein Buffer Mechanismus und kann ein parent Operator mit Tuples versorgen auch wenn sich die Übertragung des nächsten Blocks von Tuples verzögert. Als ein Resultat ist es oft besser eine Blockgrösse zu wählen die grösser ist als die Nachrichtengrösse des Netzwerks. Multicast Optimierung Manchmal muss eine Site die selben Daten an verschiedene andere Sites senden um eine Query auszuführen. Wenn das Netzwerk kein effizientes Multicasting implementiert, ist es vielleicht besser Daten von der Site A zur Site B und dann von Site B zur Site C zusenden, anstatt von A nach B und C. Wenn z.B. Site A eine langsame CPU hat oder stark ausgelastet (load) ist, kann es ebenfalls günstiger sein, wenn C die Daten von B weitergeleitet bekommt. Im allgemeinen hat der Query Optimizer für unser Beispiel aus den folgenden drei Strategien die beste auszuwählen: • site A → site B und site A → site C • site A → site B und site B → site C • site A → site C und site C → site B Multithreaded Query Execution Um den besten Gewinn aus dem intraquery Parallelismus zu erhalten, ist es manchmal vorteilhaft an einer Site verschiedene Threads zu laufen zu lassen. Als Beispiel dient die Query A1 ∪ A2 ∪ A3 aus Abbildung 7.11. Wenn die union und receive Operatoren in Site 0 in einem einzelnen Thread ausgeführt werden, dann fordert Site 0 nur ein Block aufs Mal an (z.B. mit round-robin) und die Möglichkeit die drei Partitionen von Site 1,2 und 3 parallel zu senden ist nicht genutzt. Nur wenn die union und receive Operatoren in Site 0 in verschiedenen Threads laufen, können die drei receive Operatoren kontinuierlich die send Operatoren in Site 1,2 und 3 nach Tuples fragen, so dass alle drei send Operatoren parallel laufen und Tuples produzieren können. Separate Threads für jeden Query Operator zu etablieren ist aber nicht immer die beste Lösung. Erstens muss die shared-memory Kommunikation zwischen den Threads synchronisiert werden, was zusätzliche Kosten hervorruft und zweitens ist es nicht immer vorteilhaft alle Operationen zu Parallelisieren. Für die Query aus Abbildung 7.12 ist es z.b. nicht unbedingt sinnvoll die Tabellen A1 und A2 parallel zu erhalten uns zu sortieren. Wenn in Site 0 genügend Arbeitsspeicher vorhanden ist um grosse Teile von beiden Tabellen zu speichern, sollte die zwei Paare von receive und sort Operatoren durchaus parallel ausgeführt werden. Ist dies nicht der Fall sollten sie nacheinander ausgeführt werden, um einen Ressourcen “Streit” (Disk Trashing, wenn beide sorts gleichzeitig auf die selbe Disk schreiben möchten) in der Site 0 zu vermeiden. Der Query Optimizer und/oder zur Laufzeit ein Scheduler muss entscheiden welche Teile einer Query parallel und welche Operatoren deshalb im selben Thread laufen sollen. 86 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN site 0 merge−join sort receive sort receive site 2 site 1 send scan(A1) send scan(A2) Abbildung 7.12: 2. Beispiel für Multithreaded Query Execution Joins mit Horizontal Fragmentierten Daten1 Sind Tabellen horizontal Partitioniert machen es die logischen Eigenschaften der join und union Operatoren möglich Joins auf verschiedene Arten auszuführen. Wenn z.B. die Tabelle A horizontal in A1 und A2 partioniert ist, so dass gilt A = A1 ∪ A2 , dann kann A ./ B auf zwei Arten berechnet werden (A1 ∪ A2 ) ./ B oder (A1 ./ B) ∪ (A2 ./ B) Ist A sogar in mehr als zwei Partitionen fragmentiert oder ist B auch noch fragmentiert ergeben sich sogar noch mehr Varianten. Z.B. kann ((A1 ∪ A2 ) ./ B) ∪ (A2 ./ B) ein attraktiver Plan sein, wenn B vervielfältigt wurde und sich eine Kopie von B auf einer Site befindet, die nahe einer Site die A1 und A2 speichert liegt und sich eine andere Kopie in der Nahe der Site, welche A3 speichert befindet. Semijoins Semijoin Programme wurden als eine weiter Technik für die Verarbeitung von Joins zwischen Tabellen die an verschiedenen sites gespeichert sind vorgeschlagen. Wenn eine Tabelle A in Site 1 und eine Tabelle B in Site 2 gespeichert ist, ist die “konventionelle” Vorgehensweise A ./ B durchzuführen, die Tabelle A von Site 1 nach Site 2 zu senden und den Joint in site 2 durchzuführen (oder andersherum). Die Idee von einem Semijoin Programm ist es nun nur diese Kolonnen von A die für die Evaluierung der Join Prädikate gebraucht werden (S1 = π(A ∩ B)(A)) von Site 1 nach Site 2 zu senden, die Tuples von B welche den Join näher bestimmen (S2 = S1 ./ B) in Site 2 zu finden, diese zur Site 1 zu senden und dann A mit diesen Tuples von B in Site 1 zu matchen (S2 ./ A). Formal kann diese Prozedur folgendermassen geschrieben werden2 : A ./ B = A ./ (B n π(A)) Experimente haben gezeigt, dass Semijoin Programme für die Join Verarbeitung in verteilten Standard Datenbank Systemen typischerweise nicht besonders attraktiv sind, da die Kosten für den zusätzliche Overhead der Berechnungen, normalerweise höher sind als die eingesparten Kommunikationskosten. In Applikationen, die auf verschiedenen, grossen Tabellen mit sehr grossen Tuples operieren kann eine Semijoin Technik aber durchaus effizienter sein. 1 Horizontale Fragmentation (horizontal fragmentation) verteilt eine Relation auf Sets von Tuples (Zeilen), im Gegensatz zur vertikalen Fragmentation (vertical fragmentation), wo eine Relation auf Subrelationen verteilt wird, wobei jede Subrelation als ein Subset der Kolonnen der Originalrelation definiert ist. 2 n ist der Semijoin Operator und π(A) projiziert die Joinkolonnen von A aus. 7.8. DISTRIBUTED QUERY PROCESSING 7.8.5 87 Ausnützen der Client Ressourcen Bei einem Client-Server System, indem die Datenbank persistently von einer Server Maschine gespeichert wird und Queries von einem Client initiiert werden, stellt sich die Frage, ob die Query zu den Daten gesendet werden soll (Ausführung auf dem Server), oder ob die Daten zur Query verschoben werden sollen (Ausführung auf dem Client). Ein andere verwandte Frage ist, ob und wie caching (z.B. um temporär Kopien der Daten auf dem Client zu speichern) benutzt werden soll. Query Shipping Query Shipping wird heute in den meisten Relationalen und Objekt-relationalen DBMS benutzt. Das Prinzip besteht darin, eine Query auf dem Server auszuführen. In einem System mit mehreren Servern funktioniert Query shipping nur mit einem middle-tier, der die Joins zwischen auf verschiedenen Servern gespeicherten Tabellen vornimmt. Data Shipping Data Shipping wird in vielen Objekt-Orientierten DBMS benutzt und ist genau das Gegenteil des Query Shipping. Queries werden auf der Client Maschine, welche die Query initiiert hat ausgeführt und Daten werden rigoros im Arbeitsspeicher oder auf der Disk der Client Maschine zwischengespeichert (cached). Hybrid Shipping Die Vorteile von beiden Vorgehen können in einer Hybrid Shipping Architektur kombiniert werden. Hybrid Shipping bietet die Flexibilität eine Query auf der Client oder der Server Maschine auszuführen und erlaubt das Caching von Daten durch die Clients. Das Cachen von Daten auf dem Client ist besonders günstig, wenn viel auf den selben Datenobjekt operiert wird. Performance Trade-Offs Query Shipping erfüllt gute Arbeit, wenn die Server Maschinen Leistungsstark und die Client eher langsam sind. Auf der anderen Seite bringt Query Shipping keine gute Leistung, wenn viele Clients vorhanden sind, da die Server potentielle Flaschenhälse im System darstellen. Data Shipping bringt gute Leistungen, weil die Client Maschinen ausgenutzt werde, kann aber der Grund für hohe Kommunikationskosten sein, wenn das Caching nicht effektiv ist und eine grosse Menge ungefilterter Basisdaten zu den Clients gesendet werden muss. Offensichtlich hat Hybrid Shipping das Potential mindestens eine ebenso gute Performance zu erzielen, wie die beste Performance von Data Shipping und Query Shipping, indem Caching und Client Ressourcen wie beim Data Shiping ausgenutzt werden wenn dies förderlich ist oder sonst wie beim Query Shipping vorgegangen wird. Der Preis für diese Flexibilität, ist die nicht grössere Komplexität der Query Optimierung in einem solchen Hybrid Shipping System, denn der Optimizer muss mehr Optionen berücksichtigen. Experimente zeigten drei weniger offensichtliche Effekte für Hybrid Shipping Systeme: • Manchmal ist es in einem hybrid Shipping System besser Daten von den Serverplatten zu lesen, selbst wenn die Daten auf dem Client im Cache liegen. Nehmen wir als Beispiel eine Join Query von zwei Tabellen die auf zwei verschiedenen Servern liegen und nehmen wir an, dass diese Tabellen ebenso auf der Disk des Client temporär gespeichert sind (cached) und das Netzwerk schnell ist. In diesem Fall kann die Beste Methode die Query auszuführen sein, beide Tabellen von den Servern zu lesen und den join auf dem Client auszuführen. Auf diese Art kommen sich das Lesen der Daten von der Server Disk und die Join-Bearbeitung auf der Disk des Clients nicht in die Quere. • Wenn die Daten im Arbeitsspeicher des Client gespeichert (cached) sind, das Netzwerk schnell ist aber die Query am effizientesten auf dem Server durchgeführt werden kann, kann es besser sein die Basisdaten aus dem Cache oder Zwischenresultate vom Client an den Server zurückzusenden. • Transaktionen, welche viele kurze Update Operationen beinhalten, sollten am besten auf dem Client ausgeführt und dabei die neuen Versionen der Tuples in den Cache geschrieben werden. Der Vorteil besteht darin, dass solche Transaktionen auf dem Client wieder rückgängig gemacht werden können ohne dass der Server dadurch beeinflusst wird und die Updates können in einem Batch, mit wenig Overhead, an den Server gesendet werden. 88 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN display update Binäre Operatoren z.B. join Unäre Operatoren z.B. sort, group-by scan Data Shipping Client Client Konsument Query Shipping Client Server Produzent des linken oder rechten Inputs d.h. Client Konsument Produzent Client Server Hybrid Shipping Client Client oder Server Konsument oder Produzent des linken oder rechten Inputs Konsument oder Produzent Client oder Server Tabelle 7.1: Site Selection für Client-Server oder Peer-to-Peer 7.8.6 Query Optimierung Site Selektion Von der Perspektive eines Query Optimizers können Data Shipping, Query Shipping und Hybrid Shipping als Optionen für die Site Selektion modelliert werden. Jeder Operator eines Plans hat eine site annotation, welche angibt wo der Operator ausgeführt werden soll. Tabelle 7.1 zeigt mögliche site annotations für verschieden Klassen von Query Operatoren und die drei alternativen Vorgehensweisen. Eine Client Site Annotation gibt an, dass der Operator vom Client der die Query erstellt hat ausgeführt werden soll. Eine Consumer (Producer) Annotation gibt an, dass der Operator an der selben Maschine ausgeführt werden soll, die auch den Operator der das Resultat (Input) des Operators weiterverarbeitet ausführt. Eine Server Annotation für einen scan gibt an, dass der scan auf einem der Server, die eine Kopie der gescannten Daten speichern, ausgeführt werden soll, während eine Server Annotation für einen update angibt, dass die Operation auf allen Servern, die eine Kopie der betroffenen Daten speichern, durchgeführt werden soll. Wo und Wann Optimieren? Es gibt zwei Fragen von speziellem Interesse für die Query Optimierung in einer Client-Server Umgebung. Die erste Frage ist, wo das eine Query optimiert werden sollte. Das Vorgehen, gewisse Schritte der Query-Verarbeitung auf dem Client (wo die Query herkommt) selber durchzuführen, während andere auf dem Server durchgeführt werden sollten, macht Sinn, da gewisse Operationen wie Parsing und Query Rewriting sehr gut auf dem Client selber ausgeführt werden können ohne das der Server gestört wird, während aber andere Schritte, wie Query Optimization, eine gute Kenntnis des aktuelle Systemstatus benötigen und deshalb dem Server überlassen werden sollten. In einem System mit vielen Servern hat keine einzelner Server die komplette Kenntnis des ganze Systems. In einem solchen System, muss ein Server die Query Optimierung übernehmen. Dieser Server muss entweder den Zustand des Netzwerkes und der anderen Servern auf Basis von statistischen Erfahrungswerten schätzen oder aber versuchen durch Abfragen der aktuellen Zustande der Server herauszufinden. Die zweite Frage ist, wann eine Query optimiert werden soll. Diese Frage stellt sich für so genannte canned Queries, die Teil eines Applikationsprogrammes sind und während der Ausführung der Applikation evaluiert werden. Die traditionelle Vorgehensweise kompiliert und optimiert eine solche Query zur Compilezeit des Applikationsprogrammes, speichert sie die Pläne für die Queries in der Datenbank und lädt und führt diese Pläne immer aus, wenn die Applikation ausgeführt wird. Offensichtlich kann diese Methode nicht auf Änderungen wie Serverauslastungsverschiebungen reagieren und der kompilierte Plan zeigt in vielen Situationen eine schlechte Performance. Eine andere Idee besteht darin zu Compilezeit mehrere alternative Pläne und/oder Subpläne zu generieren und zur Ausführungszeit einen auszuwählen, der am besten für den aktuellen Zustand des Systems passt. Sogar noch dynamischere Methoden optimieren Queries on the fly. Die Idee besteht darin die Ausführung eines kompilierten oder dynamisch ausgewählten Plans zu starten und zu überwachen, ob die Zwischenergebnisse mit der erwarteten Rate produziert und ausgeliefert wurden. Werden die Erwartungen nicht erreicht, wird die Ausführung gestoppt, Zwischenergebnisse werden materialisiert und der Optimizer wird aufgerufen um einen neuen Plan für den verbleibenden Teil der Query zu finden. Two-Step Optimization Two-step Optimierung funktioniert folgendermassen: 7.8. DISTRIBUTED QUERY PROCESSING display display display join join join join A join join B 89 C (a) Two−Step Plan zur Kompilezeit D A join join B C (b) Two−Step Plan zur Laufzeit D A join D C B (c) Optimaler Plan Abbildung 7.13: Erhöhte Kommunikationskosten durch Two-Step Optimierung 1. Zur Compilezeit: Generiere einen Plan der die Join Reihenfolge, Join Methoden und die Zugriffspfade spezifiziert. 2. Bevor die Query ausgeführt wird: Transformiere den Plan und führe die Selektion der Site aus (d.h. entscheide wo jeder Operator verarbeitet wird). Two-step Optimierung hat eine angemessene Komplexität, weil beide Schritte mit akzeptablen Aufwand ausgeführt werden können. Two-Step Optimierung ist nützlich um die Auslastung in verteilten System aus zu balancieren, weil die Ausführung von Operationen in stark belastet Sites, durch die Site Selektion zur Laufzeit vermieden werden können. Dies Optimierungsmethode ist ebenso nützlich um das Caching in Hybrid Shipping System auszunutzen, da Query Operatoren dynamisch auf einem Client platziert werden können, wenn dieser die Daten im Cache hat. Ein Nachteil ist, dass Two-Step Optimierung in Plänen mit unnötig hohen Kommunikationskosten resultieren. Abbildung 7.13 zeigt warum. der Plan in (a) zeigt die Join Reihenfolge die im ersten Schritt bestimmt wurde, (b) zeigt das Resultat der Site Selektionierung im zweiten Schritt (Ausführungszeit) und (c) zeigt den optimalen Plan. die Färbung in (b) und (c) geben dabei die Site Annotation an (gleiche Färbung = gleiche Site). der zweite durch two-step Optimierung erhaltene Plan hat höhere Kommunikationskosten als der optimale Plan, weil der erste Schritt den Datenort und den Einfluss der Join Reihenfolge auf die Kommunikationskosten in einem verteilten System ignorierte. 7.8.7 Query Ausführung Ein Thema das in Hybrid Shipping System auftaucht, ist wie man mit Transaktion umgehen soll, die zuerst Daten im Client Cache updaten und dann eine Query auf einem Server ausführen in der diese Daten involviert sind. Z.B. wird der Lohn eines Angestellten, sagen wir von Hr. Meier, zuerst erhöht und dann nach dem Durchschnittslohn aller Angestellten gefragt. Der Update wird wahrscheinlich auf dem Client der die Transaktion startete ausgeführt (siehe 7.8.5). Auf der anderen Seite wird der Optimizer wahrscheinlich entscheiden die zweite Query auf dem Server der die Angestellten-Tabelle speichert auszuführen, um nicht die ganze Tabelle zum Client schicken zu müssen. Der Punkt ist, dass die Berechnung des durchschnittlichen Lohnes, den neuen Lohn von Hr. Meier berücksichtigen muss, welcher dem Client nicht aber dem server bekannt ist. Es gibt dafür zwei möglich Lösungen: • Propagiere kurz vor der Ausführung der Query auf dem Server alle relevanten Updates an den Server. • Führe die Query auf dem Server durch und justiere das Resultat auf dem Client so, dass die Änderungen miteinbezogen werden. In beiden Fällen involviert das Ausführen der Query auf dem server zusätzliche Kosten, die von einem dynamischen oder Two-Step Optimizer bei der Entscheidung, ob es billiger ist eine Query auf dem Client oder der Server auszuführen, in Betracht gezogen werden muss. 90 KAPITEL 7. CLIENT/SERVER/MIDDLEWARE ARCHITEKTUREN Ziel Granularität Speichermedium Auswirkungen auf den Katalog Update protocol Löschen der Kopie Mechanismus Replication Server grob (ganze Tabellen, Indizies,. . . ) typ. Disk Ja Propagierung explizit separates Holen Caching Client oder Middle-Tier fein (individuelle Seiten von Tabellen) typ. Memory Nein Invalidierung implizit Fehlende Daten und Behalten der Kopien nach Gebrauch Tabelle 7.2: Unterschiede zwischen Replication und Caching 7.8.8 Dynamische Datenplatzierung In den vorherigen Abschnitten haben wir uns mit den Strategien befasst, mit gegebener Query und den gegeben Orten mit Kopien der Daten und anderen Parametern einen besten (billigsten) Ausführungsplan zu finden. Jetzt befassen wir und mit Frage, wo die Kopien der Daten in einem verteilten System platziert werden sollten, so dass das der gesamte Query workload auf die günstigste und schnellst mögliche Art ausgeführt werden kann. Traditionell wurden Daten statisch platziert (static data placement), d.h. ein System Administrator entschied wo Kopien der Daten gespeichert werden, indem er spekuliert was für Arten von Queries ausgeführt werden könnten und dementsprechend die Orte auswählte. Static data placement hat verschiedene Nachteile: 1. Der Query workload ist oft nicht vorhersehbar 2. Selbst, wenn der workload vorhergesagt werden kann, wird er möglicherweise ändern und er kann so schnell ändern, dass der Systemadministrator die Datenplatzierung nicht schnell genug anpassen kann. 3. Die Komplexität eines genügend genauen Modells für statische datenplatzierung ist zu gross (Das Problem ist NP-complete) Eine andere Möglichkeit ist die dynamische Datenplatzierung, die Statistiken über Query Workloads behält und automatisch Daten verschiebt und Kopien der Daten in verschiedenen Sites anlegt um die Datenplatzierung dem aktuellen Workload anzupassen. Replication vs. Caching Im Prinzip gibt es zwei verschieden Mechanismen Kopien von Daten auf verschiedenen Sites anzulegen: Replication und Caching. Obwohl das Ziel das selbe ist (etablieren von Kopien der Daten an verschiedenen Orten, um die Kommunikationskosten zu reduzieren und/oder die Auslastung des Systems zu balancieren), gibt es einige Unterschiede. Tabelle 7.2 listet diese Unterschiede auf. Kapitel 8 Transaktionsmodelle und Systeme 8.1 Transaktionen und Concurrency Eine Transaktion ist ein ausführendes Programm das eine logische Einheit der Datenbankverarbeitung bildet. Eine Transaktion beinhaltet eine oder mehr Datenbankzugriffs Operationen, die eine Datenbank von aktuellen konsistenten Datenbankzustand in einen neuen konsistenten Zustand überführt. Eine Transaktion muss entweder vollständig erfolgreich beendet werden (commit) und die Änderungen permanent gemacht werden, oder sie beendet unvollständig (abort) und die bisherigen Effekte müssen rückgängig gemacht werden. Transaktionen die von verschiedenen Benutzern gestartet wurden, laufen in einem Multiuser System unter Umständen “parallel” in Konkurrenz zu einander ab (concurrently) und greifen vielleicht auf die selben Datenelemente zu. Wenn diese konkurrierende Ausführung von Transaktionen unkontrolliert stattfindet, kann es zu Problemen, wie inkonsistenten Datenbanken führen. 8.1.1 Warum Concurrency Control? Es können verschiedene Probleme auftauen wenn Transaktion konkurrieren zu einander ablaufen. Die verschiedenen Probleme werden anhand der zwei Transaktionen T1 und T2 die Geld von einem Konto A zu einem Konto B bzw. von B nach C überweisen (Abbildung 8.1) und einer dritten Transaktion die Kontostand aller Konten zusammenzählt aufgezeigt. • Lost Update Problem: Dieses Problem taucht auf, wenn zwei Transaktionen, die auf die selben Datenbankelemente zugreifen ihre Operationen so in einer wechselseitigen Reihenfolge ausführen, dass der Wert gewisser Datenbankelemente unkorrekt wird. (Abbildung 8.2) • Dirty Read (Temporary Update) Problem: Dieses Problem taucht auf, wenn eine Transaktion ein Datenbankelement updated und dann die Transaktion aus irgendeinem Grund scheitert. Auf das geänderte Datenelement wird von einer anderen Transaktion zugegriffen bevor die Änderung zurückgenommen wurde (rollback ) (Abbildung 8.3). • Incorrect Summary Problem: Wenn eine Transaktion eine Summary Funktion auf einer Anzahl Records berechnet (in unserem Beispiel Transaktion T3 ), während andere Transaktionen einige dieser Record Updaten, kann es sein, dass die Aggregatfunktion gewisse Werte bevor sie geändert und gewisse Werte nach dem sie geändert wurden berechnet (Abbildung 8.4). 91 92 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME Transaktion T1 : begin read A.balance read B.balance A.balance:=A.balance-50 B.balance:=B.balance+50 write A.balance write B.balance end Transaktion T2 : begin read B.balance read C.balance B.balance:=B.balance-100 C.balance:=C.balance+100 write B.balance write C.balance end Transaktion T3 : begin total:=0 read A.balance total:= total+A.balance read B.balance total:= total+B.balance read C.balance total:= total+C.balance print "total =", total end Kontostand: Initial T1 T2 T1 T2 A 100 50 50 B 200 150 150 C 50 150 150 Total 350 350 350 Abbildung 8.1: Beispiel Transaktion die Geld zwischen Bankkonten überweisen und den Totalfund berechnen 8.1. TRANSAKTIONEN UND CONCURRENCY Zeit 1 2 3 4 5 6 7 8 9 10 11 12 Transaktion T1 T1 T1 T2 T2 T1 T1 T2 T2 T1 T2 T2 Action read A.balance read B.balance A.balance:=A.balance-50 read B.balance read C.balance B.balance:=B.balance+50 write A.balance B.balance:=B.balance-100 C.balance:=C.balance+100 write B.balance write B.balance write C.balance 93 † Update von T1 verloren Kontostand: Initial T1 T2 T1 T2 hier A 100 50 50 50 B 200 150 150 100 C 50 150 150 150 Total 350 350 350 300 Abbildung 8.2: Transaktion Schedule mit Lost Update Problem 94 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME T2 abort Zeit 1 2 3 4 5 6 7 8 9 10 11 12 Transaktion T1 T2 T2 T2 T2 T2 T1 T1 T2 T1 T1 T1 Action read A.balance read B.balance read C.balance B.balance:=B.balance-100 C.balance:=C.balance+100 write B.balance read B.balance A.balance:=A.balance-50 write C.balance B.balance:=B.balance+50 write A.balance write B.balance † B stimmt nicht mehr Kontostand: Initial T1 T2 T1 T2 hier A 100 50 50 50 B 200 150 150 150 C 50 150 150 50 Total 350 350 350 200 Abbildung 8.3: Transaktion Schedule mit Dirty Read Problem Im folgenden sind nur die read und write Operationen sind aufgeführt. Zeit 1 2 3 4 5 6 7 Transaktion T3 T1 T1 T1 T1 T3 T3 Action read A.balance read A.balance read B.balance write A.balance write B.balance write B.balance write C.balance † Update von A Kontostand: Initial T1 T3 berechnet A 100 50 100 B 200 250 250 C 50 50 50 Total 350 350 400 Abbildung 8.4: Transaktion Schedule mit Incorrect Sumary Problem 8.2. CONCURRENCY CONTROL TECHNIKEN 8.1.2 95 Transaktions Eigenschaften Transaktionen sollten folgende Eigenschaften, oft ACID Properties aufweisen: 1. Atomicity: Eine Transaktion ist ein atomare Verarbeitungseinheit; sie wird entweder als Ganzes oder gar nicht ausgeführt. all-or-nothing Mechanismus: Commit/Abort Transaction 2. Consistency Preservation: Eine Transaktion ist consitency preserving, wenn ihre volle Ausführung die Datenbank von einem konsistenten Zustand in einen anderen überführt. integrity constraints satisfied Mechanismus: Transaction Programmer/TP System 3. Isolation: ein Transaktion sollte erscheinen als ob sie isoliert von anderen Transaktionen ausgeführt würde. D.h. die Ausführung einer Transaktion sollte nicht mit irgendwelchen anderen konkurenzierend ausgeführten Transaktione interferieren. serialisability Mechanismus: Locking 4. Durability oder permanency: Die Änderungen die eine Commited Transaktion in einer Datenbank vorgenommen hat, müssen in der Datenbank persistent sein. Dies Änderungen dürfen durch keine Art von Ausfällen verloren gehen. failure recovery Mechanismus: log-based Recovery 8.1.3 Serialisierbarkeit Eine Schedule S für n Transaktionen ist serialisierbar (serializable), wenn er equivalent zu einem serial schedule der selben n Transaktionen ist. Ein Schedule ist serial, wenn die n Transaktionen nacheinander (in irgendeiner Reihenfolge) ausgeführt werden, ohne dass die Operationen verschiedener Transaktionen mit einander interferieren (also jede Transaktion ist vollständig (commit oder abort), bevor die nächste beginnt). Mit folgendem Algorithmus, der einen precedence Graph (oder serialization Graph) konstruiert, kann man einen Schedule S mit n Transaktion Ti auf Serialisierbarkeit überprüfen (Abbildung 8.5 zeigt ein Beispiel): 1. Kreiere für jede Transaktion Ti die am Schedule beteiligt ist einen mit Ti bezeichneten Knoten in Graphen. 2. Für jeden Fall in S indem Tj ein read_item(X ) ausführt nachdem eine Transaktion Ti eine write_item(X ) ausführt, kreiere eine Kante (Ti → Tj ) im Graph. 3. Für jeden Fall in S indem Tj ein write_item(X ) ausführt nachdem eine Transaktion Ti eine read_item(X ) ausführt, kreiere eine Kante (Ti → Tj ) im Graph. 4. Für jeden Fall in S indem Tj ein write_item(X ) ausführt nachdem eine Transaktion Ti eine write_item(X ) ausführt, kreiere eine Kante (Ti → Tj ) im Graph. 5. Der Schedule S ist Serialisierbar, wenn und nur wenn der Graph keine Zyklen hat. 8.2 8.2.1 Concurrency Control Techniken Locks Wir haben die folgenden Shared/Exclusiv (oder Read/Write) Locking Operationen: • read_lock(X ): read-locked Elemente werden oft auch share-locked genannt. Anderen Transaktionen ist es erlaubt das gelockte Datenbankelement X zu lesen, nicht aber zu Beschreiben • write_lock(X ): write-locked Elemente werden auch exclusive-locked genannt. Eine Transaktion hält exklusiv den Lock auf das Datenbankelemnt X , keiner anderen Transaktion ist es erlaubt das Element zu Lesen oder Beschreiben. • unlock(X ): Unlock das Datenbankelement X Eine Transaktion muss einen Lock für ein Element beantragen, bevor sie auf das Element zugreifen kann. Wir kommen auf das Locking später noch mal zurück (8.3). 96 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME Schedule: T1:read_item(X), T1:write_item(X), T2:read_item(X), T3:read_item(Y), T2:read_item(Y), T2:write_item(Y), T3:read_item(Z), T3:read_item(Z), T1:read_item(Z) X T1 Z T2 Y T3 Keine Zyklen ⇒ Serialisierbar Serialization Order : T3 T1 T 2 Abbildung 8.5: Serialisierbarkeit eines Schedule 8.2.2 Two Phase Locking (2PL) Eine Transaktion folgt dem two-phase locking Protokoll, wenn alle Locking Operationen (read_lock, write_lock) vor der ersten Unlock Operation kommen. D.h. wenn einmal eine Lock von einer Transaktion freigegeben wurde, können keine weite Locks auf Datenelemente beantragt werden. strict 2PL Wenn eine Transaktione keine seiner exklusive-Locks freigibt bevor die Transaktion durch ein commit oder abort beendet ist, spricht man von strict two phase locking Obwohl kann gezeigt werden kann, dass wenn jede Transaktion in einem Schedule dem Two Phase Locking Protokoll folgt, der Schedule garantiert serializable ist, erlaubt das 2PL nicht alle möglichen serialisierbaren Schedules (d.h. gewisse serialisierbare Schedule werden durch das 2PL nicht erlaubt sein). Beide 2PL Techniken können zu zwei Problemen führen: Deadlocks und Starvation (siehe ??). Locking kann vom Programmierer versteckt werden, indem ein Datenmanager die read oder write Locks vor der Verarbeitung auf Daten setzt, die Transaktionen selber aber nur read und write Operationen ausstellt. Eine Transaktion muss nur die Transaktion durch ein Anfang und ein Ende bestimmen und der Datenmanager (data manager ) macht den Rest. Aus Performancegründen kann dem Programmierer gewisse Kontrolle über das Setzen und Freigeben von Lock übergeben werden. 8.2.3 Timestamp Ordering Ein Timestamp ist ein eindeutiger vom DBMS kreierter Identifier um Transaktionen zu identifizieren. Typischerweise werden Timestamp Wert ein der Reihenfolge in der die Transaktionen beim DMBS eingereicht werden zugeordnet und kann als eine Art Startzeit betrachtet werden. Concurrency Control Techniken, die auf dem Timestamp Ordering basieren benutzten keine Locks und sind deshalb Deadlockfrei. Timestamps können als einfacher Counter der für jede Transaktionen inkrementiert implementierte werden, oder sie benutzen die aktuelle Zeit und das Datum um einen Timestamp zu kreieren und stellen sicher, dass keine zwei Timestamp Werte zum selben Clock tick generiert werden. 8.2. CONCURRENCY CONTROL TECHNIKEN 97 Timestamp Ordering Algorithmus Die Idee für dieses Schema ist es, die Transaktionen basierend auf ihrem Timestamp zu ordnen. Ein Schedule mit diesen Transaktionen ist dadurch serialisierbar und die äquivalente Schedule hat die Transaktionen in der Reihenfolge deren Timestamp Werte. Der Algorithmus muss sicher stellen, dass für jedes Element, auf das mit sich widersprechenden Operationen im Schedule zugegriffen wird, die Reihenfolge in der die Transaktionen auf das Element zugreifen, nicht die serialisierbare Ordnung verletzt. Dazu verwendet der Algorithmus für jedes Datenbankelement X zwei Timestamp (TS) Werte: 1. read_TS(X ): Der Read Timestamp von Element X ; dies ist der grösste Timestamp unter allen Timestamps von Transaktionen, die erfolgreich das Element X gelesen haben, read_TS(X )=TS(T ), wobei TS(T ) den Timestamp der jüngsten Transaktion, die X erfolgreich gelesen hat bezeichnet. 2. write_TS(X ): Der Write Timestamp von Element X ; dies ist der grösste Timestamp unter allen Timestamps von Transaktionen, die erfolgreich das Element X beschrieben haben, write_TS(X )=TS(T ), wobei TS(T ) den Timestamp der jüngsten Transaktion, die X erfolgreich beschrieben hat bezeichnet. Der Concurrency Control Algorithmus muss in den folgen zwei Fällen überprüfen, ob zwei Konflikt-Operationen das Timestamp Ordering verletzen: 1. Transaktion T erteilt eine write_item(X ) Operation: (a) Wenn read_TS(X )>TS(T ) oder wenn write_TS(X )>TS(T ), dann abort und rollback T und verwerfe die Operation. Dies sollte gemacht werden, weil eine jüngere Transaktion mit einem Timestamp grösser als T bereits das Element gelesen oder beschrieben hat, bevor T die Chance hatte auf X zu schreiben. Dadurch wird das Timestamp Ordering verletzt. (b) Tritt die Bedingung in (a) nicht auf, dann führe die Operation write_item(X ) durch und setzte write_TS(X )=TS(T ) 2. Transaktion T erteilt eine read_item(X ) Operation: (a) Wenn write_TS(X )>TS(T ), dann abort und rollback T und verwerfe die Operation. Dies sollte gemacht werden, weil eine jüngere Transaktion mit einem Timestamp grösser als T bereits das Element beschrieben hat, bevor T die Chance hatte auf X zu schreiben. Dadurch wird das Timestamp Ordering ebenfalls verletzt. (b) Wenn write_TS(X )≤TS(T ), dann führe die Operation read_item(X ) durch und setzte read_TS(X ) zu dem grösseren der beiden Werte (TS(T ) oder read_TS(X )). Also immer wenn der TO Algorithmus zwei Konflikt-Operationen die in der falschen Reihenfolge auftauchen detektiert wird die spätere der zwei Operationen durch den Abbruch der Transaktion (abort) verworfen. Die verworfene Transaktionen wird dann neu an das System übergeben und bekommt einen neuen Timestamp. 8.2.4 Multiversion Concurrency Control Techniken Gewisse Concurrency Conrol Protokolle behalten die alten Werte von Daten wenn das Element updated wird, solche sind als Multiversion Concurrency Control bekannt. Wenn eine Transaktionen einen Zugriff auf ein Element möchte, wird wenn mögliche eine passende Version gewählt um die Serialisierbarkeit der aktuellen Schedule aufrecht zu erhalten. Die Idee besteht darin gewisse read Operationen, die in anderen Techniken (z.B. Timestamp Ordering) abgebrochen würden, trotzdem zu akzeptieren indem sie alte Versionen lesen können. Ein Nachteil von Mutiversion Techniken ist offensichtlich der grössere Speicherbedarf, weil alte Versionen gespeichert werden müssen. Jedoch müssen alte Versionen vielleicht sowieso gehandhabt werden, z.B. für ein Recovery. 98 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME Mutiversion Timestamping Verschieden Versionen X1 , X2 , . . . , Xk des Datenbankelements X werden gehandhabt. Für jede Version werden der Wert von Version Xi sowie folgende zwei Timestamps behalten: 1. read_TS(Xi ): Der Read Timestamp von Xi ist der grösste Timestamp unter allen Timestamps von Transaktionen, die erfolgreich die Version Xi gelesen haben. 2. write_TS(Xi ): Der Write Timestamp von X ist der grösste Timestamp unter allen Timestamps von Transaktionen, die erfolgreich den Wert der Version Xi geschrieben haben. Wann immer einer Transaktion erlaubt ist eine write_item(X ) Operation auszuführen, wird eine neue Version Xk+1 von X kreiert, mit write_TS(Xk+1 ) und read_TS(Xk+1 ) auf TS(T ) gesetzt. Wenn einer Transaktion erlaubt ist den Wert einer Version Xi zu lesen, wird der Wert von read_TS(Xi ) auf den grösseren Timestamp Wert von dem aktuellen read_TS(Xi ) und TS(T ) gesetzt. Um die Serialisierbarkeit zu gewährleisten werden folgende Regeln benutzt: 1. Wenn Transaktion T eine write_item(X ) Operation erteilt und Version i von X den höchsten write_TS(Xi ) von allen Versionen von X und dieser kleiner oder gleich TS(T ) und read_TS(Xi )>TS(T ) ist, dann abort und rollback die Transaktion T ; Andererseits, wenn TS(T )=write_TS(Xi ) überschreibe den Wert von Version Xi , sonst kreiere eine neue Version Xj von X mit read_TS(Xj )=write_TS(Xj )=TS(T ). 2. Wenn Transaktion T eine read_item(X ) Operation erteilt, finde die Version i von X , die den höchsten write_TS(Xi ) aller Versionen von X hat, aber kleiner oder gleich TS(T ) ist; Dann gib den Wert von Xi an die Transaktion T zurück und setze den Wert von read_TS(Xi ) auf den grösser Wert von TS(T ) und den aktuellen read_TS(Xi ). Fall zwei ist wie man sieht immer erfolgreich, weil die passende Version Xi , basierend auf dem write_TS der verschiedenen existierenden Versionen von X gelesen wird. Beachte, dass wenn T zurück gerollt wird kann ein cascading rollback eintreten. Deshalb sollte, um Recoverabilty zu gewährleisten, einer Transaktion nicht erlaubt sein zu commiten, bevor nicht alle Transaktionen, die irgendwelche Versionen die T gelesen hat geschrieben haben committed haben. 8.3 Locking Implementation Locking kann einen bedeutenden Effekt auf die Performance in Transaktions Prozessing Systemen haben. Viel System offerieren deshalb Tuning Mechansimen an um die Performance zu optimieren. Einige dieser Optimierungsmöglichkeiten können so weit gehen, dass sie die Korrektheit verletzen. Es ist deshalb wichtig Locking zu verstehen um richtig einschätzen zu können wann solche Optimierungen gemacht werden können, und was für Alternativen dazu existieren. Das Implementieren von Locking umfasst drei Aspekte: • Das Setzen und Freigeben von Locks • Das Implementieren eines Lock Managers • Der Umgang mit Deadlocks 8.3.1 Lock Manager Ein Lock Manager kann grundsätzlich drei Operationen ausführen: 1. Lock(transaction-id, data-item, lock-mode ): setzt für eine Transaktion transactionid einen Lock mit dem Modus lock-mode (read, write) auf das Datenbankelement dataitem. 8.4. MULTIGRANULARITY LOCKING Daten Element X Y Z 99 Locks Hold Pending Lock Requests [tid1, read][tid2, read] [tid2, write] [tid,read] [tid3, write] [tid4, read][tid1,read] Abbildung 8.6: Lock Tabelle 2. Unlock(transaction-id, data-iteme ): gibt den Lock von Transaktion transactionid auf das Datenbankelement data-item frei. item Unlock(transaction-id ): gibt alle Locks der Transaktion transaction-id frei. Die Locks können z.B. in einer Locktabelle im Arbeitsspeicher gespeichert werden, jeder Eintrag in der Tabelle beschreibt einen Lock auf ein Datenbankelement (siehe Abbildung 8.6). Da im allgemeinen zur selben Zeit nur immer eine kleine Anzahl von Elementen gelockt sind, kann zu Speicherung eine Hashtabelle mit dem Identifier der Datenelemente als Key benutzt werde. 8.3.2 Lock Granularität Höhere Ebenen des Data Managers wählen die Grösse der Datenbankelemente die gelockt werden. Man nennt dies auch die Locking Granularity. Ein Datenbankelement das man Locken kann, kann z.B. ein Datenbank Record, ein einzelnes Feld eines Datenbank Records, ein ganzer Diskblock, ein File oder sogar die ganze Datenbank sein. Verschiedene Tradeoffs muss man beachten, wenn man Datenelement Grösse bestimmt. Je grösser die Datenelement Grösse, je kleiner ist der Grad an erlaubter Concurrency, dafür hat der Daten Manager wenig Overhead. Auf der anderen Seite, je kleiner Granularität ist, umso höher ist der Grad an Concurrency, dafür müssen viele Locking Informationen gespeichert und verwaltet werden, was zu einem grossen Overhead führt. Was die beste Granularitätsgrösse ist, hängt vom den involvierten Transaktionen ab. Wenn eine typische Transaktion auf eine kleine Anzahl von Records zugreift, ist es vorteilhaft die Granularität klein zu halten, z.B. ein einzelnes Record. Wenn auf der anderen Seite eine typische Transaktionen aber auf viele Records im selben File zugreift ist es vielleicht besser Block oder File Granularität zu haben, so dass die Transaktion all diese Records als ein (oder wenige) Datenelemente betrachtet. Es ist üblich auf die Kompromisse beim Locking auf Page und File Granularität einzugehen, wenn hohe Transaktions Raten nicht erforderlich sind und/oder wie erwähnt die Transaktionen auf viele Record pro Page zugreift, weil es die Implementierung eines Data Managers und den Recovery Algorithmus vereinfacht. High-Performance TP Systeme erfordern aber ein Locking auf Record Granularität. 8.4 Multigranularity Locking Da die beste Granularitätsgrösse von den gegebenen Transaktionen abhängt, scheint es angebracht, dass ein DBMS verschiedene Granularitätslevel unterstützt, die für verschiedene Transaktionen unterschiedlich sind. Eine einfache Granularitätshierarchie zeigt z.B. Abbildung 8.7. Für Transaktionen die auf grosse Mengen von Daten (lange Transaktionen) zugreifen, lockt der Data Manager grosse Einheiten (Coarse granularity), z.B. Files, Tabellen, während er für andere Transaktionen (kurze) eine kleinere Granularität (fine Granularity) wählt. Der Manager muss fähig sein widerstreitend Locks auf verschiedenen Granularitätsstufen zu erkennen. Um dies zu ermöglichen werden weitere Typen, Intention Locks genannt, gebraucht. Es gibt drei Typen von Intention Locks: 1. Intention-Read (IR) (oder Intention-Shared (IS)): Indiziert, dass für Nachfolgerknoten (descendant node(s) ein Shared-Lock beantragt wird. 100 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME DB 1 Disk Area 1 ...... File 1.1 Record 1.1.1 .... Disk Area 2 Record 1.1.i File 1.n Record 1.n.1 .... ...... File 2.1 Record 1.n.j Record 2.1.1 .... Record 2.1.i File 2.m Record 2.m.1 .... Record 2.m.j Abbildung 8.7: Eine Beispiel für einen Garanularitätshierarchie Baum R W IR IW RIW R ja nein ja nein nein W nein nein nein nein nein IR ja nein ja ja ja IW nein nein ja ja nein RIW nein nein ja nein nein Tabelle 8.1: Kompatibilitäts Matrix für Multigranularity Locking 2. Intention-Write (IW) (oder Intention-Exclusiv (IX)): Indiziert, dass für Nachfolgerknoten (descendant node(s) ein Exclusive-Lock beantragt wird. 3. Read-Intention-Write (RIW) (oder Shared-Intention-Exclusive (SIX)): Indiziert, dass der aktuelle Knote im Shared Mode gelockt ist, aber ein Exclusive-Lock wird für Nachfolger beantragt. Ein Lock auf eine grobes Granulat x lockt explizit x und lock implizit alle Nachfolger von x. Bevor eine Transaktionen ein Read oder Write Lock auf ein x setzen kann, muss sie zuerst Intention Locks für alle Vorgänger anfordern. Das Setzen eines Intention Locks auf allen Vorgängern vo x stellt sicher, dass kein Write Lock implizit auf x einen Write Lock setzt. Die Kompatibiliätstabelle der drei Intention und der andere zwei Locks zeigt Tabelle 8.1. Das Multiple Granularity Locking (MGL) Protokol besteht aus folgenden Regeln: 1. Die Lock Compatibilität (Tabelle 8.1) muss eingehalten werden. 2. Der Root Node des Baumes muss in jedem Modus zuerst gelockt werden. 3. Ein Node N kann durch eine Transaktion T nur im R oder IR Modus gelockt werden, wenn der Parent Node bereits im IR oder IW Modus gelockt ist. 4. Ein Node N kann durch eine Transaktion T nur im W, IW oder RIW Modus gelockt werden, wenn der Parent Node bereits im IW oder RIW Modus gelockt ist. Ein Lock auf N ist ein expliziter Lock für N , ein Lock auf eine Vorgänger von N ist ein impliziter Lock für N 5. Um einen Node N zu lesen/beschreiben, muss T einen read/write Lock auf einem seiner Vorfahren besitzen. 6. Eine Transaktion T kann einen Node nur locken, wenn sie nicht bereits einen Node vom Lock befreit hat (2PL Protokoll) 7. Eine Transaktionen T kann einen Lock nur dann von einem Node N wegnehmen, wenn keines der Children von N gerade von T gelockt ist. MGL sichert nicht Serialisierbarkeit und muss deshalb in Verbindung mit 2PL benutzt werde. 2PL gibt die Regeln wann ein Lock zu setzen oder freizugeben ist und MGL spezifiziert wie die Locks für Datenelemente verschiedener Granularität gesetzt und freigegeben werden. Die Grundoperationen des Lock Managers sind wie zuvor: 8.5. DEADLOCK requested lock type 101 IR IW R RIW W current lock type IR IW R IR IW R IW IW IW R RIW R RIW RIW RIW W W W RIW RIW RIW RIW RIW W W W W W W W Tabelle 8.2: Lock Conversion • Service verlangen einen Lock zu setzen oder freizugeben • bei einem Lock Request, prüfe auf Konflikte • wenn kein Konflikt vorhanden ist, setze den Lock • wenn ein Konflikt vorhanden ist, wird die anfragende Transaktion blockiert, bis entweder der Lockrequest gewährt werden kann oder ein Deadlock zu einem Abbruch der Transaktion zwingt (rejected und abort) Um den Granularitätslevel für eine Transaktione anpassen zu können, kann ein Daten Manager escalation benutzen, d.h. er detektiert dynamisch wenn eine Transaktionen viele Lock verlangt und entscheidet einen Intention Lock zu setzen. Das Transaktionen Locking Verhalten kann zu lock escalation führen was grössere (coarser ) Granularität einführt. In gewissen Applikationen haben lock escalations grosse Wahrscheinlichkeiten in einen Deadlock zu führen, weil es nötig ist Lock zu konvertieren. Wie Locks konvertiert werden müssen zeigt Tabelle 8.2. Es kann die Möglichkeit geben eine Transaktion abzubrechen, wenn das Locking Verhalten als zu fein granuliert detektiert wird. Eine Analyse während der Kompilation des Transaktions Programms kann vielleicht dabei helfen eine passende Granularitätsstufe zu finden. Man kann die Baumstruktur des Lock Type Graphen auch auf einen direkten azyklischen Graphen (DAG) verallgemeinern, vorallem wenn man auch Indices in Betracht zieht. Da ein Knoten jetzt mehrere Parent haben kann, müssen das MGL Protokoll angepasst werde: 1. Die Lock Kompatibilität (Tabelle 8.1) muss eingehalten werden. 2. Der Root Node des Baumes muss in jedem Modus zuerst gelockt werden. 3. Ein Node N kann durch eine Transaktion T nur im R oder IR Modus gelockt werden, wenn einer seiner Parent Nodes bereits im IR oder IW Modus gelockt ist. 4. Ein Node N kann durch eine Transaktion T nur im W, IW oder RIW Modus gelockt werden, wenn alle seiner Parent Nodes bereits im IW oder RIW Modus gelockt ist. 5. Um einen Node N zu lesen, muss T einen read oder write Lock auf einem Vorfahren besitzen. Um auf N zu schreiben, muss T für jeden Pfad von der Wurzel des Graphen bis zu N einen write Lock auf einem Vorgänger von N entlang dieses Pfades haben. 6. Eine Transaktion T kann einen Node nur locken, wenn sie nicht bereits einen Node vom Lock befreit hat (2PL Protokoll) 7. Eine Transaktionen T kann einen Lock nur dann von einem Node N wegnehmen, wenn keines der Children von N gerade von T gelockt ist. 8.5 Deadlock Wenn zwei oder mehr Transaktionen blockiert sind, weil sie aufeinander warten bis ein Lock freigegeben wird, spricht man von einer Deadlock Situation. Ein Beispiel zeigt Abbildung 8.8. Deadlock bedeuten in 2PL nicht Serialisierbarkeit. Um einen Deadlock auflösen zu können muss mindesten eine der beteiligten Transaktionen abgebrochen werden. Deadlock sind aber äusserst selten (∼< 1% der Transaktionen), trotzdem sollten sie wenn möglich verhindert oder zumindest detektiert und behandelt werden können. 102 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME T1 data item x y Locks held [T1 , read] [T2 , read] T2 locks requested [T2 , write] [T1 , write] Abbildung 8.8: Deadlock Situation mit korrespondierendem waits-for Graph In Betriebssystemen können Deadlock verhindert werden, indem man niemals Locks erlaubt die zu einem Deadlock führen könnten. In einem TP System könnte dies erreicht werden, indem Transaktionen dazu gezwungen werden alle ihre Locks zu verlangen bevor die Ausführung startet, dies ist aber für Transaction Processing zu restriktiv. Allgemein kann man sagen, dass Deadlock Prävention in TP Systemen entweder die Concurrency begrenzt oder die Art Transaktionen zu Programmieren begrenzt. Die meisten Systeme beschränken sich deshalb auf Deadlock Detektion und Recovery anstelle von Prävention. 8.5.1 Deadlock Detection Es gibt zwei Grundsätzliche Vorgehensweisen Deadlock zu detektieren: 1. Timeouts: Es wird eine Timeout Periode länger als die Typische Ausführungszeit einer Transaktion verwendet um vermeintliche Deadlock zu detektieren. Diese Methode ist einfach und leicht zu Implementieren und funktioniert auch in einem verteilten System, aber kann dazu führen, dass Transaktionen unnötigerweise abgebrochen werden und erlaubt vielleicht einem Deadlock unnötig lange zu bestehen. 2. Graph basierte Detektion: Ein transaction waits-for Graph wird periodisch auf Deadlocks überprüft (Zyklus) im Graph. Diese Methode muss für verteilte Deadlock angepasst werden, indem z.B. ein Node (im Netzwerk) für die Detektion von Deadlocks verantwortlich ist und die anderen Nodes im periodisch eine Kopie ihrer waitsfor Graphen schicken. Wie bereits erwähnt können Lock Konvertierungen zu Deadlocks führen. Ein Deadlock kann z.B. auftreten, wenn zwei Transaktionen einen Read Lock auf Datenelement x haben und beide einen Write Lock auf x möchten. Solche Konvertierungen von Read zu Write Locks sind recht häufig und sie können verhindert werden, indem Transaktionen bereits beim ersten Zugriff auf ein Datenelement einen Write Lock verlangen. Damit der Daten Manager erkennen kann ob er besser ein Write Lock anstelle eines Read Locks setzt, muss die Transaktion dem Datenmanager beim Lesen von x einen Hinweis geben. Transaktionen geben ihre Anfragen in höheren Sprachen aus und so kann die Lock Konvertierung angepasst werden (z.B. update in SQL). 8.6 8.6.1 Performance Issues und Bottlenecks Lock Trashing Das Blockieren von Transaktionen durch das Warten auf Lock Anfragen, kann die Performance eines TP System beträchtlich beeinträchtigen. Wenn der Transaktions Load steigt, kann er einen Punkt erreichen indem eine grosse Anzahl Transaktionen blockiert werden und der throughput fällt. An diesem Punkt spricht man von Lock Trashing. Designer von Daten Managern, Datenbanken und Applikationen benutzen verschiedene Techniken um das Blocken von Transaktionen zu minimieren. Wenn man annimmt, dass eine Transaktion einen Write Lock L für t Sekunden hält, dann ist die maximale Transaktionsrate für Transaktionen die L setzen 1/t (eine Transaktion pro t Sekunden). Die meisten Techniken versuchen diese Zeit, die eine Transaktionen einen Lock hält zu reduzieren. Man kann z.B. die Applikation so anpassen, dass sie Locks später in der Transaktionsausführung setzt, z.B. 8.6. PERFORMANCE ISSUES UND BOTTLENECKS 103 indem Updates bis zum Commit in lokalen Variablen gehalten werden. Eine andere Möglichkeit besteht darin, die Ausführungszeit von Transaktionen zu reduzieren, indem die Anzahl Instruktionen reduziert werden, Daten effektiv gebuffert werden um Disk Zugriffe zu vermindern und/oder indem die Benutzung von Ressourcen wie Kommunikation optimiert werden. 8.6.2 Hot Spots Die Lock Granularität beeinflusst die Performance, Konflikte können durch feiner-granulare Locks reduziert werden. Durch gutes Datenbank Design kann die Performance gesteigert werden. Nehmen wir an, dass ein File mit Records einige stark frequentierte Felder, so genannte “hot spots”, und einige kaum besuchte Felder (“cold spots”) hat, dann könnte man das File vertikal teilen und hot spots in dem einen File und cold spots in dem anderen Speichern, dadurch werden Transaktionen die auf cold spots zugreifen nicht mehr länger durch Transaktionen die auf hot spots zugreifen blockiert. Auf gewisse Datenelemente wird so oft zugegriffen (selbst auf kleiner Granularitäts Stufe), dass sie zu einem Flaschenhals werden. Solche Elemente sind als Hot Spots bekannt und beinhalten typischerweise: - Summary Informationen, wie z.B. der Geldbetrag in einer Bank Zweigstelle - end-of-file Marker in Files, die als Datenbank Entry benutzt werden. - die nächste Seriennummer die sequentiell vergeben wird, wie z.B. Bestellnummer oder Transaktionsnummer Um Hot Spot Flaschenhälse zu entlasten, können z.B. “Heisse” Daten im Arbeitsspeicher gehalten werden, Operationen auf Hot Spots bis kurz vor den Commit hinausgezögert werden, Read Operationen durch Verifikationen ersetzt werden die bis zum Commit herausgezogen werden oder Operationen in Private Batches Gruppiert werden, die periodisch zum Einsatz gelangen. 8.6.3 Query Update Problem Queries die Lesezugriff auf viele Daten benötigen (z.B. für Reporte oder Entscheidungs Unterstützung) können auch zu Concurrency Botlenecks führen, weil sie z.B. im 2PL viele Locks setzen und diese für lange Zeit halten. Viele Systeme geben die Serialisierbarkeit von Queries auf und benutzen schwächere Regeln für Queries als 2PL. Man Unterscheidet in solchen Systemen drei Isolationsgrade (siehe Tabelle 8.3): • Cursor Stability (2. Isolationsgrad): Der Daten Manager hält nur einen Read Lock währen der Zeit in der die Query tatsächlich die Daten liest und gibt den Lock frei sobald alle Daten gelesen sind. Es wird garantiert, dass die Query nur Daten liest die von commited Transaktionen produziert wurden, sichert aber nicht sie Serialisierbarkeit. • Dirty Reads (1. Isolationsgrad: Queries können ebenfalls Daten die noch uncommited sind lesen. Update Transaktionen bleiben immer noch Serialisierbar, wenn sie 2PL benutzen. Viele DBMS bieten Optionen um Transaktionen in verschiedenen Isolationsgraden auszuführen, was die Performance steigern, aber auch falsche Resultate liefern kann. Die vier Isolationsgrade die in SQL unterstütze werden zeigt Abbildung 8.9. 104 KAPITEL 8. TRANSAKTIONSMODELLE UND SYSTEME Degree of Isolation 1 2 3 Technical Term Dirty Reads Cursor Stability Repeatable Reads Behaviour Setze keine Read Locks Lese nur commited Daten serialisierbar Tabelle 8.3: Die drei Isolationsgrade in DBMS SET SET SET SET TRANSACTION TRANSACTION TRANSACTION TRANSACTION ISOLATION ISOLATION ISOLATION ISOLATION LEVEL LEVEL LEVEL LEVEL READ UNCOMMITTED READ COMMITTED REPEATABLE READ SERIALIZABLE Dirty Reads Cursor Stability (siehe den Kommentar unten †) Default † Wenn man eine Query zweimal ausführt, kriegt man bei der zweiten Ausführung mindestens die selben Daten wie bei er ersten, aber vielleicht auch mehr. (Superset von 1. Query Ausführung) Für jeden Level ist der Default, dass die Transaktion READ WRITE ist, man kann aber für jeden Level READ ONLY spezifizieren. Abbildung 8.9: SQL Isolations Levels Literaturverzeichnis [1] S. Abiteboul, D. Quass, J. McHugh, J. Widom, J. Wiener: “The Lorel query language for semistructured data”, Journal of Digital Libraries, November 1996, (http://www-db.stanford.edu/lore/pubs/lorel96.pdf) [2] Alfred V. Aho, Ravi Sethi, Jeffrex D. Ullman: “Compilers Principles, Techniques, and Tools”, Pearson Education (Singapore) Pte. Ltd., Indian Branche, Delhi, India, Fifteenth Indian Reprint (2004), ISBN 81-7808-046-X [3] S. Alagic: “The ODMG object model: does it make sense?”, Proceeding of the 12th ACM SIGPLAN conference on Object-oriented programming, system, languages, and applications, Volume32 Issue10, (1997), Atlanta, Georgia, United States, Pages: 253-270, (http://doi.acm.org/10.1145/263698.2637461 ) [4] A. Albano, L. Cardelli, R. Orsini: “Galileo: A Strongly Typed, Interactive Conceptual Language”, (1985) (http://www.di.unipi.it/˜albano/galileo/documentation/Galileo.pdf) [5] A. Albano, G. Antognoni, G. Baratti, G. Ghelli, R. Orsini: “Galileo97 Reference Manual. Version 2.0”, September 1998, (http://www.di.unipi.it/˜albano/galileo/documentation/index.html) [6] T. Lougenia Anderson, Earl F. Ecklund Jr., David Maier: “PROTEUS: objectifying the DBMS user interface”, Proceedings on the 1986 international workshop on Object-oriented database systems, Pacific Grove, California, United States, Pages: 133 - 145, (1986), IEEE Computer Society Press, Los Alamitos, CA, USA, ISBN: 0-8186-0734-3 [7] M. P. Atkinson, P. J. Bailey, K. J. Chisholm, W. P. Cockshott, R.’Morrison: “An Approach to Persistent Programming”, computer Journal 26, 4 (1983) pp 360-365, (http://www.dcs.st-and.ac.uk/research/ publications/download/ABC+83a.pdf) [8] M. P. Atkinson, P. J. Bailey, K. J. Chisholm, W. P. Cockshott, R.’Morrison: “PS-algol: A Language for Persistent Programming”, In Proc. 10th Australian National Computer Conference, Melbourne, Australia (1983) pp 70-79, (http://www.dcs.st-and.ac.uk/research/publications/download/ABC+83b.pdf) [9] Malcolm P. Atkinson, O. Peter Buneman : “Types and persistence in database programming languages”, ACM Computing Surveys (CSUR), Volume 19 , Issue 2 (June 1987), Pages: 105 - 170, (http://doi.acm.org/10.1145/62070.45066) [10] Malcolm P. Atkinson,François Bancilhon, David DeWitt, Klaus Dittrich, David Maier, Stanley Zdonik: “The Object-Oriented Database System Manifesto”, Proceedings of the First International Conference on Deductive and Object-Oriented Databases, Kyoto, Japan, Pages: 223-240, 1989, (http://www.cl.cam.ac.uk/Teaching/2003/Databases/oo-manifesto.pdf) 1 die PDFs, welche bei ACM zu finden sind, können nur geöffnet oder abgespeichert werden, wenn man sich registriert hat, oder wenn man mit einer ETH IP darauf zugreifft 105 106 LITERATURVERZEICHNIS [11] Malcolm P. Atkinson: “Orthogonally persistent object systems”, The International Journal on Very Large Data Bases, Volume 4 , Issue 3 (July 1995), Pages: 319 - 402 [12] M. P. Atkinson, L. Daynes, M. Jordan, T. Printezis, S. Spence: “An Orthogonally Persistent JavaTM ”, ACM SIGMOD Record, Volume 25, Number 4, December 1996, (http://research.sun.com/forest/COM.Sun.Labs.Forest.doc.sigmod 96.paper pdf.pdf) [13] Philip A. Bernstein: “Transaction processing monitors”, Communications of the ACM, Volume 33, Issue 11, 1990, Pages: 75 - 86 (http://doi.acm.org/10.1145/92755.92767) [14] Sergey Brin, Lawrence Page: “The anatomy of a large-scale hypertextual Web search engine”, Proceedings of the 7th international conference on World Wide Web, Brisbane, Australia, 1998, Pages: 107 - 117 (http://www-db.stanford.edu/pub/papers/google.pdf) [15] Rick Cattell: “Experience with the ODMG standard”, StandardView, Volume 3, Issue 3 (September 1995), Pages: 90 - 95, ACM Press, (http://doi.acm.org/10.1145/226191.226197) [16] D. Chamberlin: “XQuery: An XML query language”, IBM SYSTEMS JOURNAL, VOL 41, No 4, 2002, (http://www.research.ibm.com/journal/sj/414/chamberlin.pdf) [17] R. L. Cooper, M. P. Atkinson, A. Dearle, D. Abderrahmane: “Constructing Database Systems in a Persitent Enviroment, Proceedings of the 13th VLDB Conference, Brighton 1987, (http://www.vldb.org/conf/1987/P117.PDF) [18] Devanshu Dhyani, Wee Keong Ng, Sourav S. Bhowmick: “A survey of Web metrics”, ACM Computing Surveys, Volume 34, Issue 4 (December 2002), Pages: 469 - 503 (http://doi.acm.org/10.1145/592642.592645) [19] Beverly A. Dutton, Kathryn C. Kinsley: “Design and implementation of RAQUEL II: a relational query language”, Proceedings of the 16th annual Southeast regional conference, Atlanta, Georgia, Pages: 327 - 333, (http://doi.acm.org/10.1145/503643.503714) [20] Andrew Eisenberg, Jim Melton: “SQL/XML and the SQLX Informal Group of Companies”, (http://www.acm.org/sigmod/record/issues/0109/standards.pdf) [21] Andrew Eisenberg, Jim Melton: “SQL/XML is Making Good Progress”, (http://www.acm.org/sigmod/record/issues/0206/standard.pdf) [22] Ramez Elmasri, Shamkant B. Navathe: “Fundamentals of DATABASE SYSTEMS”, Pearson Education (Singapore) Pte. Ltd., Indian Branche, Delhi, India, Fourth Edition, First Indian Reprint (2004), ISBN 81-297-0228-2 [23] David Flanagan, Jim Farley, William Crawford: “Java Enterprise in a Nutshell”, O’Reilly, second edition, 2002, ISBN 0-596-00152-5 [24] IBM: “Overview of DB2’s XML Capabilities: An introduction to SQL/XML functions in DB2 UDB and the DB2 XML Extender” (http://www-106.ibm.com/developerworks/db2/library/techarticle/dm0311wong/index.html) [25] IBM Internal Report on the Contents of a Sample of Programs Surveyed, San Jose, CA, 1978 [26] Mick Jordan: “Early Experiences with Persistent JavaTM ”, The First International Workshop on Persistence and Java (PJ1), Glasgow, Scotland, 16-18 September 1996. (http://research.sun.com/forest/COM.Sun.Labs.Forest.doc.eepjava.paper pdf.pdf) [27] Mick Jordan, M. P. Atkinson: “Orthogonal Persistence for JavaTM — A Mid–term Report”, Third International Workshop on Persistence and Java, Tiburon, CA, Sep 1-3, 1998, (http://research.sun.com/forest/COM.Sun.Labs.Forest.doc.opjmidterm.paper pdf.pdf) LITERATURVERZEICHNIS 107 [28] Donald Kossmann: “The state of the art in distributed query processing”, ACM Computing Surveys (CSUR), Volume 32, Issue 4 (December 2000), Pages: 422 469, (http://doi.acm.org/10.1145/371578.371598) [29] Donald Kossmann, Konrad Stocker: “Iterative dynamic programming: a new class of query optimization algorithms”, ACM Transactions on Database Systems, Volume 25, Issue 1, March 2000, Pages: 43 82 (http://doi.acm.org/10.1145/352958.352982) [30] Charles Lamb, Gordon Landis, Jack Orenstein, Dan Weinreb: “The ObjectStore database system”, Communications of the ACM archive, Volume 34, Issue 10 (October 1991), Pages: 50 63, (http://doi.acm.org/10.1145/125223.125244) [31] C. Lecluse, P. Richard, F. Velez: “O2, an object-oriented data model”, Proceedings of the 1988 ACM SIGMOD international conference on Management of data, Chicago, Illinois, United States, Pages: 424 - 433, (http://doi.acm.org/10.1145/50202.50253) [32] Y. E. Lien: “Design and implementation of a relational database on a minicomputer”, Proceedings of the 1977 annual conference, Pages: 16 - 22 [33] F. Matthes, J. W. Schmidt: “Definition of the Tycoon Language Tl – A Preliminary Report”, Informatik Fachbericht FBI-HH-B-160/92, Fachbereich Informatik, Universität Hamburg, Germany, October 1992. (Revised 17-aug-1995), (http://www.sts.tu-harburg.de/papers/1992/MaSc92/paper.pdf) [34] F. Matthes, S. Müssig, J. W. Schmidt: “Persistent Polymorphic Programming in Tycoon: An Introduction”, FIDE Technical Report Series FIDE/94/106, FIDE Project Coordinator, Department of Computing Sciences, University of Glasgow, Glasgow G128QQ, August 1994, (http://www.sts.tu-harburg.de/papers/1994/MMS94/paper.pdf) [35] David C. J. Matthews: “Poly Manual”, ACM SIGPLAN Notice, Volume 20, Issue 9 (August 1985), Pages: 52-76), (http://doi.acm.org/10.1145/988364.988371) [36] B. Mathiske, F. Matthes, S. Müssig: “The Tycoon System and Library Manual”, DBIS Tycoon Report 212-93, Fachbereich Informatik, Universität Hamburg, Germany, December 1993. (Revised 19-jan-1996), (http://www.sts.tu-harburg.de/papers/1993/MMM93/paper.pdf) [37] Jason McHugh, Serge Abiteboul, Roy Goldman, Dallan Quass, Jennifer Widom: “Lore: A Database Management System for Semistructured Data”, SIGMOND Record, 1997, (http://citeseer.ist.psu.edu/mchugh97lore.html) [38] Ron Morrison, Fred Brown, Richard Connor, Al Dearle: “The Napier88 Reference Manual”, A. Universities of Glasgow and St Andrews Report PPRR-77-89, (1989), (http://www.dcs.st-and.ac.uk/research/publications/download/MBC+89a.pdf) [39] NeoCore White Paper: “What is NeoCore XML Management System?”, (NeoCore Inc, Release 1.0 June 29, 2001, http://www.dmreview.com/whitepaper/wid327.pdf) [40] “PSE Pro for Java Tutorial”, PSE Pro for Java Release 6.1 Service Pack 2 for all platforms, March 2004, Progress Software Corporation [41] Joel E. Richardson, Michael J. Carey, Daniel T. Carey: “The design of the E programming language”, ACM Transactions on Programming Languages and Systems (TOPLAS), Volume 15 , Issue 3 (July 1993), Pages: 494-534, (http://doi.acm.org/10.1145/169683.174157) 108 LITERATURVERZEICHNIS [42] David W. Shipman: “The functional data model and the data language DAPLEX”, Proceedings of the 1979 ACM SIGMOD international conference on Management of data, Boston, Massachusetts, Pages: 59 - 59, (1979), (http://doi.acm.org/10.1145/582095.582105) [43] A. R. Schmidt, M. L. Kersten, M. A. Windhouwer, F. Waas:“Efficient Relational Storage and Retrieval of XML Documents”, International Workshop on the Web and Databases (In conjunction with ACM SIGMOD), Dallas, TX, USA, May 2000, Pages: 47-52, (http://www.cwi.nl/themes/ins1/publications/docs/ScKeWiWa:WEBDB:00.pdf) [44] Joachim W. Schmidt: “Query processing strategies in the PASCAL/R relational database management system”, Proceedings of the 1982 ACM SIGMOD international conference on Management of data, Orlando, Florida, Pages: 256-264, (http://doi.acm.org/10.1145/582353.582402) [45] Sun microsystems: “JDBCTM API Documentation”, (http://java.sun.com/j2se/1.4.2/docs/guide/jdbc/index.html) [46] Christian Ullenboom: “Java ist auch eine Insel”, Galileo Computing <openbook> (www.galileocomputing.de/openbook/javainsel4/) [47] Michael L. Van de Vanter: “PJama: Orthogonal JavaTM Platform”, The Forest project, sun Microsystems Laboratories, 1999, (http://www.jugs.ch/html/events/1999/persistence.pdf) persistence for the [48] Andrew E. Wade: “Object query standards”, ACM SIGMOD Record archive, Volume 25 , Issue 1 (March 1996), Pages: 87 - 92, (http://doi.acm.org/10.1145/381854.381895) [49] W3C: Scott Boag, Don Chamberlin, Mary Ferndandez, Daniela Florescu, Jonathan Robie, Jérôme Siméon:“XQuery 1.0: An XML Query Language”, W3C Working Draft 29 October 2004, (http://www.w3.org/TR/xquery/) [50] W3C: James Clark, Steve DeRose: “XML Path Language (XPath) Version 1.0”, W3C Recommendation 16 November 1999, (http://www.w3.org/TR/xpath) [51] W3C: Mary Ferndandez, Ashok Malhotra, Jonathan Marsh, Norman Walsh: “XQuery 1.0 and XPath 2.0 Data Model”, W3C Working Draft 29 October 2004, (http://www.w3.org/TR/xpath-datamodel/) [52] W3school: Online DTD Tutorial, (http://www.w3schools.com/dtd) R [53] Xpriori Technical Paper: “A Description of NeoCoreXMS versus Traditional Database Management Systems”, Xpriori, LLC, Release 1.0, 11/8/2004, (http://www.xpriori.com/library/White Paper-XMS vs RDBMS.pdf)