Embedded SQL (checked).......................................................................................................... 2 JDBC (checked) ......................................................................................................................... 4 Objekt-Relationale Datenbanken am Beispiel von Oracle (checked) ........................................ 6 Optimierung (checked) ............................................................................................................... 7 Trigger (checked) ....................................................................................................................... 9 Deadlock (checked) .................................................................................................................. 10 Hash-Verfahren (checked) ....................................................................................................... 11 B-Tree-Verfahren (checked) .................................................................................................... 12 Optimizer (checked) ................................................................................................................. 13 Replikation (checked) .............................................................................................................. 14 ANSI/SPARC-Architektur (checked) ...................................................................................... 15 Das hierarchische Modell (checked) ........................................................................................ 16 DRDA (checked) ...................................................................................................................... 16 Datenbank-Entwurf (checked) ................................................................................................. 17 Objektorientierte Datenbanken (checked) ................................................................................ 19 Embedded SQL (checked) Um SQL-Anweisungen in einen Quelltext einzubetten, ich möchte hier als Beispiel PL/1 entsprechend den Vorlesungsfolien verwenden, bietet sich die Nutzung von Embedded SQL an. Hier werden die SQL-Anweisungen direkt in den Quelltext eingebettet, vor der jeweiligen SQL-Anweisung steht noch das Schlüsselwort EXEC SQL welches der Precompiler später benötigt um die eingebettete Anweisung zu erkennen. Beispielsweise wäre so die folgende Programmierung möglich: EXEC SQL UPDATE KUNDEN SET NAME=MUELLER WHERE KUNDEN-NR=56789; Alternativ können auch Hostvariablen verwendet werden, diese beginnen immer mit einem : (in der SQL-Anweisung), also z.B. :Kundenname. Hostvariablen sind Variablen, die in dem PL/I-Programm (der Hostsprache) UND in dem eingebetteten SQL verwendet werden können. Zu Beginn eines Programms, welches Hostvariablen benötigt, müssen diese deklariert werden (damit der Precompiler dieser als Hostvariablen erkennt): EXEC SQL BEGIN DECLARE SECTION DCL sqlstate CHAR(5); DCL kundennummer CHAR(15); ... EXEC SQL END DECLARE SECTION; Ausnahmebedingungen können ebenfalls abgefangen werden, z.B. wenn eine SelectAnweisung die leere Menge ergab: EXEC SQL WHENEVER NOT FOUND GOTO LABEL; Wobei “LABEL” eine beliebige Sprungmarke des Programms in der Hostsprache ist. Der Precompiler hat die Aufgabe, die eingebetteten SQL-Anweisungen in Konstrukte der jeweiligen Programmiersprache zu übersetzen (diese Konstrukte arbeiten dann mit Bibliotheken, die die Verbindung zur Datenbank herstellen). Er arbeitet vor der Übersetzungszeit das Programm sequentiell ab. Zusammen mit dem jeweiligen DBMS überprüft er, ob die Syntax der SQL-Anweisungen korrekt ist, ob die Referenzen stimmen und die nötigen Rechte vorhanden sind. Bei statischen Anwendungen kann außerdem noch der optimale Zugriffspfad bestimmt und gespeichert werden. Bei dynamischen SQL-Anweisungen, die erst zur Laufzeit ermittelt werden ist dies natürlich von Hause aus nicht möglich. Um dynamische Anweisungen mit wiederholtem Zugriff zu beschleunigen gibt es auch in Embedded SQL die Möglichkeit der Prepared Statements. Beispielsweise: EINSTRING = “INSERT INTO STUDENTEN VALUES (?,?); EXEC SQL PREPARE PREP_ANWEISUNG FROM :EINSTRING; EXEC SQL EXECUTE PREP_ANWEISUNG USER :VARIABLE1; :VARIABLE2; Dynamische SELECT-Anweisung mit Deskriptor: EXEC SQL BEGIN DECLARE SECTION; material char(10); anzahl Int; regal Int; prep char(18); descriptorOut char(18); descriptorIn char(18); c1 char(18); satz char(512); EXEC SQL END DECLARE SECTION; … satz = "SELECT material, anzahl, regal, FROM demo where material = ?"; EXEC SQL PREPARE prep FROM :satz; EXEC SQL DECLARE c1 CURSOR FOR prep; //Anlegen Descriptoren EXEC SQL ALLOCATE DESCRIPTOR :descriptorOut; EXEC SQL ALLOCATE DESCRIPTOR :descriptorIn //Initialisieren Descriptoren EXEC SQL DESCRIBE OUTPUT prep USING SQL DESCRIPTOR :descriptorOut; EXEC SQL DESCRIBE INPUT prep USING SQL DESCRIPTOR :descriptorIn; //die Eingabewerte in Descriptor Area füllen material = "Schrauben"; EXEC SQL SET DESCRIPTOR :descriptorIn VALUE 1 DATA = :material; //Cursor öffnen und einlesen EXEC SQL OPEN c1 USING SQL DESCRIPTOR descriptorIn //Lese und Verarbeitungsschleife EXEC SQL FETCH c1 INTO SQL DESCRIPTOR EXEC SQL GET DESCRIPTOR descriptorOut EXEC SQL GET DESCRIPTOR descriptorOut EXEC SQL GET DESCRIPTOR descriptorOut ... //Lese und Verarbeitungsschleife Ende EXEC EXEC EXEC EXEC SQL SQL SQL SQL descriptorOut; VALUE 1 :material = DATA, VALUE 2 :anzahl = DATA, VALUE 3 :regal = DATA, CLOSE c1; DEALLOCATE PREPARE : prep; DEALLOCATE DESCRIPTOR descriptorOut; DEALLOCATE DESCRIPTOR descriptorIn; JDBC (checked) JDBC ist eine normierte Schnittstelle von einem JAVA-Programm zum Datenbankmanagement-System. Dabei beruht JDBC auf CLI und SQL/92 Entry-Level. Es hat sich in letzter Zeit auch eine Erweiterung zum herkömmlichen JDBC, nämlich JDBC 2.0 etabliert, mit vielen nützlichen Erweiterungen, wie änderbaren Ergebnismengen und anderem. Die meisten DBMS Hersteller bieten mittlerweile Klassen mittels derer man über JDBC direkt auf das jeweilige DBMS zugreifen kann, alternativ kann auch noch eine JDBC-ODBCBridge oder ein Pure-JAVA-Treiber verwendet werden. Ich möchte nun direkt vorstellen, wie man eine Verbindung zu einem DBMS in einem JAVAProgramm initialisiert und wie man SQL-Befehle ausführt. try { Class.forName(“jdbc.jconnector-mysql“); //Laden eines Treibers um über JDBC auf //MySQL zuzugreifen //Vom Treibermanager, bei dem der MySQL-Treiber mittels dem obigen Befehl automatisch //registriert wurde eine Verbindung anfordern: Connection conn = DriverManager.getConnection(“jdbc:heubes.de”, “root”, “geheim”); } //Exceptions abfangen… Generell kann man über die Klasse Statement Statements anlagen, die man dann als Queryoder Update-Anweisung an das DBMS übertragen kann. Hierzu ein kurzes Beispiel: int count; Statement einStmnt = conn.createStatement(); Und dann für ein Update: count = einStmnt.executeUpdate(“CREATE TABLE TESTTABLE (NUMMER INT, TEST VARCHAR(20))”); Oder im Falle einer Select-Anweisung: ResultSet = einStmnt.executeQuery(“select * from tab”); Hier erhalten wir als Ergebnis ein ResultSet, selbiges entspricht dem Cursor-Konzept und wird auch ähnlich behandelt, nur eben „objektorientiert”, auf diesen Aspekt komme ich später noch detaillierter zu sprechen. Soll eine Select-Anweisung , z.B. „SELECT * FROM Kunden WHERE Ort = ?“ mit wechselnden Parametern ausgeführt werden, so kann dies die Klasse Statement nicht gewährleisten, hier wird das PreparedStatement-Interface benötigt. Die Verarbeitung sieht dann folgendermaßen aus: PreparedStatement prepStmnt = conn.prepareStatement(“SELECT * FROM Kunden WHERE Ort = ?“); Bevor diese Anwendung ausgeführt werden kann, soll das ? mit einem Wert, der sich ändern kann, wiederholt ausgeführt werden. Dieses Besetzen wird über die Operation setXXX(a,b) realisiert. „setString“ wird es anzunehmender Weise in unserem Fall sein: prepStmt.setString(1, „Dortmund“); Dieser Befehl richtet sich nach folgender Syntax: setXXX(a,b) a = Position des Platzhalter, bei uns wird nur ein Fragezeichen, also ein Platzhalter verwendet, entsprechend benötigen wird den 1. Platzhalter, daher muss hier eine 1 eingesetzt werden. Das b ist der entsprechende Wert, oben wird der Ort auf „Dortmund“ gesetzt. Die volle SQL-Anweisung würde also für diese Iteration so aussehen: SELECT * FROM Kunden WHERE Ort = „Dortmund“. ResultSet rs = prepStmnt.executeQuery() liefert uns also einen ResultSet für den jeweils eingesetzten Platzhalter. Vorteil dieser Methode gegenüber normalen Statements mit zusammengesetzten Strings: Wenn viele Durchläufe nötig sind, kann die SQL-Anweisung zur Analyse und „Vorübersetzung“ an das DBMS geschickt werden. Der Optimizer muss also weniger häufig bemüht werden, wodurch diese Vorgehensweise als deutlich performanter als die mit „normalen“ Statements bezeichnet werden kann. Werden Stored Procedures benötigt, so kann auch das CallableStatement-Interface verwendet werden. Abschließend möchte ich noch die Weiterverarbeitung des ResultSets kurz erläutern: Wir benötigen eine Schleife zum durchlaufen der einzelnen „Tupel“ des Sets: while (rs.next()) { String Temp = rs.getString(“Kundenname“); //... } Hier würde aus dem aktuellen Tupel der Wert der Spalte „Kundenname“ ausgelesen, rs.next() ist solange True, wie es ein Nachfolgetupel gibt. Objekt-Relationale Datenbanken am Beispiel von Oracle (checked) Der objekt-relationale Ansatz von Oracle baut auf SQL 92 auf, das bedeutet, er ist nicht von Grund auf objektorientiert (wie z.B. im Fall von Poet oder Versant) sondern beinhaltet die gesamte Relationentheorie nach SQL-Standard und wurde um objektorientierte ZUSÄTZE erweitert. Folgende Erweiterungen hinsichtlich eines objektorientierten Datenbankmanagementsystems wurden bei Oracle vorgenommen: Es wurde das Konzept der benutzerdefinierten abstrakten Datentypen implementiert. Des weiteren werden Tupeltypen (row) zur Verfügung gestellt. Basierend auf den abstrakten Datentypen wird das Subtypkonzept, mit Vererbung, ermöglicht. Es ist somit auch möglich, Attribute und Funktionen zu redefinieren, es handelt sich hierbei also um Erweiterungen die klar in Richtung Polymorphismus abzielen (auch wenn dies natürlich ohne echte Vererbung nicht möglich ist – daher auch der Zusatz in Richtung). Erstmalig sind durch die objektorientierten Erweiterungen von Oracle auch nicht atomare Wertebereiche für Attribute (im Zusammenhang mit row) möglich, hierzu benötigt man dann die Typkonstruktoren list, set und multiset. Oracle 8 stellt in Erweiterung hierzu auch Objekt-Views zur Verfügung, Kollektionstypen werden aber auf eine Tabelle begrenzt. Nicht implementiert wurde hingegen eine echte Vererbungsstruktur mit echten Klassen usw. Ich möchte nun noch auf ein paar Aspekte des Kompromiss „objekt-relational“ (Kompromiss zwischen den alten und bewährten relationalen Aspekten und den „neu-modernen“ objektorierentierten Konzepten) eingehen. Grade dieser Kompromiss hat nämlich in der Praxis eine herausragende Bedeutung, ist es doch so, dass sich in der Softwareentwicklung die Objektorientierung weitestgehend durchgesetzt hat, so kann sie im Bereich der Datenbanken mehr oder minder als Flopp bezeichnet werden – Systeme wie poet finden keine breite Verwendung, vielmehr gewinnen objekt-relationale Systeme wie Oracle, db2 oder auch der Microsoft SQL-Server immer mehr an Bedeutung. Für den Datenbankadministrator besonders erleichternd in Oracle 8 ist die Möglichkeit, User Defined Datatypes zu verwenden. Im Klartext bedeutet dies, dass dem Datenbankadministrator und oder Anwendungsentwickler nun auch direkt auf Datenbankebene die Möglichkeit gegeben ist, eigene Objekt und Kollektionstypen anzulegen. Objekttypen bestehen erwartungsgemäß aus dem Namen des Typs, der Menge der Attribute und den Methoden, ich möchte hier ein kurzes Beispiel für das Anlegen eines Objekttyps anführen: Create type Typ_Kontakt as Object ( Tel_private varchar(20), Tel_gesch varchar(20), Fax varchar(20), E-Mail varchar(20) ); Dieser Typ kann nun in der gesamten Datenbank zur Speicherung der Kontaktdaten eingesetzt werden, somit findet hier das Prinzip der Wiederverwendung Anwendung. Ähnlichen funktioniert dies für Methoden. Es existiert allerdings keine echte Kapselung, so dass auf get- und set-Operationen verzichtet wurde. Weitere nützliche Erweiterungen von Oracle sind, dass Typen als Arrays angelegt werden können: Create type EinArray as Varray(spalten) of number; Ähnlich nützlich ist die Verwendung von nested tables, wo Tabellen in andere Tabellen “eingenistet” werden können. Ebenfalls noch nicht implementiert ist die Objektidentität, dies wird für kommende Releases erwartet. Optimierung (checked) Oft kommt es in der Praxis vor, dass ein bestehendes Datenbanksystem unter längerer Last von Tag zu Tag langsamer wird und der verantwortliche Datenbankadministrator sich fragen muss, woran das liegen kann und entsprechende Strategien entwickeln muss um ein weiteres Absinken der Transaktionsgeschwindigkeit zu verhindern. Doch auch bei der Neuinstallation sollte von Anfang an auf eine weitergehende Optimierung geachtet werden. Wichtig bei einem Datenbanksystem ist zunächst einmal, dass die Festplatten, sei es SCSI, RAID oder nur normales IDE nicht überlastet sind. Hierfür besitzen Server-Systeme meist viele Festplattenschächte. Auf Grund häufiger paralleler Zugriffe, sollten sich die folgenden Bereiche eines Datenbanksystems möglichst auf verschiedenen Platten befinden: 1. Die DBMS-System-Dateien, je nach Betriebssystem Binaries, DLLs, Installationsumgebungen und ähnliches, dies ist der Kern des Datenbanksystems. 2. Das Data Dictionary sollte sich ebenfalls auf einer gesonderten Platte, oder zumindest Partition befinden, da in ihm Schemata und Meta-Informationen gespeichert werden. 3. Sämtliche Log-Files sollten möglichst auf einer eigenen Platte gespeichert werden, je nach Konfiguration können in kürzester Zeit riesige Datenmengen an Log-Files entstehen. 4. Temporärer Speicherplatz ist auch für Datenbanken von besonderer Wichtigkeit, grade bei großen Datenbanken sind hier immense Plattengrößen erforderlich (genau so wie beim Arbeitsspeicher, 3 GB RAM keine Seltenheit) um die bei umfangreichen SELECT oder SORT / ORDER Aufgaben nötigen Zwischenstände zwischenzuspeichern. 5. Jede große Datenbank (also das oder die Datenfile/s) sollte eine eigene Festplatte zugewiesen bekommen. 6. Sollten viele Indexe eingesetzt werden, so kann sich auch für die Speicherung der IndexStrukturen ein eigener Festspeicher lohnen, ansonsten können sie auf einer separaten Partition auf der Hauptsystemplatte gespeichert werden. Soll die Plattenverteilung bei einem bestehenden System umgestellt werden, so ist dies meist nur Nachts unter vorübergehender Außerbetriebnahme des Systems möglich! Weniger aufwendig bei einem zu langsam gewordenem Datenbanksystem (natürlich auch bei einem neu installiertem!) ist die gezielte Implementation von Indexen. Einsatzmöglichkeiten von Indexen gibt es viele, zu den wesentlichsten gehört sicherlich die Möglichkeit den direkten Zugriff auf bestimmte Datensätze zu haben, ohne einen kompletten sequentiellen Durchlauf starten zu müssen. Über Indexe sind meist auch Bereichsanfragen an bestimmte Tabellen oder gar ganze Datenbanken möglich. Indexe sind vom Prinzip her in den meisten Fällen sortiert, so dass eine Vorsortierung vorhanden ist und ein wiederholtes Sortieren entfällt. Für Indexe eignen sich generell Schlüsselspalten oder Spalten die in Join, Having und Sort Operationen enthalten sind. Gegebenenfalls kann eine Partitionierung Zugriffe extrem beschleunigen. Beispielsweise könnte man eine Tabelle, die Aufträge enthält horizontal partitionieren und jedes Jahr eine andere Festplatte verwenden, dies verhindert, dass von Jahr zu Jahr das Gesamtsystem langsamer wird. Vertikale Partitionierungen sind dagegen wegen der zusätzlichen Schlüssel, die eingefügt werden müssen weniger zu empfehlen. Das Prinzip des Clusterns basiert auf den gleichen Grundsätzen, im Grunde ist die oben beschriebene horizontale Partitionierung nichts anderes als Clustering, Daten die zusammengehören werden zusammen gespeichert, genau das geschieht in diesem Beispiel, wird in der Praxis sehr häufig angewendet! Gegebenenfalls können Tabellen umstrukturiert werden, so dass NOT NULL-Spalten am Anfang stehen, dann die mit fester Länge und am Ende schließlich die mit variabler Länge stehen – dies wird in den meisten Fällen aber so oder so schon von vornherein geschehen sein. In vielen Fällen, wo viele lesende Transaktionen stattfinden, kann eine Denormalisierung, die teure JOINs vermeidet, die Anwendung extrem beschleunigen, daher sollte überdacht werden, ob es im konkreten Fall Sinn macht. Aber nicht nur die Datenbankinstallation kann optimiert werden, auch die auf das System erfolgenden Zugriffe bzw. die Anwendungen die selbige erzeugen müssen optimiert werden. So kann es deutliche Performance-Steigerungen bringen, wenn sämtliche die Datenbank verwendenden Anwendungen bei SELECT-Abfrage statt dem * die wirklich benötigten Spalten aufführen und nicht benötigte weglassen. Auch sollten stets korrekte Datentypen verwendet werden, um unnötige Konvertierungen zu vermeiden. Ebenso ist der Index bei zu kleinen Datenbanken oder auf falsche Spalten als eine deutliche Performance-Bremse zu sehen, da er bei ändernden Transaktionen stets mit aktualisiert werden muss. Die Benutzung von between, like, or und not sollte minimiert werden, um dem Datenbanksystem Arbeit abzunehmen. Prepared Anweisungen können den Optimizer und die gesamte Datenbank deutlich entlasten! Gegebenenfalls kann das manuelle Setzen von Sperren und manuelle Commit-Anweisungen die Datenbank deutlich beschleunigen, wenn das vom DBMS verwendete Sperrprinzip für den jeweiligen Fall ungeeignet ist, auch die Nebenläufigkeit kann in bestimmten Fällen erhöht werden. Man sollte eventuell versuchen, nicht all zu viele JOINs zu verwenden, da diese viele Ressourcen verbrauchen und somit „teuer“ sind. Und wenn denn gar nichts mehr hilft und das System einfach zu umfangreich geworden ist, dann muss über ein verteiltes System nachgedacht werden, Stichwort ist hierbei Replikation, also die Verteilung der Daten auf mehrere Datenbankknoten. Hierbei ist der limitierende Faktor immer die Datenübertragung. Deutliche Vorteile liegen in der erhöhten Ausfallsicherheit und in der erhöhten Lesegeschwindigkeit, auch die CPUAuslastung und der Arbeitsspeicher werden auf den einzelnen Maschinen meist proportional weniger verwendet. Großes Problem bei der Replikation: Ändernde Transaktionen, denn hier müssen zeitnah alle Replikate aktualisiert werden. Trigger (checked) Trigger sind immer dann von Bedeutung, wenn an einer Tabelle etwas geändert wird und vor oder nach dieser Änderung etwas Bestimmtes passieren soll. Warum werden nun Trigger unterstützt? Fragen wir uns, wie wir die oben kurz angerissenen Funktionen von Triggern implementieren würden, würde uns ein DBMS keine Trigger bieten (wie z.B. derzeit noch MySQL). Wir müssten wohl oder übel alle Trigger-Funktionen durch die Anwendung realisieren, das bedeutet, vor einem Update, nach deinem Update, vor dem Löschen, nach dem Löschen, vor dem Einfügen, nach dem Einfügen usw. bestimmte Operationen aufrufen. Das ist auch problemlos möglich, zumindest solange wir nur mit einem Client auf der Datenbank arbeiten – gäbe es jeweils nur eine Anwendung, die auf eine Datenbank zugreift, könnte man in der Tat relativ getrost auf Trigger verzichten – aber genau das ist in der Praxis selbstredend nicht der Fall, viel mehr ist es so, dass Datenbanken grade dann benutzt werden, wenn viele Anwendungen/Clients auf eine oder gar mehrere Datenbasen zugreifen müssen. Im Falle einer Client-Server Architektur wäre die Implementation ohne Trigger schon denkbar schwierig, denn woher soll ein Programm wissen, ob nicht vielleicht ein anderes grad etwas ändert um dann entsprechend eines Triggers zu reagieren? Hierfür müsste die Datenbank entsprechende Konstrukte zur Verfügung stellen und es müssten ein oder mehrere Clients die Datenbank ständig auf Änderungen überwachen. Dieser Misstand war den Machern von SQL bekannt und sie haben befunden, dass die einfachste Lösung eine DMBS-Seitige ist, nämlich dass auf Seite der Datenbank direkt bevor z.B. eine Einfügen-Transaktion gestartet wird, bestimmte Aktionen gestartet werden können, und aus diesem Grund gibt es Trigger, sämtliche Eigenprogrammierungen sind in den meisten Fällen unangebracht, da viel zu komplex, wie ich oben schilderte, folglich sind Trigger eine in vielen Fällen unverzichtbare Vorrichtung für den Datenbankadministrator und Anwendungsentwickler. Ein typisches Anwendungsbeispiel für die Benutzung von Triggern ist die Protokollierung bzw. das Logging der Änderungen an einer Tabelle, nach jeder Änderung soll protokolliert werden, wann die Änderung von wem durchgeführt wurde. Hierfür wird eine separate Tabelle benötigt, in der die Protokollierung eingefügt werden kann. Nehmen wir an, wir wollen in einer Linux-Umgebung protokollieren, welche User auf einem PROFTPD-Daemon einen Account haben, an dem in den letzten Wochen Änderungen an den Rechten vorgenommen wurden. Wir möchten also konkret loggen, bei welchem User von wem in letzter Zeit wann welche Änderungen an den User-Rechten vorgenommen wurden. Der Trigger könnte hier wie folgt aussehen: CREATE TRIGER PROTOKOLL_UPDATE AFTER UPDATE ON FTPUSER FOR EACH ROW BEGIN INSERT INTO FTPUSER_PROTOKOLL VALUES (USER, CURRENT TIMESTAMP, “NEUE RECHTE”) END; Ein entsprechender Trigger könnte dann beispielsweise auch noch für insert und delete Operationen vorgenommen werden. Obiges Beispiel zeigt, dass bei jedem Update der Tabelle FTPUSER ein neues Tupel in die Tabelle FTPUSER_PROTOKOLL eingetragen wird. Grade im Bereich der Protokollierung gibt es viele Anwendungsmöglichkeiten. Doch auch in einem Warenwirtschaftssystem sind Beispiele denkbar, z.B. kann bei jedem Update der Bestandstabelle überprüft werden ob bestimmte Mindestbestände unterschritten wurden und schließlich eine entsprechende Stored Procedure aufgerufen werden oder Ähnliches. Weitere Anwendungsbereiche sind in der Replikation zu finden der Trigger sorgt zum Beispiel bei Primary Copy dafür, dass die Änderungen an alle anderen Knoten weitergegeben werden. Auch im Datenschutz können Trigger verwendet werden, z.B. um bestimmte Informationen automatisch zu entfernen. Schließlich sind Trigger eigentlich immer dann ein Ausweg, wenn die „normale“ Funktionalität eines Datenbanksystems nicht ausreicht, z.B. bei Zusammenhängen die über einfache Referenzen oder Fremdschlüssel hinausgehen. Deadlock (checked) Grundsätzlich treten Deadlocks meist bei der Verwendung von Sperrverfahren auf. Ein Deadlock lässt sich beschreiben als eine Verklemmung die zwei oder mehr Transaktionen bei Zugriff auf ein und dieselben Daten in der Datenbank verursachen. Hier ein Beispiel: Es gibt eine Transaktion 1, diese hat verschiedene Aufgaben zu erledigen und sperrt dabei unter anderem Tabelle A. Eine weitere Transaktion 2 läuft parallel ab und sperrt dazu unter anderem Tabelle B. Nun benötigt Transaktion 1 Zugriff auf Tabelle B und Transaktion 2 zeitgleich Zugriff auf Tabelle A. Es entsteht eine Verklemmung, in der beide Transaktionen dauerhaft laufen und jeweils auf die Freigabe der von der anderen Transaktion gesperrten Tabelle warten. Werden nicht entsprechende Maßnahmen ergriffen werden beide Tabellen ewig gesperrt bleiben und beide Transaktionen ewig laufen. Unter Umständen kann durch solch eine Verklemmung das ganze System zum Erliegen kommen! Solche Verklemmungen nennt man daher auch Deadlocks. Im Rahmen des SQL-Standards stehen für die Vermeidung von Deadlocks in der DDL mit SET TRANSACTION einige Möglichkeiten zur Verfügung. So kann hier die volle Serialisierbarkeit mit SERIALIZABLE eingestellt werden. Wenn bei mehrfachen Selects in einer Transaktion auch Datensätze angezeigt werden sollen, die zwischenzeitlich von anderen Transaktionen geändert wurden, so kann mit SET TRANSCATION schließlich in den REPEATABLE READ-Modus geschaltet werden. Dürfen bei einem wiederholten gleichartigen Commit in einer Transaktion auch bereits gelesene Zeilen von einer anderen Transaktion geändert werden, so kann der READ COMITTED-Modus aktiviert werden, sollen darüber hinaus auch Zeilen gelesen werden können, die von anderen Transaktionen noch nicht freigegeben wurden, so sollte der READ UNCOMMITTED-Modus aktiviert werden. Des weiteren gibt es laut SQL-Standard in der DCL die Möglichkeit mit COMMIT explizit eine Transaktion zu beenden (erfolgreich) oder sie mit ROLLBACK zurückzusetzen, auch können mit LOCK TABLE Tabellen explizit gesperrt werden. In aktuellen DBMSen gibt es im wesentlichen 2 Lösungsansätze zum Umgehen von Deadlocks, da wären zum einen verschiedene Sperrverfahren, für die jeweils Strategien zum Umgehen von Deadlocks entwickelt werden müssen und zum anderen die optimistischen Synchronisationsverfahren, bei denen generell keine Deadlocks auftreten können. Als einfachster Vertreter der Sperrverfahren bleibt das Zwei-Phasen-Sperrprotokoll zu nennen, hier werden in der ersten Phase die benötigten Sperren gesetzt, nach Durchführung der Transaktion werden in der zweiten Phase alle Sperren wieder entfernt. Die Deadlockgefahr ist relativ hoch. Als Erweiterung wurde das Zeitstempelverfahren eingesetzt, wo jedem sperrbaren Objekt bei einer Sperre ein Zeitstempel zugewiesen wird, kommt es zu einem Konflikt kann so z.B. eine Transaktion bevorzugt werden, sofern sie jünger ist als die Transaktion die das betroffene Objekt gesperrt hat, dies garantiert die Deadlockfreiheit! Bei optimistischen Synchronisationsverfahren ist, wie oben bereits erwähnt die Deadlockfreiheit garantiert, dieses Verfahren sollte nur angewendet, wenn nur selten Transaktionskonflikte auftreten. Hier wird nicht in den Ablauf der Transaktion eingegriffen sondern erst am Ende der selbigen überprüft ob ein Konflikt aufgetreten ist. Hash-Verfahren (checked) Das Hash-Verfahren ist eins von vielen, um einen Index bei einer Datenbank zu realisieren. Ein Index wird auf bestimmte Spalten gelegt, um Zugriffe über diese Spalten auf die Tabelle entscheidend zu beschleunigen, das macht entsprechend nur dann Sinn, wenn die Spalten auch häufig aufgerufen werden und die Datenmengen entsprechend groß sind, ansonsten kann der Einsatz eines Indexes mehr „kosten“ als die entsprechenden Operationen so durchzuführen! Es gilt nämlich zu beachten, dass ein Index auch bei Änderungen (also Einfügen, Ändern, Löschen) mitgepflegt werden muss. Das Hash-Verfahren zeichnet sich dadurch aus, dass die Verbindung zwischen Schlüssel und der Tupel-ID (also dem „Link“ zum eigentlichen Datensatz) über eine Funktion (der HashFunktion) realisiert ist. Entscheidender Vorteil dieser Methode ist, dass keine Lesezugriffen zum Finden der TID nötig sind, also keine Plattenzugriffe den Vorgang ausbremsen. Dies hat zur Folge, dass Hash-Verfahren besonders schnell sind. Hash-Verfahren sind meistens auf mathematischen Funktionen und Gleichungen basiert, die auf Grundlage mathematischen Grundeigenschaften in speziellen Algorithmen einem Schlüssel ein Tupel durch Berechnung bereitstellen. Ganz unproblematisch sind Hash-Verfahren aber dennoch nicht (sonst würde man nichts anderes mehr einsetzen): So kann es bei den Berechnungen passieren, dass einem Schlüssel zwei Tupel-IDs zugeordnet werden können, da die Hash-Funktionen oft einen zu tolerierenden Ungenauigkeitsfaktor haben können. Für solche Probleme gibt es jedoch effektive Lösungsmöglichkeiten. Problematischer wirkt es sich da schon aus, dass keine direkten Bereichsanfragen möglich sind, da die Hash-Funktion immer nur ein Tupel direkt findet und nicht einen ganzen Bereich (z.B. Rechnungen von 2002). Des weiteren ist kein Zugriff in sortierter Reihenfolge möglich, wieder aus dem gleichen Grund, weil die Funktion uns nur ein Tupel zurückliefert. Für den Fall, dass der Index allerdings zunächst sortiert und aufgelistet wird, ist beides dann doch möglich, wobei zu überlegen ist, ob dann nicht andere Verfahren, wie zum Beispiel der binäre Baum vorzuziehen sind. Auf Grund der oben genannten Probleme mit den Hash-Funktionen, ist die Auswahl derselbigen oft ein Grund, woran die Nutzung dieses Verfahrens scheitert. Trotz allem ist dieses Verfahren auf Grund der schnellen Zugriffsmöglichkeit nicht wegzudenken und von unverzichtbarer Bedeutung. B-Tree-Verfahren (checked) Zunächst einmal ist zu sagen, dass ausgewogene k-Trees ein Spezialfall von indexsequentiellen Dateien sind. Dies bedeutet, dass die meisten Vorzüge aber auch Nachteile von index-sequentiellen Dateien auch für das Speicherverfahren des Indexes in Forum von binären Bäumen gilt. Grundsätzlich gelten erstmal die folgenden Vorraussetzungen: Die Speicherung erfolgt auf den Datenseiten sequentiell in Schlüsselreihenfolge. Im Index wird dann für jede Daten-Seite der Schlüssel zusammen mit der Tupel-ID des letzten Records jeder Datenseite gespeichert, dies ist dann jeweils der Schlüssel mit dem größten Wert. Das k bezeichnet die für den Baum vereinbarte Schlüsselanzahl. Der Baum muss außerdem zwingend sortiert sein und jeder Knoten enthält entweder k oder 2k Knoten, dadurch resultiert, dass der Weg von der Wurzel bis zu jedem Blatt gleich lang ist. Besonders positiv wirkt sich diese Struktur dann aus, wenn wir nun zu einem Schlüssel im Baum eine Tupel-ID finden wollen, der erste Zugriff ermöglichst uns (bei k=100) bereits das Auffinden von 100 TIDs, mit dem zweiten werden es dann schon 10.000! Das bedeutet, dass die Anzahl der auffindbaren Schlüssel exponentiell steigt und mit extrem wenigen Zugriffen extrem viele Schlüssel aufgefunden werden können. Aus diesen sehr erfreulichen Eigenschaften resultiert sicherlich die weite Verbreitung von binären Bäumen im Bereich der Indexe, der Zugriff ist so schnell wie bei kaum einem anderen Verfahren – nicht nur bei Datenbanken, auch bei anderen Verfahren in der Informatik haben sich binäre Bäume als hervorragende Speicherstrukturen erwiesen. Es ist problemlos möglich in diesem Baum ab einem bestimmten Knoten Bereichsanfragen zu starten, da vom Prinzip des Baumes her schon alles exakt sortiert ist, ist natürlich auch ein sortierter Zugriff problemlos möglich. Großes Problem der binären Bäume: Reorganisation: Die Daten in der Datenbank verändert sich, der Index muss aktualisiert werden, hierfür muss der komplette Baum neu aufgebaut werden, dies verursacht hohe Kosten in Bezug auf Plattenzugriffe, Speicher- und Cacheauslastung, sowie natürlich der Prozessorauslastung. Dieses generelle Problem der Indizierung ist bei Bäumen besonders prägnant, da ein balancierter Baum eigentlich schon bei kleineren Änderungen komplett neu aufgebaut werden muss, damit er auch balanciert bleibt. Je öfter der Index geändert wird, desto größer ist das Problem. Relationale DBMSe benutzen dieses Verfahren trotz allem vorherrschend, zur Entschärfung des Problems wird die Indizierung jedoch für besonders häufig geänderte Indexe auch mit anderen Verfahren durchgeführt. Da dies jedoch DBMS-interne Vorgänge sind, hat der Datenbankadministrator auf diese Vorgänge kaum Einfluss. Optimizer (checked) Aufgaben des Optimizers eines Datenbankmanagementsystems ist die Ermittlung von Zugriffspfaden. Darüber hinaus soll ein möglichst optimaler Zugriffspfad ermittelt werden. Dieser günstige Pfad führt dann zu einer Beschleunigung der Ausführungsgeschwindigkeit. Zur Ermittlung des günstigen Pfades gibt es im wesentlichen 3 Möglichkeiten: Die erste ist auch gleich die trivialste, der Optimizer sucht einfach „drauf los“ und der nächst kürzeste Pfade wird ausgewählt, ansonsten gibt es hierbei keine konkreten Kriterien zur Pfadfindung. Bei den zunächst eingesetzten regelbasierten Optimizern wurden bestimmte Regeln eingesetzt, um die günstigsten Zugriffspfad zu finden. Neuerdings schließlich, bei den kosten-basierten Optimizern werden die jeweils anfallenden Kosten für Plattenzugriffe, Prozessorbelastung und den Arbeitsspeicher abgeschätzt und so ein kosten- und leistungsoptimaler Zugriffspfad ausgewählt. Im Relation zu den Optimizern, die unsystematisch und ohne Regeln vorgingen, waren die regelbasierten Optimizer schon ein gewaltiger Fortschritt, während herkömmliche Optimizer noch sequentiell meist die kompletten Tabellen auslesen mussten, erlauben regelbasierte Optimizer meist direktere Zugriffspfade, die sich meist an der Reihenfolge der SELECTAnweisungen im aktuellen Query ausrichtet und danach auf die möglichst häufige Ausnutzung von Indexen zur Erhöhung der Geschwindigkeit abzielt. Nutzt man als Datenbankadministrator einen regelbasierten Optimizer, so kann man dessen Ausführung meist dadurch beeinflussen, in dem man Indexe so setzt, dass sie für häufig benutzt Abfragen dem Optimizer direkt zur Vefügung stehen. Unter Umständen wertet der Optimizer auch die Reihenfolge der Tabellen in einer SELECT-Anweisung aus. Es kann daher sinnvoll sein, die SELECT-Anweisungen so zu formulieren, dass der regelbasierte Optimizer, entsprechend der verwendeten Regeln möglichst direkt über Indexe auf Daten zugreifen kann und nicht sequentiell lesen muss. Es wird deutlich, dass es keine gute Lösung ist, wenn der Optimizer an Hand von Regeln vorgeht und die Kosten außer Acht lässt, dies wird häufig zu Systemüberlastungen führen, und auch der Index und optimierte SELECT-Anweisungen werden hier wenig helfen. Daher sind kosten-basierte Optimizer vorzuziehen, da diese zunächst keine starren Regeln verwenden sondern alle Möglichkeiten in Betracht ziehen und an Hand der geschätzten Kosten einen optimalen Zugriffspfad auswählen. Eine Möglichkeit, den Optimizer nicht ständig aufzurufen wäre beispielsweise, die bereits ermittelten Zugriffspfade abzuspeichern, denn häufig werden dieselben Anfragen gestellt. So kann der Optimizer deutlich entlastet werden und nur bei „ganz neuen“ Anfragen wird er noch benötigt. Grade auch kosten-basierte Optimizer, die die Systemlast reduzieren sollen, verwenden solche Verfahren. Replikation (checked) Bei der Replikation von Datenbanken bzw. Datenbankknoten ist das Ziel durch das Kopieren bzw. Duplizieren von Datenbanken gezielte Redundanz zu schaffen. Mit der Replikation kann eine deutlich größere Verfügbarkeit der Daten geschaffen werden, des weiteren kann ein Lastausgleich einzelner Knoten leicht implementiert werden, woraus eine Verbesserung des Datendurchsatzes resultiert. Bei lesenden Transaktionen ist die Geschwindigkeit durch lokal vorgehaltene Kopien der Datenbanken um ein vielfaches höher, als würden die Daten über ein Netzwerk übertragen. Als besonderes Goodie ist es bei verteilten Informationssystemen möglich, z.B. Laptops/PDAs/Smartphones von Unterwegs per GPRS+GSM/UMTS in eine Firmendatenbank einzuklinken! Dem gegenüber steht natürlich ein erhöhter Systemaufwand und auch eine erhöhte Kommunikation zum Beispiel per TCP/IP über ein IP-Netzwerk. Für lesende Transaktionen ist die Replikation als völlig unproblematisch zu sehen, hier bietet sie nahezu nur Vorteile, der kaum Nachteile gegenüber stehen, problematisch wird es allerdings bei ändernden Transaktionen, da hier ALLE Replikate möglichst in Echtzeit aktualisiert werden müssen, was einen hohen Kommunikationsaufwand bedeutet, bei langsamen oder abgebrochenen Übertragungen kommt es zu empfindlichen Konsistenzproblemen, da die Daten in verschiedener Aktualität auf verschiedenen Knoten liegen. Es wird deutlich, dass geeignete Verfahren der Replikation, aber grade auch in Bezug auf ändernde Transaktionen geeignete Verfahren der Synchronisation, spricht Transaktionskontrolle in verteilten Informationssystemen von Nöten sind. Auf alle Fälle ist die Verteilung sorgfältig zu planen und auf Grund der angerissenen Probleme gilt, dass man sie nur so viel wie nötig einsetzen sollte. Sämtliche Verfahren der Replikation hier in Gänze aufzuführen, würde sicherlich den Rahmen dieser Zusammenfassung sprengen, ich möchte daher kurz einige wesentliche Aspekte skizzieren und dann schließlich auf deren konkrete Implementierung in konkreten relationalen Datenbanksystemen zu sprechen kommen. Verbreitet für die globale Transaktionsunterstützung ist das 2-Phasen-Commit-Protokoll, bei selbigem eine Transaktion von einem Koordinatorknoten aus (der die Transaktion initiiert) nur dann durchgeführt wird, wenn alle anderen Knoten melden, dass die für diese Transaktion bereit sind. Nun muss auf den jeweiligen Knoten noch die Synchronisation gewährleistet sein, dies geschieht meist mit Sperrverfahren, wie dem Zwei-Phasen-Sperrprotokoll oder dem Zeitstempelverfahren. Alternativ setzen Systeme wie Microsoft Navision DB auch optimistische Synchronisationsverfahren ein, wodurch die Deadlockgefahr deutlich herabgesetzt/eliminiert wird. Bei den Verfahren der Replikation als solches gibt es zwei verschiedene Verfahren, die semantischen und die syntaktischen. Ich möchte hier kurz die syntaktischen vorstellen, hier wäre zunächst Primary Copy zu nennen, wo von einem Knoten, auf dem Änderungen erfolgten, die Kopien an die anderen Knoten verteilt werden schließlich wäre im Bereich des unstrukturierten Quorums das Majority Consensus Verfahren zu nennen, wobei hier eine Abstimmung von dem Knoten der ursprünglichen Knoten durchgeführt wird, der die Transaktion initiiert, ist die Mehrheit der Knoten für die Replikation bereit, so wird diese durchgeführt. Ähnlich läuft es im strukturierten Quorum, konkret beim Tree Quorum ab, nur dass hier die Mehrheit der Ebenen bereit sein muss. Dynamische Quoren bieten den großen Nachteil der Netzpartitionierung, sind aber flexibler, da sie die jeweils aktuelle Knotenanzahl in die Abstimmung mit einbeziehen. Alternativ gibt es noch das ROWA Verfahren, bei dem eine Transaktion von jedem beliebigen Knoten lesen kann, die Replikate aber auf allen Knoten ändern muss. Abschließend möchte ich noch kurz auf die eingesetzten Verfahren in Oracle und DB2 eingehen. Oracle unterstützt das Prinzip der Basic Replication, welches dem oben genannten Primary Copy Prinzip entsprecht, des weiteren wird Advanced Replication untersützt. Hier gibt es eine symmetrische Replikation, wobei alle Replikate gleichberechtigt sind und die Änderungen an die übrigen Replikate asynchron weitergereicht werden. Der DELETE-Konflikt wird nicht behandelt. Änderbare Snapshots sind eine Kombination der beiden grade genannten Verfahren in Oracle, ROWA ist hingegen nicht vorgesehen. Auch in DB2 ist ROWA nicht vorgesehen. Wie auch in Oracle wurde aber das Primary CopyVerfahren implementiert. Bei der asynchronen Aktualisierung und Einsatz einer Primärtabelle wird die Netzlast verringert, weil Änderungen zusammengefasst und erst dann übertragen werden. Beim Verfahren der asynchronen Aktualisierung unter Einsatz einer Primärtabelle mit änderbarem Kopien übernimmt zwar die Primärtabelle die Verteilung der Änderungen, die Replikate können jedoch trotzdem geändert werden und schicken dann bei einer Änderung einer entsprechende Mitteilung an die Primärtabelle. ANSI/SPARC-Architektur (checked) Laut der ANSI/SPARC-Architektur gibt es drei Ebenen: Die erste Ebene ist die externe Sicht, die Benutzersicht, die zweite ist konzeptionelle, die globale Sicht, die dritte ist die interne Sicht, die Speichersicht. Das hierarchische Modell (checked) In diesem Modell nimmt der Benutzer die Daten in einer hierarchischen Form wahr und muss durch die Daten navigieren. Ein Vertreter solcher Datenbankmanagementsysteme ist IMS/DB. Die Struktur der Datenbank befindet sich dabei in einer Assemblerdatei, der IMSStrukturdatei. Sichten, z.B. für die Rechteverwaltung, lassen sich über PCB-Files realisieren. In PL/I stehen entsprechende Funktionen zur Navigation durch eine hierarchische Datenbank zur Verfügung! DRDA (checked) DRDA ist eine von IBM 1999 festgelegte Architekturbeschreibung, die eine Menge von Regeln bzw. Protokollen umfasst, um plattformübergreifend auf verteilte Daten zugreifen zu können. Hierfür werden Methoden zur koordinierten Kommunikation zwischen verteilten relationalen Datenbankmanagementsystemen definiert. Für den Benutzer bzw. die Anwendung wirkt der Zugriff hingegen genau so als wäre es ein lokaler Zugriff. Verschiedene Hersteller bieten bereits APIs an, die sich an DRDA orientieren, wobei Vorreiter hierbei verständlicher Weise das aus dem selben Hause stammende DB2 ist. DRDA realisiert den verteilten Zugriff im wesentlichen über drei Funktionen: Der Database Server ist hierbei der eigentliche Datenbankserver, miteinander in Kommunikation stehen der Application Server, der auf dem selben Rechner wie der Database Server läuft, und der Application Requester, der Anforderungen aus Anwendungen von Client-Rechnern aus durch das Netz an den Application Server schickt. Der Application Server seinerseits reicht die entsprechenden Anforderungen an den Database Server weiter, der mit Hilfe des DBMS die gwünschten Transaktionen vornehmen kann. DRDA bietet insgesamt 5 Ebenen für die Unterstützung der Verteilung. Da wäre als erstes die User-Assisted Distribution zu nennen, hier verteilt der Benutzer selbst die Daten, ihm stehen nur die beiden Funktionen Daten zu extrahieren und Daten einzuladen zur Verfügung. Beim DRDA-Remote-Request kann ein einziger Rechner, die so genannte Single Unit of Work, eine einzelne SQL Anweisung an einen entfernten Datenbankserver stellen. Der selbe Fall liegt vor, wenn mehrere SQL-Anweisungen in einer Transaktion an einen Server geschickt werden, nur wird hier von der Remote Unit of Work gesprochen statt von der Single Unit of Work, wobei jedoch pro SQL-Anweisung nur ein DBMS angesprochen werden kann. Im vierten Fall, der Distributed Unit of Work, können mehrere SQL-Anweisungen gegen mehrere entfernte DBMS gestellt werden, wobei auch hier pro SQL-Anweisung nur ein DBMS angesprochen werden kann, sollen mehrere DBMSe auf einmal in einer SQLAnweisung angesprochen werden, so muss die vollständige Verteilung mit Distributed Request verwendet werden. Data Warehouse (checked) Der Ansatz des Data Warehouse beruht auf dem Wunsch einer präzisen Entscheidungsfindung bei Geschäftsvorfällen, hier wird ein so genanntes Decision Support System (kurz DSS) gewünscht. Erreicht wird dies durch die interaktive Verbindung von Regeln sowie Intuition von Experten mit dem Ziel einer ad hoc-Modellierung jeweils neuer Situationen unter Simulation eines kleinen Ausschnitts der realen Welt. Ein Decision Support System bietet dabei verschiedene Reports, testet Hypothesen an Hand konkreter Zahlen, erzeugt Modelle an Hand historischer Zahlen und erkennt aus selbigen und aktuellen Daten unbekannte neue Trends. Die Analysen im Data Ware House greifen mit den so genannten OLAP-Anforderungen, den Anfragen aus dem OnLine Analytical Processing auf die Datenbank zu, daraus ergibt sich für die Datenbank, dass die Daten themenorientiert zusammengefasst sein müssen und die Daten pro Abfrage relativ groß sind. Da meist auf historische Daten zugegriffen wird, kann meist von fast ausschließlich lesenden Anfragen ausgegangen werden. Und da die zur Verfügung stehenden Daten auch aktualisiert werden müssen, muss ein regelmäßiges Update aus dem OLTP-System, dem System des OnLine Transaction Processing, welches die aktuellen Geschäftsvorfälle unterstützt, stattfinden. Zunächst werden die benötigten Daten aus dem OLTP-System extrahiert und ins Data Warehouse importiert. Für diesen Datentransfer gibt es verschiedene Möglichkeiten, zunächst einmal kann mit normalen SQL-Anweisungen in off-Zeiten des OLTP gearbeitet werden, hier können auch bereits Pre-Joins vorgenommen werden, um die Performance zu steigern, zur Performance-Steigerung komme ich gleich noch detaillierter. Eine weitere Möglichkeit die Daten ins Data Warehouse zu portieren ist die SnapshotMethode, bei der eine bzw. mehrere Tabellen per Replikation übertragen werden. Sollen die Daten zur Laufzeit des OLTP importiert werden, so bietet sich vor allem die Methode des Log-Sniffings an, man benötigt hier ein Programm, dass die Log-Files des OLTPS entsprechend auf Änderungen durchsucht. Mittels Insert-, Update- oder Delete-Trigger ist des weiteren alternativ ein direktes Übernehmen jeder Änderung möglich. Stehen spezielle Data Extract Programme zur Vefügung, so sollten diese in den Off-PeakZeiten des OLTP verwendet werden. Wie bereits oben geschildert, ist mit einer großen Datenmenge pro lesender Transaktion zu rechnen. Um die Transaktionsdauer nicht unnötig in die Länge zu treiben, bietet sich hier die Denormalisierung der OLTP-Datenbank an, so werden während des Ladens bereits PreAggregate angelegt und Pre-Joins über Tabellen angelegt, um die Anzahl der JoinOperationen im laufenden Betrieb zu minimieren. Pre-Joins führen zum STAR- oder gar zum SNOWFLAKE-Schema. Soll die Zeit in die Analysen mit einbezogen werden, so werden oft multidimensionale Datenbanken eingesetzt. Datenbank-Entwurf (checked) Die wesentliche Fragestellung beim Datenbankentwurf ist, WIE man von einer Vorstellung/Idee schließlich zu einem fertig gestellten Datenbanksystem kommt. Dabei muss sichergestellt werden, dass die Anforderungen an das System über einen langen Zeitraum erfüllt werden und dass das System nicht nach 2 Wochen unter Volllast zusammenbricht oder nur noch im Scheckentempo läuft. Als Vorgehensweise wird meist ein 4 Phasen-Modell, ähnlich den Modellen des Software Engineering gewählt: Analyse, konzeptueller Entwurf, logischer Entwurf und physischer Entwurf. Dieses Modell wird gewählt, um möglichst früh Anforderungen an die Datenverarbeitung und Informationsverarbeitung definieren zu können, wobei man sich so spät wie möglich auf ein konkretes Datenbanksystem festlegen sollte und erst ganz am Ende Hardware und Betriebssystem entsprechend der Gesamtanforderungen auswählen sollte. Um in der Analysephase den Informationsbedarf ermitteln zu können, erstellt man sich zunächst drei Verzeichnisse: Ein Datenverzeichnis, in dem man alle zu speichernden Daten mit deren Namen und Eigenschaften auflistet. Dann erstellt man sich ein Operationsverzeichnis, in dem man die Verwendung der Daten auflistet, jeweils mit Eingabe und Ausgabeinformationen der Operationen. Schließlich erstellt man ein Ereignisverzeichnis in dem festgehalten wird, welches Ereignis eine Operation aufruft, hieraus ergeben sich dann schließlich die Abläufe. In der konzeptuellen Phase versucht man, Sachverhalte und Gesetzmäßigkeiten in eine formale Gestalt zu überführen, meist tut man dies mit Hilfe eines semantischen Modells. Dieses Modell soll den Sachverhalt vollständig und korrekt beschrieben, wobei diese Beschreibung unabhängig von einem bestimmten Datenbanksystem sein soll. Ist ein relationales Modell auch für semantische Zwecke geeignet, so kann es als Ausgangspunkt sukzessiver Transformationen gesehen werden, hierfür müssen Redundanzen und Anomalien (Änderungsanomalie, Löschanomalie und Einfügeanomalie) beseitigt werden, wofür sich besonders die Normalisierung eignet - für neue und bereits bestehende Modelle. Die Modellierung kann mit dem ER-Modell durchgeführt werden, doch werden hier nur statische Aspekte erfasst, für dynamische Eigenschaften eignen sich objektorientierte Darstellungen. Oft muss in der konzeptuellen Phase identifiziert werden, welche Anwendergruppen es gibt, welche Aufgaben diese erfüllen und resultierend daraus, welche Sicht auf die Datenbasis sie benötigen. Am besten verwendet man hierfür eine 2-Phasen-Methode, in der ersten Phase werden die verschiedenen Anwendergruppen identifiziert und für jede Anwendergruppe wird ein eigenständiges konzeptuelles Modell erstellt. In der zweiten Phase schließlich, werden die Modelle zu einem ganzen integriert. Diese Integration beschreibt nach einigen Modifikationen schließlich den logischen Entwurf, der bereits auf die logischen Strukturen eines konkreten Datenbanksystems ausgelegt ist. Im physischen Entwurf gilt zunächst einmal die Grundregel, dass die Kosten durch den physischen Zugriff zu minimieren sind. So müssen die Konstrukte optimal auf die Platten aufgeteilt werden, Partitionierungen, Clusterungen und Indexe müssen geplant und implementiert werden. Schließlich müssen nach der Installation eines Datenbanksystems die Installationsparameter angepasst werden, so die Directory-Größe, temporärer Speicherplatz, Reservierungen im RAM, Cache für Datenseiten, Cache für DB-Kommandos usw.. Schließlich müssen die bereits geplanten Sichten mit der Benutzerverwaltung und der Rechtevergabe implementiert werden. Gegebenenfalls sollten dann noch benötigte Integritätsregelen und DB-Prozeduren verwirklicht werden. Vor Inbetriebnahme im Produktivbetrieb sollte das System ausführlich getestet werden! Objektorientierte Datenbanken (checked) Objektorientierte Datenbanken wurden zunächst am Beispiel von Versant behandelt. Versant bietet eine JAVA und eine C++-Schnittstelle, so dass man aus üblichen objektorientierten Programmen direkt auf eine Versanddatenbank zugreifen kann. Es werden auch komplexe Datenmodelle unterstützt. Multithreading und Multi-ClientConnects werden verwendet. Des weiteren stehen spezielle Vorrichtungen für die ausgewogene Nutzung von Netzwerkressourcen und für das Wideranlaufen nach Fehlern zur Verfügung. Es ist möglich, einem Programm bei bestimmten Ereignissen in der Datenbank eine EventNotification zu schicken. Versant vergibt implizit Object-IDs, wodurch jedes Objekt in der Datenbank automatisch eine eindeutige Identität hat. Für den Entwickler stehen verschiedene Tools und Konsolen für Abfragen und Konfigurationen bereit. Ein bestehendes oder auch neu erstelltes JAVA-Programm lässt sich relativ leicht um eine persistente Speicherung mit Versant erweitern: Ein Session-Objekt für die Verbindung mit der Datenbank wird zu Beginn des Programms angelegt: TransSession transaction = new TransSession(“Datenbank“); Änderungen an den Objekten werden in die Datenbank übernommen: transaction.commit(); Doch woher weiß Versant nun, welche Objekte persistent gespeichert werden sollen und welche nicht? Die Klassen bzw. deren Objekte, die gespeichert werden sollen, werden in einer Konfigurationsdatei mit dem Namen config.jvi vermerkt, und zwar nach der Syntax: p Klasse (p steht für persistent) Für die Benutzung muss noch eine entsprechende Klasse von Versant geladen werden. Es existiert hier also kein direkter Code um in der Datenbank Objekte zu speichern und zu ändern. Entsprechend objektorientierter Prinzipien unterstützt das DBMS Vererbung, Referenzen und Assoziationen. Auch eine Anfragesprache ist in Versant implementiert, diese ist SQL mit seiner SelectAnweisung sehr ähnlich, erhält jedoch ein paar Erweiterungen speziell für Objektmengen.