Data Cartridges für Oracle 8i Datenbanksysteme II

Werbung
Data Cartridges für Oracle 8i
am Beispiel
räumlicher Daten
Notizen zum Begleitkurs zur Vorlesung
Datenbanksysteme II
Sommersemester 2000
Universität Hannover
Institut für Informatik
Dr. H. H. Brüggemann
Carsten Kleiner
1
1 Einführung
Objekt-relationale Datenbanksysteme bieten einige der Vorteile von objekt-orientierten Systemen (benutzerdefinierte Datentypen, Objektidentität, Vererbung, Polymorphismus über late binding) ohne die Vorteile von traditionellen relationalen Datenbanksystemen aufzugeben. Ausführlich werden objekt-relationale DBMS u. a. in
[SB99] beschrieben.
Ab der Version 8.1.5 (wird im allgemeinen kurz Oracle 8i genannt) bietet das DBMS
Oracle einige objekt-relationale Konzepte an. Es kann jedoch (noch) keine Vererbung und damit auch kein Polymorphismus verwendet werden. Man hat jedoch die
Möglichkeit, eigene Datentypen zu definieren und deren effiziente Verwaltung direkt
in den Kern des Datenbankmanagementsystems zu integrieren (durch benutzerdefinierte Indexstrukturen und Anfragebearbeitung). Einen Datentyp (objekt-orientiert
auch Klasse genannt) zusammen mit seinen Operationen (Methoden) und Informationen zur effizienten Verwaltung realisiert man mithilfe eines sogenannten Data
Cartridge. Dies ist eine Sammlung von SQL-Skripten und Programmen in PL/SQL und
möglicherweise weiteren Programmiersprachen, die die o. g. Aufgaben erfüllen. Die
genaue Realisierung ist in [Ora99f] beschrieben, woraus auch Teile der folgenden
Kapitel stammen.
2 Data Cartridges für komplexe Datentypen
Um komplexe Objekte der realen Welt (das Spektrum reicht von einfachen 2-dimensionalen Punkten bis zu komplexen Sound- oder Bildsequenzen) in einer Datenbank
effizient verwalten zu können, benötigt man komplexe Datentypen sowie Möglichkeiten, mit diesen Datentypen zu operieren. Dazu dienen in Oracle 8i sogenannte
Data Cartridges (sinngemässe Übersetzung: abstrakte Datentypen). Die Inhalte werden direkt in den Datenbank-Server integriert, um diesen für die neuen Datentypen
zu erweitern.
Abbildung 1: Data Cartridge als Erweiterung des DBMS-Servers
Ein Data Cartridge wird als Paket (package) deklariert (mit create package) und
später implementiert (mit create package body).
2
create [or replace] package <Paketname> fauthid [current userjdefiner]g
as <Paketdeklaration>;
create [or replace] package body <Paketname> as <Paketdefinition>;
Innerhalb eines solchen Paketes müssen alle erforderlichen Routinen für den entsprechenden Datentyp vereinbart werden. In der Deklaration können dabei Funktionen, Prozeduren, Objekttypen und andere Datenbank-Objekte deklariert werden.
Diese können entweder mit den Zugriffsrechten des ausführenden Benutzers (current user) oder des Programmierers (definer) ausgeführt werden. In der Deklaration
werden die Prototypen der Funktionen und Prozeduren angegeben, die in der Implementierung (create package body) dann implementiert werden müssen.
In Oracle 8i gibt es einige vordefinierte neue Datentypen, um objekt-relationale Konzepte zu realisieren (siehe Vorlesung DBS I: Kollektionen [VARRAY, NESTED TABLE],
Referenzen [REF], Typen für grosse unstrukturierte Objekte [BLOB, CLOB]), sowie benutzerdefinierte Datentypen, die hier den Schwerpunkt bilden.
2.1 Komponenten eines Data Cartridge
Ein benutzer-definierter Objekttyp kapselt die zu einem Objekt der realen Welt
gehörigen persistenten Daten (Attribute) und auf diesen Daten definierte Operationen (Methoden) mit ihrer Signatur.
create [or replace] type <Typname> fauthid [current userjdefiner]g
as object (<Elementliste>);
Analog zur Deklaration eines Pakets können die Methoden mit den Zugriffsrechten des Anwenders oder des Programmierers ausgeführt werden. Die vereinbarten
Methoden zu einem Typ müssen dann in einem sogenannten Rumpf (type body) implementiert werden.
create [or replace] type body <Typname>
as f<Funktionsdefinition>j<Prozedurdefinition>g end
Hierbei ist zu beachten, dass es möglich ist, Methoden in anderen Progammiersprachen (zur Zeit C und Java) zu implementieren und im PL/SQL-Rumpf lediglich den
genauen Aufruf dieser Methode zu spezifizieren. Je nach gewählter Programmiersprache sind noch weitere Aktionen erforderlich, um die externen Funktionen dem
Datenbank-Server bekannt zu machen (siehe [Ora99j] für C-Funktionen und [Ora99g]
für Java-Methoden). Es ist ebenfalls möglich, Methoden durch externe Bibliotheken
(external library) zu implementieren.
Um den Zugriff auf die neuen Datentypen zu beschleunigen, können mithilfe des
extensible indexing benutzerdefinierte Indexe auf diesen Datentypen angelegt (siehe Kapitel 3.3) sowie mithilfe des extensible optimizer Methoden zur benutzerunterstützten Anfrageoptimierung vereinbart werden (siehe Kapitel 3.4). Letzteres ist
vor allem sinnvoll, wenn an das System Anfragen über mehrere Domains gestellt
werden sollen.
3
2.2 Grobe Schritte bei der Entwicklung eines Data Cartridge
Genrell erfolgt die Entwicklung eines Data Cartridge nach dem Diagramm in Abbildung 2.
Abbildung 2: Schema zur Entwicklung eines Data Cartridge ([Ora99f])
Je nach Komplexität des zu modellierenden Objekttyps können also maximal die
folgenden Schritte erforderlich sein:
• Definition von Objekttypen (create type, siehe 3.1)
• Implementierung der Methoden der Objekttypen (create type body, siehe 3.2)
• Definition und Implementierung eines Index (create indextype, siehe 3.3)
• Definition und Implementierung von Funktionen für eine erweiterte Anfrageoptimierung (analyze, siehe 3.4)
4
Diese einzelnen Schritte werden im nächsten Kapitel näher untersucht und beschrieben.
3 Entwicklung eines Data Cartridge
3.1 Definition von Objekttypen
Zunächst sind alle Objekttypen mit ihren gewünschten Attributen und Methoden zu
definieren.
create [or replace] type <Typname> fauthid [current userjdefiner]g
as object (<Attributvereinbarung>f,<Attributvereinbarung>g
f,<Methodendeklaration>g f,<Ordnungsfunktionsdeklaration>g
f,<Pragmadefinition>g );
/
Als Datentyp für die Attributvereinbarungen können Standarddatentypen oder vorher deklarierte benutzer-definierte Typen verwendet werden (zirkuläre Abhängigkeiten von Typen können durch Vorwärtsverweise (create type <Typname>;) aufgelöst
werden). Eine Attributvereinbarung hat die gleiche Syntax wie eine Spaltenvereinbarung einer relationalen Tabelle. Die Methodendeklarationen geben nur die Signatur
der Methoden an:
member function <Funktionsname> [(<Parameter>f,<Parameter>g)]
return <Typ>
member procedure <Prozedurname> [( <Parameter> f,<Parameter> g)]
Um Objekte benutzer-definierter Datentypen auch strukturell (und nicht nur auf
Gleichheit der Adressen) vergleichen und ordnen zu können, gibt es spezielle mapund order-Methoden, die spezielle inhaltliche Vergleiche implementieren können.
Zusätzlich zu objektbezogenen Methoden, die auf das zugehörige Objekt mit self
zugreifen können, ist auch die Vereinbarung statischer Objekttypmethoden möglich:
es ist dann member durch static zu ersetzen.
Um Funktionen optimal ausführen zu können, ist es hilfreich dem Datenbank-Server
mitzuteilen, ob und welche Seiteneffekte eine Methode hat. Dies geschieht in der
Typdeklaration durch Angabe von sogenannten Pragmas zusätzlich zu den Methodendeklarationen:
pragma restrict references (<Methodenname>,f[wnds,wnps,rnds,rnps,trust]g)
Durch Angabe von wnds garantiert man beispielsweise, dass die Methode keine
schreibenden Datenbankzugriffe ausführt (writes no database state). Näheres dazu
findet man in [Ora99j].
Wie solche selbstdefinierten Typen bei der Definition von Tabellen eingesetzt und
später in SQL angefragt werden können, wurde bereits im Kurs zu Datenbanksysteme I besprochen. Konstruktoren zum Erstellen von Objekten eines Objekttyps
werden von Oracle 8i automatisch erzeugt. Ein Objekt wird mit <Typname>(<Attributwerteliste>) erzeugt. Dabei müssen exakt so viele Argumente angegeben sein, wie
5
der Objekttyp Attribute besitzt; außerdem müssen die Werte den deklarierten Datentypen der Attribute in der Reihenfolge der Deklaration entsprechen. NULL-Werte sind
zulässig, sofern sie nicht ausdrücklich ausgeschlossen wurden.
CREATE OR REPLACE TYPE point AS OBJECT (
x1 REAL,
x2 REAL,
MEMBER FUNCTION getcoord(dim INTEGER) RETURN REAL,
MEMBER PROCEDURE setcoord(dim INTEGER, value REAL),
MEMBER FUNCTION distance(frompoint POINT) RETURN REAL,
STATIC FUNCTION dimension RETURN INTEGER,
MAP MEMBER FUNCTION x then y RETURN REAL);
/
Abbildung 3: Deklaration des selbstdefinierten Objekttyps point
Soll ein einmal definierter Objekttyp gelöscht werden (oder in wesentlichen Teilen
geändert), so kann man
drop type <Typname> [force];
verwenden. Falls es von diesem Typ abhängige Datenbankobjekte gibt, so kann der
Typ nicht gelöscht werden, es sei denn, es wird die Option force angegeben. Dann
kann allerdings ein konsistenter Datenbankzustand nicht mehr garantiert werden.
Um einen Typ zu verändern verwendet man alter type. Allerdings sind die Möglichkeiten der Änderung begrenzt (i. w. können Methoden hinzugefügt werden). Bei
grösseren Änderungen bietet sich ein drop type mit anschliessendem modifizierten
create type an.
3.2 Implementierung von Methoden
Die in einer Typdeklaration deklarierten Funktionen und Prozeduren müssen im
Typrumpf implementiert werden. Dabei besteht die Möglichkeit, die Methoden direkt
durch einen PL/SQL-Block zu implementieren, oder sie können durch die Spezifikation des Aufrufs einer Java-Methode oder einer externen C-Funktion implementiert
werden.
3.2.1 Implementierung in PL/SQL
Wird eine Methode direkt implementiert, so ist eine herkömmliche PL/SQL-Funktion
bzw. -Prozedur zu schreiben (siehe [Ora99j] und Datenbanksysteme I).
[memberjstatic] procedure <Prozedurname> (<Parameterliste>)
is <PL/SQL-Block>
[memberjstatic] function <Funktionsname> (<Parameterliste>) return <Typ>
is <PL/SQL-Block>
6
Es sind in diesen Funktionen alle Datentypen auch benutzer-definierte (einschliesslich dem gerade implementierten) erlaubt. Ebenso können Kollektionen (⇒ DBS I)
eingesetzt werden. Die PL/SQL-Syntax ist etwas gewöhnungsbedürftig; PL/SQL bietet
aber den Vorteil einer sehr engen Integration in das DBMS, so dass sehr elegant auf
die verwalteten Datenbestände zugegriffen werden kann. Diese Art der Implementierung bietet sich also für Funktionen mit Zugriff auf grössere Teile des Datenbestandes an.
CREATE LIBRARY geolib AS ’/home/ck/geolib.so’;
/
ALTER JAVA CLASS “GeoPackage.Point“ COMPILE;
CREATE OR REPLACE TYPE BODY point AS
MEMBER FUNCTION getcoord(dim INTEGER) RETURN REAL IS
BEGIN
IF (dim=1) THEN RETURN x1;
ELSIF (dim=2) THEN RETURN x2;
END IF;
END;
MEMBER PROCEDURE setcoord(dim INTEGER, value REAL) IS
BEGIN
IF (dim=1) THEN x1:=value;
ELSIF (dim=2) THEN x2:=value;
END IF;
END;
MEMBER FUNCTION distance(frompoint POINT) RETURN REAL
is language java name ’GeoPackage.Point.distance(GeoPackage.Point) return float’;
STATIC FUNCTION dimension RETURN INTEGER
BEGIN
RETURN 2;
END;
MAP MEMBER FUNCTION x then y RETURN REAL
is language c name “map2to1“ library geolib with context;
END;
/
Abbildung 4: Implementierung des Objekttyp point
3.2.2 Implementierung in Java
Seit kurzem ist auch eine Implementierung von Methoden durch den Aufruf von
Java-Methoden möglich. Syntaktisch ist dies relativ einfach:
[memberjstatic] procedure <Prozedurname> (<Parameterliste>) is language java
name ’<Java-Methodenaufruf>’
Für Methoden mit Rückgabewert ist eine analoge Implementierung über function
möglich.
7
Semantisch ist die Verwendung solcher Methoden jedoch recht komplex: sollen nicht
nur Standard-Datentypen sondern auch Klassen verwendet werden, so sind in der
Java-Klasse entsprechende Interfaces zu implementieren, die die SQL-Datentypen
der Argumente und Rückgabewerte auf Java-Datentypen abbilden. Im Beispiel der
Funktion distance in Abbildung 4 ist eine mögliche Variante, dass die Klasse GeoPackage.Point das Interface SQLData implementiert (siehe Abbildung 5). Dieses von
Oracle mitgelieferte Interface umfasst drei Methoden und ein Attrribut: das Attribut
sql type für den entsprechenden Typnamen in SQL mit einer get-Methode sowie die
Methoden writeSQL bzw. readSQL, die die Attribute des SQL-Objekttypen auf Attribute der Java-Klasse abbilden. Weitere Details findet man in [Ora99g]. Dort werden
auch andere Besonderheiten beim Aufruf von Java-Konstruktoren behandelt.
package GeoClass;
import oracle.jdbc2.*;
public class Point implements SQLData f
private float x;
private float y;
. . . weitere Attribute . . .
public float distance (Point otherpoint) f . . . g
// implements SQLData
String sql type;
public String getSQLTypeName() throws SQLException f
return sql type;
g
public void readSQL(SQLInput stream, String typeName) throws SQLException f
sql type = typeName;
x = stream.readFloat();
y = stream.readFloat();
g
public void writeSQL(SQLOutput stream) throws SQLException f
stream.writeFloat(x);
stream.writeFloat(y);
g
g
Abbildung 5: Fragment einer Java-Klasse zur Verwendung in Datenbankmethoden
Auf jeden Fall müssen die Java-Sourcen der aufgerufenen Klassen (oder die .classDateien) vor der Benutzung auf der Shell-Ebene mit
loadjava -u <Oracle-Connect-String> -v -r [-oci8jt] <Java-Klasse>.[classjjava]
(Details hierzu siehe ebenfalls [Ora99g]) in die Datenbank geladen werden. Danach
muss das Datenbankobjekt für die Java-Klasse noch in SQL-Plus mit
alter java class “<Java-Klasse>“ compile;
kompiliert werden, damit es dann aufgerufen werden kann. Bei der Ausführung von
Java-Methoden als Implementierung einer Methode eines Datenbank-Objekttyps ist
8
es auch möglich wieder auf die Datenbank zuzugreifen. Dabei bedient man sich innerhalb der Java-Methode der herkömmlichen Datenbank-Anbindung über JDBC (siehe auch [Ora99h]). Eine Datenbankverbindung braucht in diesem Falle nicht mehr
hergestellt zu werden (die Methode wird ja in der Datenbank ausgeführt, in der der
Objekttyp existiert); syntaktisch erreicht man dies in Java durch Verwendung von
Connection <Java-Variablenname> =
new oracle.jdbc.driver.OracleDriver().defaultConnection();
um innerhalb des Java-Codes die Datenbankverbindung identifizieren zu können.
3.2.3 Implementierung in C
Um Methoden eines Objekttyps in C zu implementieren, ist eine externe Bibliothek
zu erstellen. In diese Bibliothek sind die fertig kompilierten C-Routinen aufzunehmen. Die Bibliothek ist der Datenbank mithilfe von
create [or replace] library <DBS-Bibliothekname> as <Bibliothek-Dateiname>;
bekanntzugeben. Dabei muss der Dateiname mit vollständigem Pfad angegeben werden. Um schliesslich einzelne Methoden im Typrumpf zu implementieren, wird
[memberjstatic] procedure <Prozedurname> (<Parameterliste>) is language c
[name “<C-Funktionsname>“] library <DBS-Bibliothekname> [with context]
[parameters (<C-Parameterliste>)]
angegeben. Dabei ist der C-Funktionsname der Name der C-Routine in der externen
Bibliothek. Um aus der C-Routine Datenbankzugriffe ausführen zu können, ist die
Klausel with context anzugeben. Sie gibt der Routine Zugriff auf die Datenbankumgebung und ihre Dienste. Diese Zugriffe erfolgen über das Oracle Call Interface (OCI)
und sind recht komplex, daher werden sie hier nicht näher beschrieben (Details siehe
[Ora99e]). Mit der optionalen zweiten Parameterliste können Informationen für Parameter beim Aufruf der C-Routine angegeben werden. Hier können beispielsweise
Datentypen der Datenbank auf Datentypen in C abgebildet werden.
Ein wesentlicher Unterschied zwischen diesen echt externen Funktionen und den
im letzten Abschnitt beschriebenen gespeicherten Funktionen (stored procedures)
ist, dass externe Funktionen in einem anderen Adressraum als der Datenbankserver
ausgeführt werden, während gespeicherte Funktionen im gleichen Adressraum ausgeführt werden. Möglicherweise ergeben sich dadurch Vorteile in der Leistungsfähigkeit für gespeicherte Funktionen.
3.3 Operatoren und Indexe für spezielle Domains
Hat man wie in den vorherigen Abschnitten beschrieben einen neuen Objekttyp angelegt, so erfolgt der Zugriff auf eine Spalte dieses Typs in einer Datenbanktabelle sequentiell. Da das DBMS die interne Struktur des neuen Objekttyps nicht einschätzen
kann, sind zunächst keine typspezifischen Anfragen möglich. Hat man z. B. einen
Objekttyp Rectangle angelegt, so möchte man Anfragen nach allen Rechtecken einer
Tabelle, die ein gegebenes Rechteck überlappen (d. h. in einem gegebenen Bereich
9
liegen), effizient beantworten können. Funktionen, die auf solchen selbstdefinierten
Datentypen arbeiten, nennt man in Oracle 8i auch Operatoren.
3.3.1 Operatoren
Operatoren werden in zwei Schritten implementiert. Zunächst ist eine PL/SQL-Funktion zu schreiben, die die gewünschte Funktion ausführt. So ist z. B. eine Funktion
overlaps(Rectangle, Rectangle) return boolean zu schreiben, die überprüft, ob sich
zwei Objekte vom Typ Rectangle überlappen. Anschliessend ist ein Datenbankobjekt
vom Typ Operator anzulegen, das dem Datenbank-Server die definierte Funktion als
Operator auf dem neuen Datentyp erklärt.
create operator <Operatorname> binding (<Datentypliste>) return <Datentyp>
using <Funktionsname>;
Der Funktionsname ist dabei der Name der zuvor definierten PL/SQL-Routine, deren
Signatur natürlich mit der hier angegebenen übereinstimmen muss. Diese Deklaration ist erforderlich, damit benutzer-definierte Indexe und Anfrageoptimierung vom
Datenbank-Server verwendet werden können: diese werden nämlich nur für whereKlauseln der Formen
• <Operator> <RelOp> <Konstante>,
• <Konstante> <RelOp> <Operator> sowie
• <Operator> like <Konstante>
eingesetzt. Daher ist die Verwendung einer benutzer-definierten Funktion in der
where-Klausel einer Anfrage nie index-unterstützt.
CREATE FUNCTION pointdistance (first point, second point) RETURN real AS
BEGIN
RETURN sqrt(((first.x1 - second.x1)**2)+((first.x2 - second.x2)**2));
END;
/
CREATE OPERATOR opdistance BINDING (point, point) RETURN real USING pointdistance;
/
Abbildung 6: Operator für Punktabstand
3.3.2 Indexe
Nach der Definition von Operatoren auf einem neuen Datentyp, können Anfragen an
Spalten dieses Datentyps nur sequentiell beantwortet werden: um alle Rechtecke zu
finden, die ein gegebenes Rechteck überlappen, muss für alle Rechtecke der Tabelle der Operator overlaps ausgeführt werden. Danach werden alle Zeilen, für deren
Rechtecke der Operator wahr zurückgibt ausgegeben. Diese Methodik kann bei relativ komplexen Funktionen (z. B. Überlappung bei allgemeinen Polygonen) zu langen
Antwortzeiten führen. Um diese Zeiten zu verbessern, bietet sich die Einführung von
10
Indexen für spezielle Operatoren an. Beispielsweise könnte man den gesamten Raum
aller Rechtecke in kleine Quadrate einteilen und die Überlappungsoperation nur für
Rechtecke ausführen, die ein gemeinsames Quadrat mit dem gesuchten Bereich besitzen. Um diese sogenannten Kandidaten herauszufinden, bietet sich ein Index an,
der schnell zu einem gegebenen Quadrat alle überlappenden Rechtecke bestimmen
kann.
Um einen Operator indexunterstützt auswerten zu können, sind mehrere Schritte
erforderlich. Zunächst ist ein benutzerdefinerter Objekttyp anzulegen, der das Interface ODCIIndex (Oracle Data Cartridge Interface) implementiert. Dazu gehört die
Implementierung zahlreicher Funktionen zur Implementierung des Index (Details
siehe Kapitel 15 in [Ora99f]). Beispielsweise dient die Funktion ODCIIndexCreate der
Initialisierung eines benutzer-definierten Index (z. B. durch Erzeugung der notwendigen Datenstrukturen, da auch die physische Speicherung der Indexeinträge zu implementieren ist). Sie wird vom DBMS bei Erzeugung des Index automatisch ausgeführt.
Die Funktion ODCIIndexFetch soll gefundene Objekte zurückgeben.
CREATE OR REPLACE TYPE treetable AUTHID CURRENT USER AS OBJECT (
name VARCHAR2(60),
MEMBER PROCEDURE createtable(SELF IN treetable),
MEMBER FUNCTION getnode(rid ROWID) RETURN rsnode,
MEMBER FUNCTION createnode(node rsnode) RETURN ROWID,
... )
/
CREATE OR REPLACE TYPE BODY treetable AS
MEMBER PROCEDURE createtable(SELF IN treetable) IS
...
END;
/
Abbildung 7: Objekttyp für benutzerdefinierte Indextabelle
Die Implementierung eines Indextyps besteht dabei wiederum aus einem create type
sowie einer Implementierung mit create type body. Da der Programmierer jedoch
für die Verwaltung, Speicherung und den Zugriff auf die eigentlichen Indexeinträge
selbst verantwortlich ist, wird häufig ein weiterer Objekttyp definiert, der diese Aufgaben übernimmt. Im Beispiel stellt der Objekttyp treetable eine Tabelle zur Speicherung allgemeiner Informationen über den Index sowie eine Tabelle für die eigentlichen Indexeinträge zur Verfügung. Methoden dieses Objekttyps werden dann
im eigentlichen Objekttyp für den Index (rstree ind) verwendet. Beide Typen werden
durch das weiter oben erklärte Verfahren mit create type und create type body implementiert. Schliesslich ist der Index-Objekttyp im DBS mittels
create indextype <DBS-Indextypname> for <Operatorname>(<Parameterliste>)
using <Typname>;
als Indextyp zu erklären. Nun muss zur Verwendung des Index nur noch auf der
Spalte einer Tabelle des zu indexierenden Datentyps mit
create index <Indexname> on <Tabellenname>(<Spaltenname>)
11
indextype is <DBS-Indextypname>;
ein Index angelegt werden. Das DBMS erzeugt mithilfe der implementierten ODCIIndex-Routinen nun automatisch den gewählten Index. Außerdem wird der Index bei
allen Anfragen, in denen in der where-Klausel der oben definierte Operator verwendet wird, zur Effizienzsteigerung eingesetzt. Das gilt jedoch nur, wenn die indexierte
Spalte als erstes Argument des Operators verwendet wird.
Zum Ändern und Löschen von Indexen gibt es die Operationen
alter index <Indexname> <Indexänderungsklausel>;
drop index <Indexname> [force];
Details zur Indexänderungsklausel findet man in [Ora99i].
CREATE OR REPLACE TYPE rstree ind AUTHID CURRENT USER AS OBJECT (
rstree treetable,
cmpvalue Point,
opname VARCHAR2(30),
retval NUMBER,
STATIC FUNCTION ODCIGetInterfaces(ifclist OUT sys.ODCIObjectList) RETURN NUMBER,
STATIC FUNCTION ODCIIndexCreate (ia sys.ODCIindexinfo, parms VARCHAR2)
RETURN NUMBER,
STATIC FUNCTION ODCIIndexInsert(ia sys.ODCIindexinfo, rid VARCHAR2, newval Point)
RETURN NUMBER,
STATIC FUNCTION ODCIIndexUpdate(ia sys.ODCIindexinfo, rid VARCHAR2,
oldval Point, newval Point) RETURN NUMBER,
MEMBER FUNCTION ODCIIndexFetch(SELF IN OUT rstree ind, nrows NUMBER,
rids OUT sys.ODCIridlist) RETURN NUMBER,
...
indextyp-spezifische Methoden
. . . );
/
CREATE OR REPLACE TYPE BODY rstree ind AS
...
/
CREATE INDEXTYPE rstree FOR opdistance(point, point) USING rstree ind;
CREATE TABLE city (name VARCHAR2(40), location point);
CREATE INDEX idx loc city ON city(location) INDEXTYPE IS rstree;
Abbildung 8: Benutzerdefinierter Indextyp rstree
3.4 Anfrageoptimierung
Um Anfragen an die Datenbank wirklich effizient zu bearbeiten, reichen Indexe jedoch nicht aus. So ist beispielsweise das Durchsuchen eines Index zur Ermittlung
von Kandidaten für ein Anfrageergebnis sinnlos, wenn alle Einträge zu Kandidaten
werden. In diesem Falle hätte man die Zeit für das Durchsuchen des Index sparen
12
können. Die effiziente Bearbeitung einer Anfrage hängt also auch von deren Selektivität und den Kosten für die Indexauswertung ab. Um möglichst viele dieser Faktoren
in die Optimierung der Anfragebearbeitung einbauen zu können, verwendet Oracle
8i eine kostenbasierte Anfrageoptimierung. Das DBMS kann natürlich bei benutzerdefinierten Operatoren keine Abschätzung dieser Faktoren vornehmen. Daher kann
der Programmierer selbst durch Implementierung bestimmter Methoden dem System Hilfen bei der Kostenabschätzung geben. Der (ältere) regelbasierte Optimierer
von Oracle 8i kann nicht erweitert werden.
Da die gesamte Thematik sehr komplex ist, sollen hier nur die prinzipiellen Grundlagen vorgestellt werden. Für benutzerdefinierte Indexe und Spalten mit Objekttypen
können zusätzlich zu den systemgenerierten Standard-Statistiken noch benutzerdefinierte Statistiken erstellt werden. Dabei ist der Programmierer für deren Verwaltung und Speicherung verantwortlich. Weiterhin können für Prädikate (d. h. Klauseln
im where-Teil einer Anfrage bzw. anderer DML-Operationen) der Formen
• <Operator>(<Parameterliste>) <RelOp> <Konstante>,
• <Konstante> <RelOp> <Operator>(<Parameterliste>) sowie
• <Operator>(<Parameterliste>) like <Konstante>
benutzerdefinierte Selektivitätsschätzungsfunktionen sowie Kostenfunktionen definiert werden. Diese wiederum können system- und benutzerdefinierte Statistiken
verwenden. Dazu werden die erforderlichen Methoden in einem benutzerdefinierten
Objekttyp implementiert. Dessen Methoden müssen gemäss dem Interface ODCIStats implementiert werden. Alle Funktionen dieses Interface, die realisiert wurden,
werden automatisch vom Optimierer des Systems verwendet. Es gibt beispielsweise
die Funktionen ODCIStatsCollect, ODCIStatsSelectivity, ODCIStatsFunctionCost. Die
Details hierzu sind in Kapitel 8 bzw. 16 von [Ora99f] beschrieben.
Benutzt werden die selbstdefinierten Statistiken, wenn nach Definition des Statistikdatentyps (siehe oben) dieser mit einem Datenbankobjekt in Verbindung gesetzt
wird. Dies geschieht für Tabellenspalten mit
associate statistics with columns <Tabellenname>.<Spaltenname>
using <Statistiktypname>;
und für andere Datenbankobjekte (Funktionen, Pakete, Typen, Indexe, Indextypen)
mit
associate statistics with <Assoziationsklausel>;
Die genaue Syntax der Assoziationsklausel findet sich in [Ora99i]. Um Statistiken für
ein Schemaobjekt erstellen zu lassen, wird das Kommando
analyze <Schemaobjektart> <Schemaobjektname> compute statistics;
verwendet. Dies aktualisiert unter Verwendung der vom Benutzer implementierten
Routinen die zugehörigen Statistikeinträge. Diese werden dann bei der Anfragebearbeitung verwendet. Eine möglicherweise implementierte Selektivitätsschätzung sollte auf den so berechneten Statistiken operieren.
13
3.5 Weitere Konzepte beim Entwurf
Einige weitere Dinge sind bei der Erstellung eines Data Cartridge von Bedeutung und
werden hier kurz diskutiert.
Realisierung von Vererbung
Da es im Objektmodell von Oracle 8i keine Vererbung gibt, müssen Objekt-Hierarchien bei der Definition von Objekttypen auf andere Weise simuliert werden. Dazu
gibt es i. w. drei Möglichkeiten:
• Unterklasse enthält Oberklasse: Es werden Objekttypen für Unter- und Oberklassen angelegt. Die Unterklassen enthalten ein Attribut des Typs der Oberklasse. Alle Methoden der Oberklasse müssen in allen Unterklassen deklariert
und definiert werden, die Oberklasse hat keine Methoden. Die Oberklasse wird
nicht nach außen zur Verfügung gestellt. Diese Variante eignet sich vor allem
für Oberklassen mit wenigen Methoden und Assoziationen bzw. Unterklassen
mit zahlreichen Assoziationen.
• Oberklasse enthält alle Unterklassen: Es werden Objekttypen für Unter- und
Oberklassen angelegt. Die Oberklasse enthält ein Attribut für alle Unterklassen,
wobei sichergestellt wird, dass für jedes Objekt nur genau eines dieser Attribute gesetzt ist. Die Oberklasse deklariert und definiert alle Methoden sämtlicher
Unterklassen. Die Unterklassen sind nicht nach außen sichtbar. Dieses Modell
eignet sich für wenige Unterklassen mit möglichst wenigen Methoden und für
Klassen mit vielen 1:n-Beziehungen.
• Duale Referenzen: Es werden Objekttypen für Unter- und Oberklassen angelegt. Die Oberklasse enthält ein referenziertes Attribut für alle Unterklassen,
die Unterklassen enthalten ein referenziertes Attribut für die Oberklasse. In
der Oberklasse wird sichergestellt, dass für jedes Objekt nur genau eines dieser Attribute gesetzt ist. Jede Klasse definiert die zu ihr gehörigen Methoden.
Alle Klassen sind nach außen sichtbar. Diese Variante eignet sich besonders
für komplexe Modelle, für die keines der beiden anderen Verfahren geeignet
ist. Schwieriger ist hier die Implementierung, da mit vielen Referenzen gearbeitet wird.
Die Wahl der richtigen Alternative ist nicht einfach; in einer späteren Version von
Oracle ist mit einer internen Realisierung von Vererbung zu rechnen.
Wahl der Implementierungssprache
Eine Methode in C zu implementieren bietet sich immer dann an, wenn die Methode
CPU-intensiv ist. Der zusätzliche Aufwand für den externen Aufruf muss durch eine
kürzere Berechnungszeit der eigentlichen Funktion wettgemacht werden. PL/SQL hat
Vorteile bei Methoden mit geringer CPU-Last und vielen Datenbankzugriffen (I/O). Java ist eine Art Zwitter: es wird im Adressraum des Servers ausgeführt und hat daher
keinen Aufruf-Überhang, ist aber auch nicht unbedingt für CPU-intensive Berechnungen geeignet. Generell lohnt sich eine Implementierung in einer Sprache, in der
schon grössere Mengen von Code zur Implementierung bereit stehen.
14
Performanz
Um die Performanz eines selbstdefinierten Cartridges (und von SQL-Anfragen) zu
untersuchen, steht die tkprof-Utility zur Verfügung. Details finden sich in [Ora99d].
Allgemeines
Objekttypen sollten sehr sorgfältig entworfen werden, da eine Änderung sehr kompliziert ist, wenn das Cartridge bereits im Einsatz ist.
Wenn eine grössere Zahl von Tabellen, Sichten und Objekten innerhalb des Cartridge
verwendet werden sollen, bietet sich das Anlegen einer Metadatentabelle an, um die
Wartbarkeit zu erhöhen.
3.6 Datentypen für Multimedia
Um Mengen von relativ grossen, für das DBMS unstrukturierten Daten (wie z. B. Bilder, Sound- und Videoclips) abzuspeichern, bietet Oracle 8i einen Datentyp large
object (LOB) an. Es könne Tabellenspalten von diesem Typ angelegt werden und mithilfe von sogenannten locators kann sogar auf bestimmte Stellen in diesen Daten zugegriffen werden (Details in [Ora99a]). Weiterhin gibt es ein fertiges PL/SQL-Paket für
die Arbeit mit grossen Objekten (DBMS LOB), das in [Ora99c] ausführlich beschrieben
wird.
4 Beispiel: Räumliche Daten
In diesem Kapitel soll die Integration objekt-relationaler Konzepte in ein DBMS am
Beispiel von räumlichen Daten vorgestellt werden. Zunächst wird dabei ein vom
DBMS-Hersteller Oracle bereitgestelltes Cartridge für zweidimensionale Objekte vorgestellt. Im zweiten Teil wird als Alternative dazu einen benutzer-definierte Variante
eines solchen Cartridge für die gleichen Objekte präsentiert.
4.1 Oracle Spatial
In der aktuellen Version von Oracle 8i wird sowohl eine relationale Erweiterung für
räumliche Daten aus früheren Versionen als auch eine Erweiterung unter Verwendung der objekt-relationalen Technik unterstützt. In diesem Abschnitt gehen wir nur
auf die objekt-relationalen Konzepte ein, da das relationale Modell nur aus Kompatibilitätsgründen weiterhin zur Verfügung steht. Teile dieses Unterabschnitts folgen
der Beschreibung in [Pfa00].
4.1.1 Objekttyp SDO GEOMETRY
Für die Speicherung von Objekten mit einer räumlichen Komponente im objektrelationalen Schema von Oracle8i wird eine beliebige vom Benutzer anzulegende Tabelle
benötigt, die eine Spalte vom Objekttyp MDSYS.SDO GEOMETRY1 besitzt (MDSYS ist
ein Pseudo-Benutzer, in dessen Schema Oracle 8i Spatial installiert wird). In dieser
1
SDO steht aus historischen Gründen für Spatial Data Option; so hieß ein Vorgänger des heutigen
Spatial Cartridge in Oracle 7
15
Herunterladen