Tesina di confronto fra DBMS - Dipartimento di Informatica e

Universita' degli studi di Roma
"La Sapienza"
Corso di Basi di Dati - Prof.ssa Tiziana Catarci.
Anno Accademico 1999/2000 - Gennaio 2000
Tesina di confronto tra:
Db2 Universal Server 6.1 EE
Oracle 8i
Informix Dynamic Server 2000
• Implementazione SQL92: vincoli ed interrogazioni.
• I trigger: implementazione di trigger in Java e SQL.
• Indici: i tipi di indicizzazione disponibili.
• Ottimizzazione: intervenire sull'ottimizzatore delle query.
Menegoni Fabio - Moschetti Marco - Salce Gianluca
Introduzione
Questo studio riguarda il confronto di alcuni aspetti dei tre piu' importanti DBMS presenti sul
mercato attuale del software per la gestione delle basi di dati:
Oracle 8i (8.1.5) - IBM DB2 6.1 EE - Informix Dynamic Server 2000
Tutti e tre disponibili per molte piattaforme, sono stati provati nella versione per Windows NT 4
su un personal computer dotato di un processore AMD K6/200 e 64 MB di RAM.
Confrontare dei software come DBMS e' sicuramente un'impresa tutt'altro che semplice. Le loro
dimensioni sono tali che un confronto completo e approfondito da tutti i punti di vista
richiederebbe mesi di lavoro a persone con una certa esperienza. Anche limitando l'analisi solo
ad alcuni aspetti di implementazione e tuning, l'impresa e' stata ben piu' ardua di quanto stimato
inizialmente; infatti, questi programmi non sono particolarmente amichevoli, a causa ovviamente
della loro complessita'. Inoltre la quantita' di documentazione fornita e' semplicemente
disorientante anche per persone che hanno una certa familiarita' con l'uso del computer.
Gli argomenti sui quali e' posta l'attenzione, per la loro rilevanza, sono:
•
•
•
•
Implementazione SQL92: come esprimere vincoli ed interrogazioni.
I trigger: implementazione di trigger in Java.
Indici: i tipi di indicizzazione disponibili.
Ottimizzazione: come intervenire sull'ottimizzatore delle query.
Il Software a disposizione
IBM DB2 Universal Database V6.1 EE, versione per Windows NT, Free 60 days evaluation
copy richiesta gratuitamente sul sito IBM. DB2 SDK 5.1 e DB2 Client Application Enabler 5.1,
messoci a disposizione dal Dipartimento di Informatica e Sistemistica, Universita' di Roma.
Oracle 8i Enterprise Edition (8.1.5), Free 30 days evaluation copy for Windows NT, fornita
durante il seminario su Oracle tenuto durante il corso di Basi di Dati. Informix Dynamic Server
2000, V9.2, in versione Evaluation, ottenuto dal sito web di Informix. Per implementare i trigger
in Java, e' stato necessario servirsi della versione per Linux 2.2.
Note sull'installazione
L'installazione non e' semplice come puo' sembrare. E' richiesta la presenza di alcuni servizi di
rete di NT4 senza i quali l'installazione non va a buon fine.
Per quello che riguarda l'occupazione su disco, DB2 ed Informix richiedono sui 200-250 Mb per
l'installazione completa, Oracle richiede molto piu' spazio, circa 1.5 Gb.
DB2 e Oracle allocano praticamente tutta le memoria disponibile sul calcolatore, Informix
invece alloca la memoria solo quando ne ha bisogno rendendo agevole l'uso per altre
applicazioni su quella macchina. DB2 offre questa possibilita' a posteriori da un pannello di
controllo. Oracle 8i permette di specificare dei parametri per limitare l'uso della memoria
allocata a diversi tipi di buffer (redo buffer log e buffer cache principalmente) mentre
l'allocazione degli shared pool e del System Global Area non e' direttamente controllabile
dall'amministratore se non per aspetti come le politiche di pre-paging che comunque non sono
finalizzate a limitare l'uso di memoria, ma a disporne in anticipo per limitare il traffico di I/O.
Note sulla documentazione
La documentazione disponibile per tutti e tre i DBMS e' vasta ed esauriente.
Oracle la offre sotto forma di file HTML con uno strumento per la ricerca in javascript.
DB2 offre la possibilta' di accedere a documenti nazionalizzati, sia in PDF sia in HTML
mediante un tool, l'information centre, che la mostra in aree tematiche permettendo di effettuare
ricerche mediante keyword.
La documentazione di Informix e' fornita sul loro sito web in formato PDF.
Strumenti di interazione con i DBMS
Tutti mettono a disposizione uno strumento grafico per la creazione di database. Quello di Oracle
mette a disposizione una serie di opzioni proprietarie, riguardanti le estensioni multimediali, che
sconosciute al neofita e selezionate di default, allungano la creazione del database a circa un'ora.
DB2 mette a disposizione invece delle opzioni per far gestire dal sistema i file relativi al database
diminuendo le prestazioni, ma rende possibile un utilizzo meno ristretto del computer sul quale si
trova installato il DBMS.
Degna di nota e' la presenza di un tool grafico su DB2 e Informix per la gestione dei database: da
questo e' possibile creare tabelle, vincoli, trigger e vedere il contenuto delle tabelle in maniera
pratica, veloce ed intuitiva; sempre DB2 ed Informix offrono un editor SQL molto piu' evoluto di
quello presente su Oracle che sembra essere uno strumento primordiale e ostico. Infatti, questi
editor offrono la possibilita' di scrivere e modificare il codice SQL introdotto e vedere i risultati
su una pagina diversa da quella riservata al codice.
Il database
Lo schema di base di dati che e' stato utilizzato nello studio e' piuttosto semplice ed e' stato
importante per non perdere di vista i casi reali; era necessario avere a disposizione vincoli reali,
ed e' servito per darci l'idea di come si implementa uno schema concettuale all'interno di un
DBMS. Queste sono le specifiche.
L’agenzia di pubbliche relazioni
La base di dati di un’agenzia di pubbliche relazioni contiene le seguenti informazioni:
•
un catalogo di clienti, che possono essere aziende o persone fisiche;
•
un insieme di informazioni su dei banchetti organizzati nell’ambito di manifestazioni
o in occasione di singoli avvenimenti (congressi, matrimoni, cresime, ecc.) per conto
dei clienti;
•
gli elenchi degli invitati ai singoli banchetti; un elenco di ristoranti con le loro
caratteristiche. Tra i ristoranti si distinguono quelli caratteristici, che possono offrire
particolari specialita'; le specialita' sono offerte solo dai ristoranti caratteristici;
•
un insieme di menu', tra i quali si distinguono quelli contenenti le specialita' offerte
dai ristoranti caratteristici
Lo schema E/R
Le tabelle
Dal diagramma E/R e dalle specifiche e' stato possibile identificare le relazioni da implementare
nei DBMS, unitamente ad un insieme di vincoli che garantiscono l'integrita' della base di dati.
•
AZIENDA (codice fiscale, citta', via, #telefono, capitale_sociale, #dipendenti)
•
BANCHETTO (codicebanchetto, occasione, data, n_partecipanti, codice_fiscale,
nome_ristorante, localita'_ristorante, id_menu';)
•
COMPRENDE (id_menu' , nome portata )
•
INVITATO (codicebanchetto , nome )
•
MENU_STANDARD (id , costo)
•
OFFRE_S (nomeristorante , localitaristorante , nome_specialita')
•
OFFRE_M (nomeristorante , localitaristorante , id_menu')
•
PERSONA (codicefiscale , citta, via, #telefono, cognome, nome, professione)
•
PORTATA (nome , tipo) SPECIALITA(nome, tipo)
•
RISTORANTE (nome , localita' , #posti)
•
RISTORANTE_CARATTERISTICO (nome , localita')
Vincoli
•
Per la tabella BANCHETTO: il codice fiscale deve appartenere ad AZIENDA o a
PERSONA.
•
Per le tabelle AZIENDA e PERSONA: devono rispettare il vincolo imposto dalla gerarchia
ISA: ciascun codice fiscale deve essere unico nelle due tabelle.
•
Per RISTORANTE_CARATTERISTICO: deve esistere in RISTORANTE.
•
Per ogni banchetto deve esistere almeno un invitato. SPECIALITA' deve essere in
PORTATA. SPECIALITA' deve essere offerta da RISTORANTE_CARATTERISTICO.
•
Un banchetto deve avere necessariamente un menu'.
•
MENU' deve contenere almeno una portata.
•
Una portata deve essere contenuta almeno in un MENU'.
•
Un BANCHETTO deve avvenire in un RISTORANTE.
•
Se cancello un record da RISTORANTE, devo cancellare l'eventuale record in
RISTORANTE_CARATTERISTICO.
•
Se cancello un record da RISTORANTE_CARATTERISTICO, devo cancellare da
SPECIALITA' i piatti offerti da quel ristorante.
•
Il tipo di portata in SPECIALITA' deve essere congruente con il tipo in PORTATA.
•
Un cliente non puo' ordinare lo stesso menu' per piu' di due banchetti consecutivi (vincolo
imposto).
•
Il costo di un banchetto non puo' essere superiore al capitale sociale di un'azienda se il cliente
e' un'azienda.
Implementazione SQL92
Quella che segue e' l'implementazione in SQL92 del database di test; nell'eseguire questa
operazione, abbiamo verificato che alcune possibilita' offerte dallo standard non sono disponibili
nei DBMS in prova.
In particolare l'assenza della clausola ASSERT e l'impossibilita' di specificare una SELECT
all'interno di una CHECK rendono necessario l'uso di trigger. Di seguito riportiamo degli esempi
rappresentativi per ogni classe di problemi.
L'entita' CLIENTE non puo' essere rappresentata cosi' come compare nello schema E/R. La
scelta e' stata quella di avere due tabelle separate: PERSONA ed AZIENDA. Essendo codice
fiscale una chiave per CLIENTE, l'inserimento di un record in una delle due tabelle deve
verificare che nell'altra non esista un record con lo stesso codice fiscale. Questo richiede una
CHECK all'interno della CREATE TABLE, ma la CHECK di cui abbiamo bisogno ha una
SELECT annidata al suo interno. Questa opzione, possibile in SQL92, non e' permessa da
nessuno dei tre DBMS in prova. E' necessario quindi utilizzare un trigger. E' stato possibile
implementare tutti i vincoli di integrita' referenziale, come previsto. L'entita'
RISTORANTE_CARATTERISTICO e' stata modellata come una tabella la cui chiave primaria
referenzia la chiave primaria di RISTORANTE. Inoltre e' stata scelta la clausola ON DELETE
CASCADE per cancellare un record di RISTORANTE_CARATTERISTICO quando viene
cancellata la chiave in RISTORANTE. Anche questa opzione non ha dato nessun problema.
Creazione tabelle in SQL
CREATE TABLE Azienda (
cod_fiscale CHAR(16) NOT NULL,
citta CHAR(16) NOT NULL,
via CHAR(25),
telefono CHAR(20) NOT NULL,
capitale_soc INTEGER,
dipendenti INTEGER,
PRIMARY KEY (cod_fiscale));
CREATE TABLE Persona (
cod_fiscale CHAR(16) NOT NULL,
citta CHAR(16) NOT NULL,
via CHAR(25),
telefono CHAR(20) NOT NULL,
cognome CHAR(25) NOT NULL,
nome CHAR(25) NOT NULL,
professione CHAR(25),
PRIMARY KEY (cod_fiscale));
CREATE TABLE Ristorante (
nome CHAR(25) NOT NULL,
localita CHAR(25) NOT NULL,
posti INTEGER NOT NULL,
PRIMARY KEY (nome, localita));
CREATE TABLE Ristorante_carat (
nome CHAR(25) NOT NULL,
localita CHAR(25) NOT NULL,
PRIMARY KEY (nome, localita),
FOREIGN KEY(nome, localita) REFERENCES Ristorante ON DELETE CASCADE);
CREATE TABLE Portata (
nome CHAR(25) NOT NULL,
tipo CHAR(25) NOT NULL,
PRIMARY KEY (nome));
CREATE TABLE Specialita (
nome CHAR(25) NOT NULL, tipo CHAR(25) NOT NULL,
nomerist char(25) NOT NULL,
locrist char(25) NOT NULL,
PRIMARY KEY (nome),
FOREIGN KEY (nome) REFERENCES Portata ON DELETE CASCADE ,
FOREIGN KEY (nomerist, locrist) REFERENCES Ristorante_carat ON DELETE
CASCADE);
CREATE TABLE Menu (
id CHAR(20) NOT NULL,
costo INTEGER NOT NULL,
PRIMARY KEY (id));
CREATE TABLE Comprende (
id_menu CHAR(20) NOT NULL,
nome_portata CHAR(25) NOT NULL,
PRIMARY KEY (id_menu, nome_portata),
FOREIGN KEY (id_menu) REFERENCES Menu,
FOREIGN KEY (nome_portata) REFERENCES Portata);
CREATE TABLE Banchetto (
cod_banchetto CHAR(20) NOT NULL,
occasione CHAR(25),
data DATE,
num_part INTEGER,
cod_fiscale CHAR(16) NOT NULL,
nome_ristorante CHAR(25) NOT NULL,
loc_ristorante CHAR(25) NOT NULL,
id_menu CHAR(20) NOT NULL,
PRIMARY KEY (cod_banchetto),
FOREIGN KEY (nome_ristorante, loc_ristorante) REFERENCES Ristorante,
FOREIGN KEY (id_menu) REFERENCES Menu);
CREATE TABLE Invitato (
cod_banchetto CHAR(20) NOT NULL,
nome CHAR(25) NOT NULL,
PRIMARY KEY (cod_banchetto, nome),
FOREIGN KEY (cod_banchetto) REFERENCES Banchetto);
CREATE TABLE Offre_s (
nome_ristorante CHAR(25) NOT NULL,
loc_ristorante CHAR(25) NOT NULL,
nome_specialita CHAR(25) NOT NULL,
PRIMARY KEY (nome_ristorante, loc_ristorante, nome_specialita),
FOREIGN KEY (nome_ristorante, loc_ristorante) REFERENCES
Ristorante_Carat,
FOREIGN KEY (nome_specialita) REFERENCES Portata);
CREATE TABLE Offre_m (
nome_ristorante CHAR(25) NOT NULL,
loc_ristorante CHAR(25) NOT NULL,
id_menu CHAR(20) NOT NULL,
PRIMARY KEY (nome_ristorante, loc_ristorante, id_menu),
FOREIGN KEY (nome_ristorante, loc_ristorante) REFERENCES Ristorante,
FOREIGN KEY (id_menu) REFERENCES Menu);
SQL92: Le query
In questa sezione e' possibile vedere il comportamento dei 3 DBMS rispetto a dieci
interrogazioni in SQL92. Dopo aver creato le tabelle e aver popolato la base di dati analizziamo
come i vari sistemi implementano la parte relativa al linguaggio di interrogazione dello standard
SQL-92.
Query 1
Per conto di quale cliente e' stato servito il pranzo comprensivo della specialita'
‘Lepre in Salmi'’?
SELECT pe.cod_fiscale
FROM persona pe, banchetto b, comprende co, portata p
WHERE pe.cod_fiscale=b.cod_fiscale AND b.id_menu= co.id_menu
AND co.nome_portata=p.nome AND p.nome='Lepre in salmi';
Oracle
ok
DB2
ok
Informix
ok
Query 2
A quali banchetti ha preso parte Mario Rossi?
SELECT b.cod_banchetto
FROM invitato i, banchetto b
WHERE i.cod_banchetto = b.cod_banchetto AND i.nome = 'Mario Rossi';
Oracle
ok
DB2
ok
Informix
ok
Query 3
Per ogni menu' standard, quanti ristoranti lo offrono?
SELECT m.id_menu,count (m.id_menu) AS numero_ristoranti
FROM offre_m m
GROUP BY m.id_menu;
Oracle
ok
DB2
ok
Informix
ok
Query 4
Quanti ristoranti offrono un menu' standard, che comprenda 2 portate
SELECT COUNT (DISTINCT (M.nome_ristorante, M.loc_ristorante) As TotRist
FROM ( SELECT C.id_menu, Count (*) AS NumPort
FROM comprende C
GROUP BY C.id_Menu) AS PortTot, offre_m M
WHERE PortTot.id_menu = m.id_menu
AND PortTot.Numport = 2;
Oracle
NO
DB2
NO
Informix
NO
Perche' non funziona: Nessuno dei 3 DBMS accetta piu' di un argomento nella clausola
DISTINCT. Oracle non permette l'uso della parola chiave AS nella FROM per ridenominare le
tabelle, mentre Informix e DB2 funzionano correttamente sia in sua presenza che in sua assenza.
Riformulando la l’interrogazione:
SELECT COUNT (DISTINCT M.Nome_ristorante)
FROM Offre_m M, (SELECT C.id_menu, COUNT (*) AS NumPort
FROM comprende C
GROUP BY C.id_menu) PortTot
WHERE PortTot.id_menu = m.id_menu AND PortTot.Numport = 2;
Oracle
ok
DB2
ok
Informix
NO
Perche' non funziona: Informix non permette di specificare una SELECT annidata dentro una
FROM.
Query 5
Quali aziende hanno ordinato più di 2 banchetti?
SELECT a.cod_fiscale
FROM azienda a
WHERE a.cod_fiscale IN (SELECT b.cod_fiscale
FROM banchetto b
GROUP BY b.cod_fiscale
HAVING COUNT (b.cod_banchetto)2);
Oracle
ok
DB2
ok
Informix
ok
Query 6
Quanti posti hanno i ristoranti prenotati dalla azienda con il piu' alto capitale
sociale?
SELECT R.posti
FROM ristorante R, banchetto B, azienda A
WHERE A.cod_fiscale=B.cod_fiscale AND B.nome_ristorante=R.nome
AND B.loc_ristorante=R.localita
AND a.capitale_soc ALL (SELECT A1.capitale_soc
FROM Azienda A1);
Oracle
ok
DB2
ok
Query 7
Informix
ok
Quali sono i nomi delle portate che compaiono nel maggior numero di menu'?
SELECT Temp.Nome_Portata
FROM (SELECT CO.Nome_Portata, COUNT (CO.id_Menu) AS TotMenu
FROM Comprende CO
GROUP BY CO.Nome_Portata) Temp
WHERE Temp.TotMenu=(SELECT MAX(Temp.TotMenu)
FROM Temp);
Oracle
NO
DB2
NO
Informix
NO
Perche' non funziona: Nessuno permette la creazione di una tabella temporanea all'interno della
clausola FROM che venga anche referenziata in una SELECT all'interno della WHERE
Riformulando l'interrogazione diversamente tutto funziona correttamente
SELECT C1.nome_portata
FROM Comprende C1
GROUP BY C1.nome_portata
HAVING COUNT(C1.id_menu) = ALL(SELECT COUNT(C.id_menu) AS tot_menu
FROM Comprende C
GROUP By C.nome_portata);
Oracle
ok
DB2
ok
Informix
ok
Query 8
Esistono ristoranti non caratteristici che offrono specialita'?
SELECT S.Nome_Ristorante
FROM Offre_S S
WHERE S.Nome_Ristorante NOT IN (SELECT R.Nome
FROM Ristorante_carat R);
Oracle
ok
DB2
ok
Informix
ok
Query 9
Quali invitati hanno partecipato a 2 o piu' banchetti offerti da clienti diversi?
SELECT I.Nome
FROM Invitato I
GROUP BY I.Nome
HAVING 2<(SELECT COUNT (DISTINCT B.Cod_fiscale)
FROM Invitato I1,Banchetto B
WHERE I.Nome=I1.Nome AND B.Cod_Banchetto=I1.Cod_Banchetto);
Oracle
ok
DB2
ok
Informix
ok
Query 10
Quali sono gli invitati che hanno preso parte ai banchetti con i menu' piu' costosi e,
in quali ristoranti?
SELECT I.NOME
FROM Iinvitato I, Banchetto B, Menu M
WHERE I.cod_banchetto=B.cod_banchetto AND
M.id=B.id_menu AND
M.costo IN (SELECT MAX(M1.costo)
FROM MENU M1);
Oracle
ok
DB2
ok
Informix
ok
Conclusioni
Nessuno dei 3 DBMS accetta piu' di un argomento nella clausola DISTINCT (Query 4)
Informix non permette di specificare una SELECT annidata dentro una FROM. (Query 4)
Nessuno permette la creazione di una tabella temporanea all'interno della clausola FROM che
venga anche referenziata in una SELECT all'interno della WHERE.(Query 7)
Oracle non permette l'uso della parola chiave AS nella FROM per ridenominare le tabelle,
mentre Informix e DB2 funzionano correttamente sia in sua presenza che in sua assenza.
Trigger
Tutti e tre i DBMS mettono a disposizione del progettista un SQL ampliato con istruzioni
procedurali che puo' essere usato per scrivere dei trigger. Questo linguaggio non e' standard e
l'idea di scrivere trigger in Java nasce dalla speranza di poter superare questo limite. Abbiamo
scelto ed implementato come rappresentativo dei vincoli realizzabili solo attraverso trigger, il
primo relativo alle tabelle Banchetto, Azienda e Persona.
Banchetto Trigger
Il vincolo che devono soddisfare le istanze delle tabelle Banchetto, Azienda e Persona del
database implementato e' il seguente: l'aggiornamento o l’inserimento di una riga in Banchetto
deve contenere un codice fiscale presente anche nelle tabelle Persona oppure Azienda.
Per soddisfare questo vincolo e' stata scritta una procedura in Java invocata da un trigger che
genera una eccezione se il vincolo e' violato. La generazione di un'eccezione da parte di un
trigger provoca il rollback della istruzione SQL che ha attivato il trigger, cioe' INSERT o
UPDATE;
Trigger in Java
Nei tre DBMS che stiamo esaminando la implementazione del trigger e' simile, nel senso che
occorre scrivere la procedura Java, pubblicarne il prototipo nel DBMS effettuando il casting dei
tipi di dato passati come argomento ai tipi di dato disponibili in SQL, quindi creare un trigger e
collegare la trigger-action alla procedura.
Banchetto Trigger in Java su Oracle 8i
In Oracle 8i la macchina virtuale Java e' disponibile direttamente nel server, unitamente alla
possibilita' di far compilare a quest'ultimo il codice utente. Questo ci permette di scrivere il
codice Java di seguito alla CREATE PROCEDURE e di farlo compilare al DBMS. Le istruzioni
SQL che realizzano questo sono:
CREATE
import
import
import
public
OR REPLACE AND COMPILE JAVA SOURCE NAMED "BTrefAP" AS
java.sql.*;
java.io.*;
oracle.jdbc.driver.*;
class BT {
public static void refAP (String new_cf) throws SQLException {
Connection conn = new OracleDriver().defaultConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select cod_fiscale from
Persona " +
"where cod_fiscale = '" + new_cf + "'" + "UNION " +
"select cod_fiscale from Azienda " +
"where cod_fiscale = '" + new_cf + "'");
if (!rs.next()) {
System.out.println("Violato vincolo di referenza su
Azienda e Persona");
throw new SQLException("violazione vincolo di referenza
su Azienda e Persona");
}
rs.close();
stmt.close();
}
}
La pubblicazione della procedura appena creata si ottiene mediante la:
CREATE OR REPLACE PROCEDURE TRG(new_cf VARCHAR2)
AS LANGUAGE JAVA
NAME 'BT.refAP(java.lang.String)';
L'ultimo passo, necessario a collegare la procedura ad un trigger si ottiene in questo modo:
CREATE OR REPLACE TRIGGER BT
BEFORE INSERT OR UPDATE OF cod_fiscale ON Banchetto
FOR EACH ROW CALL TRG (:new.cod_fiscale)
L'implementazione del vincolo richiesto in Oracle non ha quindi rappresentato particolari
problemi.
Banchetto Trigger in Java su DB2 6.1
DB2 offre la possibilita' di scrivere funzioni definite dall'utente (udf) in vari linguaggi (c, c++,
rexx,...), tra cui anche Java, che vengono registrate nel server in maniera analoga a quanto
avviene in Oracle 8i.La differenza fondamentale tra i due DBMS consiste nel fatto che mentre in
Oracle la macchina virtuale Java e' presente nel server stesso, DB2 si serve di una macchina
virtuale esterna: e' necessario installare il JDK1.1 sulla macchina dove gira il DBMS, impostare
alcune variabili d'ambiente al quale quest'ultimo fa riferimento (CLASSPATH, ...) e far puntare
un valore di una tavola di sistema alla directory radice del JDK.
Per implementare il BanchettoTrigger come fatto in Oracle, quindi, occorre scrivere la classe
Java, la quale effettua i necessari controlli sulla validita' degli inserimenti e degli aggiornamenti,
pubblicarla nel server db2 come funzione che ritorna un intero (ad esempio: 0=inserimento
possibile, 1=inserimento scorretto) e quindi collegarla ad un trigger. Le difficolta' sorgono nel
momento in cui si scopre dalla documentazione della sintassi SQL di DB2 (IBM DB2 Universal
Database SQL Reference, Version 5.2, Document Number S10J-8165-01, comune alle versioni
5.2 e 6.1) che la CREATE TRIGGER puo' contenere, come trigger-action solo funzioni, sia
definite dall'utente che di sistema, e che una UDF non puo' contenere al suo interno istruzioni
SQL!
Ne segue l'impossibilita' di definire il trigger di cui abbiamo bisogno utilizzando la stessa
filosofia con cui lo abbiamo implementato in Oracle.
Banchetto Trigger in Java su Informix Dynamic Server 2000
Solo Informix Dynamic Server 2000 ha il supporto per funzioni definite dall'utente scritte in
Java. Inoltre, la versione dimostrativa per la piattaforma Windows NT disponibile presso il sito
di Informix non e' completa: la documentazione infatti fa riferimento ad una gerarchia di
directory inesistente. E' stato necessario servirsi della versione per Linux per poter scrivere
funzioni in Java.
A differenza di quanto avviene con Oracle, Informix si serve del JDK 1.1 di Sun per eseguire e
compilare il codice in Java, ed e' necessario effettuare una serie di modifiche in un catalogo di
sistema del DBMS per specificare i percorsi di ricerca della macchina virtuale Java, delle
eventuali librerie esterne e del codice da eseguire. La procedura necessaria a utilizzare una
funzione definita dall'utente in un trigger consiste dei seguenti passi:
•
scrittura della procedura in Java, (nel nostro caso, la procedura e' quella usata per Oracle, con
le necessarie modifiche per l'invocazione dei metodi JDBC di Informix), compilazione e
creazione di un archivio jar;
•
esecuzione di una procedura predefinita nel DBMS per installare il jar nel database
("install_jar()");
•
registrazione della funzione mediante l'istruzione CREATE PROCEDURE;
CREATE PROCEDURE BTRefAP(new_cf VARCHAR)
WITH (CLASS = "jvp")
EXTERNAL NAME 'BanchettoTRG:BT.refAP'
LANGUAGE JAVA;
L'istruzione crea la procedura BTRefAP() collegandola al metodo refAP() della classe BT che si
trova nell'archivio BanchettoTRG.jar.
Infine, la creazione del trigger e' molto simile a quella fatta per Oracle:
CREATE TRIGGER BT
INSERT ON Banchetto
REFERENCING NEW AS new
FOR EACH ROW EXECUTE PROCEDURE BTRefAP(:new.cod_fiscale)
La difficolta' nella scrittura di procedure in Java con Informix risiede tutta nel fatto che e' stato
necessario impostare un discreto numero di parametri del DBMS per permettergli di eseguire il
codice.
Trigger in SQL procedurale
Abbiamo implementato il trigger sulla tabella BANCHETTO anche in SPL/SQL in Informix,
PL/SQL in Oracle, DB2SQL in DB2.Benche' non siano identici, anche uno sguardo rapido rivela
una fortissima somiglianza sintattica fra il codice sorgente nei tre prodotti; scrivere un trigger
con questo metodo risulta piu' semplice e immediato rispetto al Java, ma non immediatamente
portabile. Esiste tuttavia una differenza fondamentale che distingue il DBMS di Informix dagli
altri due: mentre Oracle e DB2, quando specificata la clausola BEFORE INSERT nella creazione
del trigger intendono quello che il senso comune suggerisce (prima dell'esecuzione della
INSERT esegui il codice del trigger, quando la riga che stiamo per immettere soddisfa le
condizioni specificate nell'istruzione WHERE), in Informix esiste lo clausola INSERT BEFORE,
che sta a significare che, a prescindere dal contenuto della riga che stiamo inserendo (difatti nel
blocco di istruzioni che segue la BEFORE non e' possibile referenziare la nuova riga), occorre
eseguire in qualunque caso le istruzioni della BEFORE.
Banchetto Trigger in PL/SQL su Oracle 8i
Dal diagramma sintattico dell'istruzione CREATE TRIGGER, sembrerebbe possibile scrivere il
trigger nel seguente modo:
CREATE OR REPLACE TRIGGER banchetto_trg
BEFORE INSERT OR UPDATE OF cod_fiscale ON banchetto
REFERENCING NEW AS n
FOR EACH ROW
WHEN (n.cod_fiscale NOT IN (SELECT a.cod_fiscale FROM azienda
a)
AND n.cod_fiscale NOT IN (SELECT p.cod_fiscale FROM persona p))
(Raise_application_error(-20000, 'Codice fiscale non valido'));
Invece, cercando di eseguire questa istruzione si viene a conoscenza del fatto che non e' possibile
effettuare una query SQL all'interno della WHERE. Occorre quindi trovare una soluzione
alternativa: seguendo il suggerimento per la soluzione di un problema analogo nella
documentazione, abbiamo dichiarato ed utilizzato una coppia di cursori, uno per la tabella
PERSONA ed uno per la tabella AZIENDA, aventi come parametro il codice fiscale che si vuole
inserire o di cui si vuole effettuare un aggiornamento. Ciascun cursore ritorna il valore
NOTFOUND se fallisce nella ricerca del codice fiscale nelle suddette tabelle e questo ci
permette di generare un'eccezione. Il codice che gestisce l'eccezione esegue una chiamata alla
routine di sistema RAISE_APPLICATION_ERROR() che ferma l'esecuzione di tutta la
procedura, stampa un messaggio di errore e un numero di errore in un intervallo riservato agli
errori definiti dall'utente. Per default, non essendo questo numero di errore inviato ad un gestore
di eccezioni, viene eseguito il rollback dell'istruzione SQL che ha provocato l'esecuzione del
trigger, cioe' INSERT o UPDATE (vedi 'Oracle 8i - Application Developer's Guide', part number
A68003-01, pag. 10.44). Il codice e' il seguente:
CREATE OR REPLACE TRIGGER banchetto_trg
BEFORE INSERT OR UPDATE OF cod_fiscale ON BANCHETTO
FOR EACH ROW
DECLARE
Dummya
CHAR(20);
Dummyp
CHAR(20);
Invalid_cf
EXCEPTION;
CURSOR Dummy_cursora (cf CHAR) IS
SELECT a.cod_fiscale FROM azienda a
WHERE a.cod_fiscale = cf;
CURSOR Dummy_cursorp (cf CHAR) IS
SELECT p.cod_fiscale FROM persona p
WHERE p.cod_fiscale = cf;
BEGIN
OPEN Dummy_cursora (:new.cod_fiscale);
OPEN Dummy_cursorp (:new.cod_fiscale);
FETCH Dummy_cursora INTO Dummya;
FETCH Dummy_cursorp INTO Dummyp;
IF Dummy_cursora%NOTFOUND THEN
CLOSE Dummy_cursora;
IF Dummy_cursorp%NOTFOUND THEN
RAISE Invalid_cf;
END IF;
CLOSE Dummy_cursorp;
END IF;
EXCEPTION
WHEN Invalid_cf THEN
Raise_application_error(-20000, 'Codice fiscale non valido!');
CLOSE Dummy_cursorp;
END;
L'esecuzione di prove di inserimento nella tabella BANCHETTO con diversi valori del codice
fiscale ci hanno permesso di verificare che il comportamento del trigger e' quello desiderato.
Banchetto Trigger in DB2SQL su DB2 6.1
La possibilita' offerta da DB2 di includere una query nella WHERE ha reso la scrittura del
trigger molto piu' semplice, rispetto ad Oracle. Questo e' il codice:
CREATE TRIGGER trg2
NO CASCADE BEFORE INSERT ON banchetto
REFERENCING NEW AS n
FOR EACH ROW MODE DB2SQL
WHEN(n.cod_fiscale NOT IN (SELECT a.cod_fiscale FROM azienda a
UNION SELECT p.cod_fiscale FROM persona p))
SIGNAL SQLSTATE '75000' ('Not present in Azienda or Persona')
Banchetto Trigger in SPL SQL su Informix Dynamic Server 2000
La trigger action, come nei due casi precedenti, consta della generazione di una eccezione. Data
l'impossibilita' di effettuare una chiamata diretta all'istruzione RAISE EXCEPTION all'interno
della definizione del trigger, occorre innanzi tutto definire una procedura che effettui una tale
chiamata e quindi collegarla alla trigger action. Di seguito il semplice codice:
CREATE PROCEDURE errore()
RAISE EXCEPTION -746, 0, 'errore: codice fiscale inserito non
valido!'
END PROCEDURE
Definizione del trigger:
CREATE TRIGGER banchetto
INSERT OR UPDATE OF cod_fiscale ON BANCHETTO
REFERENCING NEW AS n
FOR EACH ROW
WHEN (n.cod_fiscale NOT IN (SELECT a.cod_fiscale FROM azienda
a)
AND n.cod_fiscale NOT IN (SELECT p.cod_fiscale FROM
persona p))
(EXECUTE PROCEDURE errore())
Rispetto a DB2, nella WHERE e' vietato l'uso dell'operatore UNION, cosa che ha reso necessario
l'uso degli operatori logici.
Da notare che l'eccezione esegue un rollback dell'istruzione che ha generato l'invocazione del
trigger solo se il database sul quale stiamo operando e' stato creato con il logging attivo. Questo
non avviene di default, ed e' necessario specificarlo all'atto della creazione del database. Questo
si ottiene con la seguente istruzione:
CREATE DATABASE AgenziaRelazioniPubbliche WITH LOG;
Gli indici
In questa sezione ci proponiamo di analizzare quali possibilita' vengono offerte dai software per
la gestione degli indici e in particolar modo quali tipi vengono messi a disposizione del
progettista per ottimizzare al massimo le prestazioni.
B-Tree
Tutti e tre i prodotti mettono a disposizione i B-Tree che sono il modo piu' frequente di gestire
gli indici. Le opzioni per questo tipo di indicizzazione sono molto complete e sono uguali per
tutti. E' possibile:
specificare se l'indice deve essere in ordine crescente o decrescente; indicare la percentuale di
spazio libero su ogni nodo dell'albero al momento delle sua creazione, secondo la previsione di
crescita del database; richiedere la “clusterizzazione” dell'indice. richiedere l'unicita' della chiave
creare l'indice su piu' colonne della tabella (per aumentare l'unicita' della chiave).
Hash
Oracle e' l'unico ad offrire questo potente metodo d'indicizzazione. Il manuale non parla di indici
di tipo hash ed effettivamente non e' possibile utilizzare la CREATE INDEX per ottenere questo
tipo di indice.
La procedura e' piu' complessa perche' le tabelle devono essere memorizzate su disco
differentemente dalle condizioni normali. Bisogna preparare lo spazio su disco prima: e'
necessario creare un "CLUSTER" specificando la dimensione della chiave, il numero di chiavi,
la dimensione del cluster. Al momento di creazione della tabella, questa deve essere inserita nel
"CLUSTER" creato. E' possibile utilizzare una funzione messa a disposizione dal sistema, ma e'
anche possibile crearla.
R-Tree
Per quello che riguarda metodi di indicizzazione basati su albero, Informix mette a disposizione
un altro tipo di albero chiamato R-Tree (range tree). Questa struttura e' studiata per migliorare le
prestazioni delle interrogazioni che includono nello spazio di ricerca un intervallo di valori
anziche' un valore preciso e per tutte le strutture multidimensionali a due o tre dimensioni piu'
eventualmente il tempo. La struttura di un R-Tree e' molto simile a quella di un B-Tree sebbene i
dati memorizzati al suo interno siano differenti. Il suo obiettivo e' di individuare un insieme di
dati che sia molto piu' piccolo dell'insieme di partenza con la massima velocita' possibile,
piuttosto che trovare subito l'insieme finale. L'insieme temporaneo trovato e' conservativo:
spesso contiene piu' dati di quelli richiesti, ma contiene sempre tutte le soluzioni. La visita di un
R-Tree permette di scartare una grande quantita' di dati non utili alla ricerca senza analizzarli.
Questo viene fatto eliminando dati che cadono fuori dall'area di interesse. Su questo argomento
la Informix mette a disposizione una sezione dedicata che spiega in dettaglio il funzionamento.
Bitmap
Sia Oracle che Informix mettono a disposizione un interessante metodo di indicizzazione: il tipo
bitmap.
Questo e' basato sull'associazione di una mappa di bit alla chiave di ricerca. Quindi se ci sono
pochi tipi diversi di chiave la mappa di bit si mantiene piccola e cosi' il file indice. Inoltre un file
piccolo viene caricato in poco tempo diminuendo accesso a disco e migliorando le performance.
Funzione
Sia Oracle che Informix offrono un sistema di indicizzazione basato su funzioni definite
dall'utente. Queste funzioni possono essere espressioni algebriche, codice PL/SQL, C o Java. Il
valore ritornato dalla funzione viene utilizzato come chiave per la gestione dell'indice. Un
esempio riportato nella documentazione di entrambi i software e' quello della funzione Area. Su
una tabella contenente due colonne che rappresentano altezza e larghezza di una certa superficie
a cui fa riferimento il record, e' possibile creare un indice sull'area, cosi' da poter ottimizzare
un’interrogazione nella cui WHERE sia presente l'area. La funzione ha come parametri altezza e
larghezza e restituisce il valore dell'area che viene utilizzata come chiave per l'indice. Informix
dice chiaramente che questo tipo di indicizzazione e' basato su B-Tree o R-Tree; benche' Oracle
non lo chiarisca espressamente, l'uso del B-Tree si intuisce dalla possibilita' di eseguire query su
un intervallo di dati.
Indici programmabili
Oracle ed Informix mettono a disposizione la possibilita' di programmare completamente le
funzioni di indicizzazione permettendo di scrivere il codice relativo alla loro completa gestione.
In Oracle questi vengono chiamati "Domain indexes" e le funzioni vengono caricate attraverso
l'istruzione IndexType. In Informix questo avviene attraverso un modulo chiamato "DataBlade".
Altre possibilita'
Tutti e tre i DBMS mettono a disposizione la possibilita' di dividere le tabelle su piu' dischi per
ottimizzare il loro accesso. Oracle offre alcune opzioni veramente interessanti: e' possibile
richiedere la compressione degli indici per diminuire l'accesso a disco a spese della CPU e la
possibilita' di avere tabelle “clusterizzate”, ovvero tabelle che condividono colonne in comune
vengono messe nella stessa area del disco per migliorare le prestazioni del join.
DB2 offre la possibilita' di fare una "Summary Table", ovvero una tabella che contiene al suo
interno dati di piu' tabelle ed evita quindi la necessita' di fare dei join su certe colonne delle
stesse.
Conclusioni
Appare chiaro che Oracle e' da questo punto di vista il piu' versatile in quanto mette a
disposizione del progettista tutti gli strumenti disponibili attualmente eccetto gli R-Tree. Informix
offre meno possibilita', ma e' ancora molto versatile. Il DB2 invece e' il piu' povero di tutti in
quanto mette a disposizione i pur sempre validi B-Tree.
Query optimizer
In questa sezione vogliamo esaminare come il progettista puo' vedere e migliorare le scelte del
query optmizer. Tutti i tre prodotti danno la possibilita' di vedere il piano scelto dal query
optimizer, influenzarne le scelte o escludere del tutto l'ottimizzatore.
Piano di esecuzione di una query
I comandi SQL EXPLAIN (per DB2), SET EXPLAIN (per Informix) o EXPLAIN PLAN (per
Oracle) permettono di esaminare il piano di esecuzione attuale di una query; questo consiste
nella descrizione dell'ordine e della modalita' con cui vengono eseguite le operazioni. E' cosi'
possibile conoscere le scelte fatte dall'ottimizzatore per poterle successivamente modificare.
DB2, a differenza degli altri due prodotti, mette a disposizione anche uno strumento grafico.
Riportiamo un esempio relativo alla prima query:
Strategie utilizzate dall'ottimizzatore
Le strategie offerte sono due: basata sulle regole e basata sui costi.
L'approccio basato sulle regole e' disponibile solo in Oracle per mantenere la compatibilita' con
le vecchie versioni del DBMS. L'ottimizzatore sceglie il percorso di accesso ai dati basandosi
sulla disponibilita' del percorso di accesso per l'istruzione corrente e sul punteggio assegnato a
tale percorso. Lo svantaggio di questo modello consiste nel fatto che tale punteggio e' fissato
staticamente e quindi non modificabile in funzione di necessita' specifiche.
L'approccio basato sui costi e' disponibile su tutti e tre i prodotti ed e' il piu' performante. Si basa
su una stima del costo di esecuzione di un piano (I/O, CPU, tempo...) per ciascun piano preso in
esame dall'ottimizzatore. Per fare questa stima il sistema mantiene delle tabelle contenenti dati e
statistiche relativi alla popolazione della base di dati. Vengono utilizzati degli istogrammi per
raccogliere delle informazioni statistiche. Tutti questi dati sono mantenuti in tabelle di sistema.
Ogni volta che si modifica in maniera sostanziale la base di dati (caricamento di un numero
rilevante di righe o modifica dello schema di una tabella) si dovra'; procedere all'acquisizione di
nuove informazioni. Informix e DB2 non mettono a disposizione nessuno strumento per
l'aggiornamento automatico delle statistiche a differenza di Oracle. Dato l'elevato costo
dell'operazione di aggiornamento, Informix mette a disposizione due diversi livelli di dettaglio
per l'acquisizione dei dati. Anche per Oracle e' possibile scegliere tra due diversi tipi di raccolta
dei dati: uno per righe, piu' dettagliato, e l'altro per blocchi, piu' veloce.
Metodi d'accesso
In mancanza di indici, il metodo d'accesso ai dati e' la lettura sequenziale.
I tre DBMS mettono a disposizione, inoltre, degli altri metodi di scansione relativi a certe loro
peculiarita'.
Oracle nomina la presenza di un metodo chiamato “Cluster Scan” per scandire le “Cluster
Table”.
Il metodo “Sample Table Scans” serve per fare statistiche rilevando righe campione da una
tabella. Viene utilizzata in query adibite a questo scopo.
DB2 offre un metodo chiamato “Table Scan” per scandire una “Summary Table” ed evitare un
join.
In presenza di indici, sono supportati tutti i metodi classici, ovvero l'accesso diretto al record,
l'accesso a piu' record in un intervallo e l'uso del solo indice per avere i dati richiesti quando
possibile.
DB2 parla di un “Multiple Index Access”: questo metodo implica l'uso di piu' indici disponibili
su una certa tabella. Viene applicato, per esempio, nel caso in cui la WHERE contenga
condizioni di AND e di OR su colonne sulle quali sia disponibile un indice. Ogni indice viene
utilizzato per creare una lista di Record ID che soddisfano il predicato in questione. Per avere la
lista finale di record da prelevare bisogna: nel caso OR eliminare i duplicati tra le liste, nel caso
AND la creazione di una bitmap dinamica velocizza la creazione di una lista in cui compaiono
solo i record presenti su entrambe.
Metodi di Join
Il Nested Loop Join, Hash Join sono comuni a tutti e tre i prodotti. Oracle e DB2 offrono inoltre
anche Sort-Merge Join e Star Join. Quest'ultimo serve solo quando nel join vengono usate chiavi
primarie o chiavi straniere tra una tabella di tipo “Fact tables” e una di tipo “Lookup table”.
Queste due tabelle sono generalmente usate nei datawearehouse: “Fact tables” sono tabelle molto
grandi, mentre “Lookup table” sono tabelle molto piu' piccole che contengono informazioni su
degli attributi delle prime.
Oracle nomina Cluster Join per le tabelle di tipo “Cluster Table”.
Influenzare l'ottimizzatore
Oracle ed Informix consentono di sostituire alle scelte dell'ottimizzatore le proprie e inoltre
mettono a disposizione delle istruzioni per poter influenzare le scelte dell'ottimizzatore:
permettono intervenire sulla query mediante indicazioni che possono essere aggiunte all'interno
dell'istruzione SQL di SELECT, UPDATE o DELETE per forzare alcune scelte. Oracle le
chiama “hints”, Informix le chiama "direttive di ottimizzazione".
E' possibile modificare:
•
La strategia dell'ottimizzatore (basato sulle regole, basato sui costi)
•
Scelta dell'obiettivo: miglior tempo di esecuzione, miglior tempo di risposta.
•
Il tipo di percorso di accesso
•
L'ordine di join per un'operazione di join
•
Quale tipo di join scegliere
DB2 ed Informix hanno inoltre delle classi di ottimizzazione per facilitare il compito del
progettista. DB2 offre la possibilita'; di scegliere tra 10 classi di ottimizzazione (0-9) che
impegnano in modo differente le risorse del nostro sistema, Informix da' la possibilita'; di variare
il tempo speso nella ricerca del piano piu' veloce modificando la classe di ottimizzazione.
Le classi del DB2 sono:
Classe 0: Per questa classe l'ottimizzatore utilizza il minimo numero possibile di risorse: tale
classe dovrebbe essere utilizzata solo per query molto semplici e che accedono a tabelle ben
indicizzate. Vengono considerate solo statistiche la cui distribuzione sia uniforme. Vengono
considerati solo Block Nested Loop Join e percorsi d'accesso tramite indice. La scelta del piano
viene effettuata tramite un algoritmo goloso.
Classe 1: Vengono considerate statistiche a distribuzione non uniforme. Sono considerati il
Merge Join e la scansione completa di una tabella. E' ora considerato anche lo Star Join. Utilizza
l'algoritmo goloso.
Classe 2: Per questa classe l'ottimizatore utilizza entrambi i tipi di statistiche: a distribuzione
uniforme e non. Sono inoltre disponibili tutti i metodi di riscrittura della query. Utilizza
l'algoritmo goloso.
Classe 3: Questa classe utilizza, come le successive e a differenza delle precedenti, il Dynamic
Programming Join Enumeration per la scelta del piano di esecuzione della query. Questo
algoritmo esplora piu' a fondo lo spazio delle soluzioni, ed e' raccomandato in presenza di join
multipli; a differenza della seconda classe pero' non utilizza tutti i metodi di riscrittura e solo
statistiche a distribuzione non uniforme. Utilizza anche il Multiple Index Access
Classe 5: Come detto e' la classe di default. Utilizza tutte le statistiche e la tutte le regole di
riscrittura della query. Inoltre, per interrogazioni particolarmente complesse, vengono
parzialmente utilizzate regole euristiche per limitare il tempo speso nella selezione di un piano
d'accesso.
Classe 7: Differisce dalla classe 5 per un uso completo delle regole euristiche.
Classe 9: Mette a disposizione tutti i metodi di riscrittura, tutte le statistiche, tutti i possibili
metodi di join e tutti i metodi d'accesso.
NB: le classi 4 e 6 non sono utilizzate.
E' inoltre possibile inserire nella SELECT delle clausole per influenzare l'ottimizzatore in casi
specifici per:
•
Recuperare solo quelle righe che permettono un'interrogazione read-only per evitare lock
esclusivi che possono penalizzare le prestazioni del sistema.
•
Limitare il numero di tuple recuperato. E' possibile in applicazioni tipo “browser” di voler
recuperare solo alcune righe.
•
Gestire in maniera piu' efficente il rilascio di memoria dovuto ad un cursore.
Conclusioni
Informix ed Oracle sono abbastanza simili nella filosofia d'approccio all'ottimizzazione,
permettendo d'intervenire su un gran numero di parametri a diversi livelli fino ad arrivare alla
completa esclusione dell'ottimizzatore. DB2 si differenzia dai precedenti mettendo a disposizione
dei profili predefiniti di ottimizzazione: L'impressione e' stata quella che DB2 offra
un'interazione piu' ad alto livello impedendo volontariamente l'intrusione nelle scelte del sistema.