Institut für Informationssysteme Prof. Dr. H.-J. Schek F. Akal, K. Jauslin K. Haller, M. Mlivoncic Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of Technology Zurich Informationssysteme IS-K WS 2003/2004 Übung 8 Beispiellösung Aufgabe 1: Servermodelle In den Teilaufgaben a) – d) sind vier Anwendungsbeispiele für den Einsatz von Datenbanken gegeben. Überlegen Sie, welche der in der Vorlesung vorgestellten Servermodelle im jeweiligen Fall verwendet werden sollten. a) Ein Softwaresystem für betriebswirtschaftliche Anwendungen soll so gestaltet werden, dass eine möglichst grosse Anzahl von Benutzern effizient unterstützt wird. Für die Datenhaltung sind dabei sehr leistungsstarke Rechner vorgesehen, die aber nicht noch zusätzlich durch Anwendungsfunktionalität belastet werden sollen. Die Benutzer sollen mit Hilfe ihres PCs am Arbeitsplatz Zugriff auf das gesamte System bekommen. Diese PCs sind dabei lediglich für die Realisierung der grafischen Benutzeroberfläche zuständig, aber ebenfalls nicht für die Anwendungsfunktionalität. In diesem Falle bietet sich eine Drei–Stufen–Architektur an. Auf der ersten Stufe (Präsentationsdienste) befinden sich dabei die PCs an den Arbeitsplätzen als Präsentationsserver. Die zweite Stufe (Anwendungsdienste) besteht aus mehreren Applikationsservern. Damit können die einzelnen Anwendungs–Module auf diese Server verteilt werden. Auf der dritten Stufe (Datenbankdienste) schliesslich befinden sich die Datenbankserver. So wird gewährleistet, dass Datenhaltung, Anwendungsfunktionalität und die Realisierung der grafischen Oberfläche voneinander getrennt werden können. Im Falle eines einzigen, zentralen Datenbankservers entspricht diese Architektur beispielsweise dem Software–System SAP R/3, das eine Vielzahl von betriebswirtschaftlichen und organisatorischen Aufgaben innerhalb eines Unternehmens abdeckt. b) Ein Weinliebhaber möchte seinen Weinkeller (ca. 300 Flaschen) mit Hilfe einer Datenbank verwalten, damit er jederzeit über den aktuellen Bestand informiert ist und rechtzeitig neue Bestellungen aufgeben kann. Für die Weinkeller-Datenbank, bei der lediglich ein Benutzer auf eine nicht allzu grosse Datenmenge zugreift, ist eine Ein–Stufen–Architektur eines Single User DBMS die geeignete Architekturform. Als mögliches Beispiel hierfür kann u.a. MS Access genannt werden. c) In einem Bibliotheks–Informationssystem stehen den Kunden im Lesesaal mehrere, schon recht veraltete PCs zur Verfügung, um Literatur-Recherchen auf der Bibliotheks-Datenbank durchzuführen. Wo sollten in diesem Fall sinnvollerweise die Benutzer–Anfragen ausgeführt werden? In diesem Fall bietet sich eine Zwei–Stufen–Architektur, bei der die PCs als Clients mit dem Datenbank– Server verbunden sind, an. Da die Server–Prozesse verschiedener Clients nicht notwendigerweise voneinander geschützt sein müssen, ist eine symmetrische Multiple–Server–Architektur zwar möglich, jedoch nicht unbedingt nötig. Ebenfalls geeignet sind monolithische, multithreaded Server–Architekturen, da die Gefahr fehlerhafter Clients bei einfachen Anfragen nicht sonderlich gross ist. Eine asymmetrische Multiple–Server–Architektur scheint jedoch hier am besten geeignet, da der Overhead durch die Verwaltung der Warteschlangen bei einfachen, kurzen Anfragen und wenigen Benutzern nicht allzu stark ins Gewicht fällt. In diesem Szenario, bei dem sehr einfache und langsame Clients mit einem leistungsfähigen Server verbunden sind, bietet es sich an, die gesamte Anfrageauswertung auf dem Server durchzuführen, und die Clients nur zur Realisierung der grafischen Oberfläche zu verwenden. d) In einer grossen Bank müssen die täglich anfallenden Daten transaktionssicher und effizient in eine Datenbank eingefügt werden bzw. Anfragen effizient beantwortet werden. Insbesondere soll dabei eine hohe Verfügbarkeit des Systems garantiert werden (z.B. darf natürlich eine Fehlbedienung an einem Terminal nicht den kompletten Betrieb beeinträchtigen). Für die Tagesgeschäfte der Bank eignet sich beispielsweise ein grosser Mainframe–Rechner (Ein–Stufen– Architektur, Multi User DBMS), auf dem mehrere Prozesse parallel ablaufen können. Eine solche Architektur besitzt den Vorteil, dass ein grosses Mass an Betriebssicherheit gewährleistet werden kann. Da 1 sämtliche Dienste zentral an einem Ort definiert sind, lässt sich so der physische Zugang zum System einfach kontrollieren. Allerdings besteht auch die Möglichkeit, eine Zwei–Stufen–Architektur anzuwenden. Aus Sicherheitsgründen sollte dies jedoch keine Multithreaded Server–Architektur sein, da hier u.U. die geforderte hohe Verfügbarkeit nicht garantiert werden kann (Ausfall eines Clients kann gesamtes System lahmlegen). Da dies bei einer symmetrischen Multiple–Server–Architektur nicht der Fall ist, kann hier eine grössere Verfügbarkeit realisiert werden. Auch die asymmetrische Multiple–Server–Architektur, die eine geschützte Ausführung der Clients ermöglicht, eignet sich für diese Anforderungen. Aufgabe 2: Client-Server Architektur von Datenbanksystemen /RJ :ULWHU 3URFHVV $3 &OLHQW DA 1 6HUYHU 3URFHVV 'DWDEDVH%XIIHU &/ %HWULHEVV\VWHP %HWULHEVV\VWHPQ 1HW]ZHUN Abbildung 1: Dedicated Server Architektur a) In einer Datenbank existiere eine Tabelle gemäss folgender DDL: CREATE TABLE MY_TERMS ( ident NUMBER NOT NULL, term VARCHAR2(50) NOT NULL, PRIMARY KEY (ident, term), UNIQUE (term), CHECK (IDENT > 0) ); Gegeben sei dazu dieses Embedded SQL/C-Programm: #include <string.h> #include <string> #include <iostream> /* Declare DB variables. */ EXEC SQL BEGIN DECLARE SECTION; VARCHAR username[20]; // *** line 1 VARCHAR password[20]; // *** line 2 VARCHAR db_string[20]; // *** line 3 EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE DB_NAME DATABASE; /* Include the SQL Communications Area */ #include <sqlca.h> /* Declare prototype for ORA error handler */ 2 void sql_error(char* msg); int main() { EXEC SQL BEGIN DECLARE SECTION; char oldterm[256]; // *** line 4 char newterm[256]; // *** line 5 unsigned int ident; // *** line 6 EXEC SQL END DECLARE SECTION; const std::string MyOldTerm("ISK macht Spass"); const std::string MyNewTerm("ISK ist doof!!!"); strcpy((char *) db_string.arr, "STUD"); // *** line 7 db_string.len = strlen((char *) db_string.arr); strcpy((char *) username.arr, "*****"); // *** line 8 username.len = strlen((char *) username.arr); strcpy((char *) password.arr, "********"); // *** line 9 password.len = strlen((char *) password.arr); /* Register sql_error() as the error handler. */ EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--\n"); EXEC SQL CONNECT :username IDENTIFIED BY :password AT DB_NAME USING :db_string; // *** line 10 ident = 0; strcpy( oldterm, MyOldTerm.c_str() ); // *** line 11 EXEC SQL AT DB_NAME SELECT IDENT INTO :ident FROM MY_TERMS WHERE TERM = :oldterm FOR UPDATE; // *** line 12 if (ident > 0) // we have found a positive identifier for our string { strcpy( newterm, MyNewTerm.c_str()); // *** line 13 EXEC SQL AT DB_NAME UPDATE MY_TERMS SET TERM = :newterm WHERE TERM = :oldterm; // *** line 14 EXEC SQL AT DB_NAME COMMIT; // *** line 15 return 0; } else { EXEC SQL AT DB_NAME ROLLBACK; // *** line 14a return -1; } return 0; } void sql_error(char *msg) { EXEC SQL WHENEVER SQLERROR CONTINUE; std::cerr << "error: %s" << msg << std::endl; std::cerr << sqlca.sqlerrm.sqlerrmc << std::endl; EXEC SQL AT DB_NAME ROLLBACK; 3 return; } Dieses Programm wird auf der in Abbildung 1 dargestellten Architektur durchgeführt. Wichtige Ereignisse mit Auswirkungen auf den DBMS-Server sind mit Nummern in den Kommentaren hinter den Code-Zeilen gekennzeichnet. Benutzen Sie diese Nummern, um den Ablauf des Programmes ausgehend von der vorgegebenen Darstellung in einer Folge von Skizzen möglichst genau zu veranschaulichen. Vergessen Sie nicht, Ihre Darstellungen zu erläutern. RXU $3 FOLHQW &OLHQW DA 1 our DA &/ /RJ :ULWHU 3URFHVV 6HUYHU 3URFHVV 'DWDEDVH%XIIHU &/ %HWULHEVV\VWHP %HWULHEVV\VWHPQ 1HW]ZHUN username:“*****“ password:“********“ db_string:“STUD“ oldterm: newterm: ident: Abbildung 2: Dedicated Server Architektur: line 1 bis line 9 Nach dem Start des Client werden zunächst die Host-Variablen des Client-Programmes im Speicherbereich des Client angelegt. Dort werden sie auch in den Zeilen 7 bis 9 initialisiert, siehe Abbildung 2. In den Speicherbereich zum Datenaustausch mit der Client-Library werden sie erst mit dem CONNECTStatement aus Zeile 10 weitergegeben (by reference), wie in Abbildung 3 dargestellt. Über einen Aufruf des Client-Interface (OCI) werden die Daten und das Statement an den Datenbank-Server über ein proprietäres Protokoll kommuniziert. Dort wird dann auch gemäss der Dedicated-Server-Architektur ein neuer Server-Prozess für den Client gestartet. In Abbildung 4 ist wieder das gleiche Prinzip veranschaulicht: Zuerst werden die Host-Variablen im Speicherbereich des Client verändert, und mit dem ESQL-Statement werden sie auch in den Speicherbereich der Client Library gemappt. Die Abbildung zeigt weiterhin, dass die Seite, die das von uns angefragt Tupel enthält, in den Datenbank-Puffer geladen wird. Durch die FOR UPDATE Klausel wird sie auch gleich exklusiv für unseren Client und dessen Transaktion gesperrt. Die Verarbeitung des UPDATE ist dann in Abbildung 5 dargestellt. Dabei wird nur die Seite im Datenbank-Puffer verändert, die Änderungen sind bislang noch nicht auf Plattenspeicher geschrieben worden. Lediglich der Log-Writer Prozess hat ein Before-Image dieser Datenbankseite zu Recovery-Zwecken auf dem Log hinterlegt. Mit dem COMMIT-Statement aus Zeile 15 wird der Log-Writer Prozess erneut aktiviert, der dann die durch die Transaktion vorgenommenen Änderungen zusammen mit dem COMMIT-Marker in das REDO-Log schreibt. b) Inwieweit würde sich der von Ihnen geschilderte Ablauf ändern, wenn das Client-Programm von einem asymmetrischen DBMS-Server verarbeitet würde? Die asymmetrische Server-Architektur zeichnet sich durch den zusätzlichen Dispatcher-Prozess und eine fest vorgegebene Anzahl an meist schon vorab gestarteten Server-Prozessen aus. Der Dispatcher ordnet für jedes an das DBMS geschickte SQL-Statement dem Client und seiner Transaktion einen der verfügbaren Server-Prozesse zu, die dann die Anfrage verarbeiten. Dies heisst auch, das verschiedene Anfragen aus der gleichen Transaktion des selben Clients von verschiedenen Server-Prozessen bedient werden können. 4 RXU $3 FOLHQW &OLHQW DA 1 our DA &/ /RJ :ULWHU 3URFHVV 6HUYHU 2XU 3URFHVV 6HUYHU 3URFHVV 'DWDEDVH%XIIHU &/ %HWULHEVV\VWHP %HWULHEVV\VWHPQ 1HW]ZHUN &211(&7 :username:“*****“ :password:“********“ :db_string:“STUD“ username:“*****“ password:“********“ db_string:“STUD“ oldterm: newterm: ident: Abbildung 3: Dedicated Server Architektur 2: line 10 RXU $3 FOLHQW &OLHQW DA 1 our DA &/ &/ /RJ :ULWHU 3URFHVV 6HUYHU 2XU 3URFHVV 6HUYHU 3URFHVV 'DWDEDVH%XIIHU RXU[ORFNHG '%SDJH OLQH %HWULHEVV\VWHP %HWULHEVV\VWHPQ 1HW]ZHUN 6(/(&7 :username:“*****“ :password:“********“ :db_string:“STUD“ :oldterm:“ISK…“ //line 12 :ident:0 //line 12 username:“*****“ password:“********“ db_string:“STUD“ oldterm:“ISK…“ //line 11 newterm: ident:0 //line 11 Abbildung 4: Dedicated Server Architektur: line 11 und line 12 5 RXU $3 FOLHQW &OLHQW DA 1 our DA &/ &/ /RJ :ULWHU 3URFHVV 6HUYHU 2XU 3URFHVV 6HUYHU 3URFHVV 'DWDEDVH%XIIHU RXU[ORFNHG '%SDJH Å,6.«6SDVV´ %HWULHEVV\VWHP %HWULHEVV\VWHPQ 1HW]ZHUN 83'$7( :username:“*****“ :password:“********“ :db_string:“STUD“ :oldterm:“ISK…“ :newterm:“…doof“ //line 14 :ident:1 //result! username:“*****“ password:“********“ db_string:“STUD“ oldterm:“ISK…“ newterm:“…doof“ //line 13 ident:1 //result! Abbildung 5: Dedicated Server Architektur: line 13 und line 14 6 RXU $3 FOLHQW &OLHQW DA 1 our DA &/ &/ /RJ :ULWHU 3URFHVV 6HUYHU 2XU &RPPLW 3URFHVV 6HUYHU 3URFHVV 'DWDEDVH%XIIHU RXU[ORFNHG '%SDJH Å,6.«GRRI´ %HWULHEVV\VWHP %HWULHEVV\VWHPQ 1HW]ZHUN &200,7OLQH :username:“*****“ :password:“********“ :db_string:“STUD“ :oldterm:“ISK…“ :newterm:“…doof“ :ident:1 username:“*****“ password:“********“ db_string:“STUD“ oldterm:“ISK…“ newterm:“…doof“ ident:1 Abbildung 6: Dedicated Server Architektur: line 15 7 Im konkreten Fall des uns hier vorliegenden Client-Programmes bedeutet dies, dass für jeden EXEC SQL AT DB NAME-Aufruf die Anfrage zunächst an den Dispatcher-Prozess geschickt wird und dieser der Anfrage dann einen Server-Prozess zuordnet. In der nächsten Anfrage kann sich der Dispatcher durchaus auch für einen anderen Server-Prozess entscheiden. Der sonstige Ablauf bleibt unverändert. Aufgabe 3: Rechnerübung Thick-Client: JDBC-Anbindung Unter Ihrer IS-K-Kennung finden Sie eine ORACLE-Tabelle demo test mit Messwerten der Luftreinhaltung. Jedes Tupel der Tabelle enthält in den Attributen a0 bis a4 die 5 Messwerte, die innerhalb eines Tages zu bestimmten Zeiten t0 bis t4 erhoben werden. Ein Messwert ist dabei als eine Fliesskommazahl in der Datenbanktabelle hinterlegt. Ein grösserer Wert bedeute eine höhere Luftverschmutzung. Implementieren Sie von Ihrem IS-K Account aus einen Java-Client, der in einem einfachen Histogramm die Verteilung der maximalen Messwerte der Tage über die Messzeitpunkte abträgt. Läge beispielsweise an 10 Tagen der maximale Messwert bei der Zeit t0 vor, so soll im Histogramm an der Stelle t0 der Wert 10 stehen. Als Lösung der Aufgabe ist der Java-Code sowie das berechnete Histogramm abzugeben. Tipp: Folgende Routinen und Datenstrukturen können zur erfolgreichen Bearbeitung der Aufgabe nützlich sein: • eine Verbindung zur IS-K-Datenbank erzeugen Sie mit folgendem Aufruf: DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver()); Connection conn = DriverManager.getConnection ("jdbc:oracle:thin:@xibalba49.ethz.ch:1521:STUD49", "<isk-db-kennung>", "<isk-db-passwort>"); • Klasse Connection mit der Routine createStatement, • Klasse Statement, • Klasse ResultSet. Bitte informieren Sie sich auf dem IS-K-Webserver weiter über die oben genannten Konstrukte. Der Verbindungsaufbau zur Datenbank ist in der ORACLE JDBC -Dokumentation beschrieben; die Beschreibung der drei genannten Klassen befindet sich in der Java JDK JDBC -Dokumentation. Insbesondere das Beispielprogramm in der ORACLE-Dokumentation zeigt den Umgang mit Statements und ResultSets. Die folgenden Befehle helfen Ihnen bei der Erstellung des Programms auf den Rechnern: Compilieren: javac < classname >.java Aufruf: java < classname > Umgebung einrichten: 1. Auf dem Windows Cluster im Raum IFW C31 ist bereits Java installiert. 2. Verbinden von Laufwerk U: mit dem Netzwerk–Share \\dbs.ethz.ch\sqlplus. 3. Öffnen Sie zunächst eine DOS Eingabeaufforderung. Man muss dann lediglich noch die Umgebungsvariablen ’Path’ und ’Classpath’ anpassen bzw. setzen. Dazu wurde ein Batch-File vorbereitet (U:\JDBC\setenv.bat). Nach dem Ausführen dieses Batch–Files können Sie mit den üblichen Java–Programmen ’javac’ resp. ’java’ arbeiten. Achtung : sobald Sie eine neue DOS-Eingabeaufforderung öffnen, müssen Sie das Batch-File wieder ausführen. 8 Die Ausführung des unten angegebenen Programmes ergibt folgendes Histogramm: t0 0 t1 0 t2 58 t3 0 t4 0 // You need to import the java.sql package to use JDBC import java.sql.*; class Luft { public static int MaxPos(float f0, float f1, float f2, float f3, float f4) { float max = f0; int pos = 0; if (f1 > max) { pos = 1; max = f1; } if (f2 > max) { pos = 2; max = f2; } if (f3 > max) { pos = 3; max = f3; } if (f4 > max) { pos = 4; max = f4; } return pos; } public static void main (String args []) throws SQLException { // Load the Oracle JDBC driver DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver()); // Connect to the database // You must put a database name after the @ sign in the connection URL. // You can use either the fully specified SQL*net syntax or a short cut // syntax as <host>:<port>:<sid>. The example uses the short cut syntax. Connection conn = DriverManager.getConnection ("jdbc:oracle:thin:@xibalba49.ethz.ch:1521:STUD49", "iskXX", "*****"); conn.setAutoCommit(false); 9 // Create a Statement Statement stmt = conn.createStatement (); // Select the complete table ResultSet rset = stmt.executeQuery ("SELECT a0, a1, a2, a3, a4 FROM demo_test"); // Iterate through the result and collect sums int Pos; int PosCount[] = new int[5]; Pos = 0; while (rset.next ()) { Pos = MaxPos(Float.valueOf(rset.getString("a0")).floatValue(), Float.valueOf(rset.getString("a1")).floatValue(), Float.valueOf(rset.getString("a2")).floatValue(), Float.valueOf(rset.getString("a3")).floatValue(), Float.valueOf(rset.getString("a4")).floatValue() ); PosCount[Pos] = PosCount[Pos] + 1; } System.out.println("Ergebnis: "); System.out.println(PosCount[0]); System.out.println(PosCount[1]); System.out.println(PosCount[2]); System.out.println(PosCount[3]); System.out.println(PosCount[4]); conn.commit(); } } 10