Introduzione a MySQL - Dipartimento di Matematica e Informatica

Transcript

Introduzione a MySQL - Dipartimento di Matematica e Informatica
Corso di Laurea in Informatica
Base Dati
a.a. 2012-2013
MySQL
Procedure, funzioni, trigger e scheduling
Laboratorio
Ing. G. Laboccetta
Dott.ssa. V. Policicchio
Definizione di stored procedure
• Una stored routine è costituita o da una procedura o da una
funzione
• Una stored procedure è costituita da un insieme di istruzioni
SQL che vengono memorizzate nel server con un nome
identificativo, può essere invocata con CALL e torna indietro
valori usando le variabili di output
• Una stored function può essere chiamata mediante il solo
nome, e può ritornare un valore scalare
• Delle stored routines possono chiamare altre stored routines
Proprietà
•
Una stored routine è associata ad un particolare database, e
questo vuol dire che:
1.
2.
3.
•
Quando viene invocata, implicitamente viene eseguito USE
<database>, non sono ammessi USE all’interno della routine
E’ possibile riferirsi ad una routine non presente nel database
corrente prefissandola con il nome del suo database
Il DROP del db provoca il DROP delle routine associate
Se non indicato diversamente le SR sono associate al DB di
default
Perché usare una SR?
• Girano in qualsiasi ambiente. Dato che sono sul
server del DB, non dipendono dall’applicativo che le
usa e dal linguaggio di programmazione.
L’aggiornamento di una SR aggiorna la logica di
funzionamento del DB senza la necessità di
modificare i client
• Possono ridurre il traffico di rete, utilizzando dei
result set direttamente sul DB senza muoverli verso il
client per l’elaborazione.
Sintassi
CREATE
[DEFINER = { user | CURRENT_USER }]
PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
//parametri:
[ IN | OUT | INOUT ] nomeParametro tipo
CREATE
[DEFINER = { user | CURRENT_USER }]
FUNCTION sp_name ([func_parameter[,...]])
RETURNS type
[characteristic ...] routine_body
Esempio di procedura
DELIMITER //
CREATE PROCEDURE simpleproc (OUT param1 int)
BEGIN
SELECT COUNT(*) INTO param1 FROM imp;
END;
//
DELIMITER ;
CALL simpleproc(@a);
SELECT @a;
Osservazioni
• DELIMITER cambia il “terminatore” di linea da ; a //, in modo che sia
possibile usare ; nella definizione della procedura e non venga interpretato
da mysql
• La procedura torna un risultato tramite l’unico parametro definito, che è
un parametro di uscita. Il parametro viene riempito dalla istruzione CALL,
e letto tramite una semplice SELECT
• E’ possibile avere procedure molto più complesse
• Per ottenere informazioni a proposito delle procedure:
SHOW PROCEDURE STATUS;
SHOW CREATE PROCEDURE <nome>;
Procedura complessa
DELIMITER //
CREATE PROCEDURE procedura1 (param1 INT, param2 CHAR(3),
OUT param3 INT)
BEGIN
DECLARE finito INT default 0;
DECLARE a INT;
DECLARE b CHAR(50);
DECLARE cur1 CURSOR FOR SELECT id,nome
FROM clienti WHERE cat = param2;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'
SET finito = 1;
OPEN cur1;
SET param3 = 0;
FETCH cur1 INTO a,b;
ciclo: WHILE NOT finito DO
IF param3 < param1 THEN
SET param3 = param3 + 1;
FETCH cur1 INTO a,b;
ELSE
LEAVE ciclo;
END IF;
END WHILE ciclo;
END; //
DELIMITER ;
Analisi
DELIMITER //
Dice al server che l’istruzione di CREATE non termina con il ; che invece mi serve
all’interno delle Stored Procedure
CREATE PROCEDURE procedura1 (param1 INT, param2 CHAR(3), OUT param3 INT)
La procedura ha come nome procedura1, e usa tre parametri, i primi due di input e
l’ultimo di output
BEGIN
END
Il codice da eseguire deve essere delimitato da BEGIN e END
Analisi
DECLARE finito INT default 0;
DECLARE a INT;
DECLARE b CHAR(50);
Definiamo tre variabili da usare all’interno della routine, una di queste inizializzata con un default
DECLARE cur1 CURSOR FOR SELECT id,nome
FROM clienti WHERE cat = param2;
Definiamo un “cursore”
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'
SET finito = 1;
Definiamo un “handler” che non interrompe l’esecuzione se si verifica la condizione indicata
L’ordine delle dichiarazioni è importante, deve essere quello qui illustrato!
Analisi
OPEN cur1;
SET param3 = 0;
Questa è la prima vera operazione, l’apertura del cursore. In questo modo viene eseguita
la select, che costituisce il corpo del cursore. Inizializziamo il parametro di output e
passiamo alla
FETCH cur1 INTO a,b;
Con questa operazione il cursore legge la prima riga tornata dalla select, e scrive i valori
trovati nelle variabili a e b. Fatta questa operazione il “cursore” si sposta sul secondo
risultato trovato, che tornerà alla successiva fetch.
Notare che la prima fetch è esterna al ciclo, per gestire il caso di tabella senza righe.
I cursori sono READ ONLY, e NOT SCROLLABLE
Analisi
ciclo: WHILE NOT finito DO
END WHILE ciclo;
Segue un ciclo di elaborazione, che viene eseguito fino a che il valore di “finito” è falso (ossia =0 come
inizializzato). Tale valore cambia nel momento in cui il cursore ha fatto la fetch dell’ultima riga
tornata dalla select, e non ha righe su cui spostarsi: in quel momento infatti il SQLSTATE ha
valore ‘02000’, e l’handler che abbiamo definito cambia il valore della variabile in 1.
IF param3 < param1 THEN
SET param3 = param3 + 1;
FETCH cur1 INTO a,b;
ELSE
LEAVE ciclo;
END IF;
Infine all’interno del ciclo si verifica che la variabile param3 abbia raggiunto param1, e in tal caso con
LEAVE si abbandona il ciclo stesso. Se non lo ha raggiunto si effettua un’altra FETCH del
cursore. Nell’esempio non uso a e b, ma potrei farlo...
Analisi
Il ciclo quindo termina o quando si è verificata la condizione sui parametri,
oppure quando il cursore si è esaurito, e al termine della esecuzione il
parametro di output conterrà il numero di righe lette.
Riassumendo in una SP si possono trovare le definizioni di :
1.
2.
3.
4.
Variabili (declare, in, out)
Condizioni (sqlstate)
Cursori
Handler
Si possono usare nel codice:
1. Variabili
2. Cursori
3. Controllo di flusso (if, loop, repeat, while, iterate, leave)
codice di esempio:
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
DECLARE a INT default 0;
DECLARE cond1 CONDITION FOR 1045;
DECLARE cond2 CONDITION FOR SQLSTATE '02000';
DECLARE cur1 CURSOR FOR query;
DECLARE EXIT HANDLER FOR cond1 SET variabile = valore;
DECLARE CONTINUE HANDLER FOR cond2 SET variabile = valore;
CONTINUE HANDLER FOR SQLWARNING;
OPEN cur1;
FETCH cur1 INTO variabile1 variabile2;
CLOSE cur1;
IF condizione THEN istruzioni [ELSEIF condizione THEN istruzioni] ... [ELSE istruzioni] END IF;
[nome:] LOOP istruzioni
END LOOP [nome];
[nome:] REPEAT istruzioni UNTIL condizione
END REPEAT [nome];
[nome] WHILE condizione DO istruzioni
END WHILE [nome];
ITERATE nomeCiclo;
LEAVE nomeCiclo;
Modifica
•
Una procedura può essere eliminata con
DROP PROCEDURE <nome>
•
E’ possibile modificare i suoi permessi con
ALTER PROCEDURE <nome> SQL SECURITY { DEFINER | INVOKER }
•
Nel caso DEFINER è l’utente che ha definito la procedura a dover avere i permessi
necessari alla sua esecuzione, nel caso INVOKER è invece l’utente che la esegue.
Stored Functions
• Rispetto alla definizione delle procedure si aggiunge la capacità di tornare
un valore con la clausola RETURNS, e i parametri sono esclusivamente in
input.
• E’ obbligatorio tornare un valore!
drop function funz2;
DELIMITER //
CREATE function funz2(codFis char(16)) returns decimal
BEGIN
DECLARE c INT default 0;
select stipendio into c FROM impiegati
where cf=codFis;
return c + 3*c;
END;
//
DELIMITER ;
ESERCIZIO
•
Scrivere una stored procedure che somma tutti gli stipendi degli impiegati.
DELIMITER //
CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE c INT default 0;
DECLARE cur_1 CURSOR FOR SELECT stipendio FROM imp;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
SET b = 0;
REPEAT
FETCH cur_1 INTO a;
if b = 0 then
SET c=c+a;
end if;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = c;
END;
//
DELIMITER ;
ESERCIZIO
•
Scrivere una stored procedure che inserisca nella tabella impsede1 tutti i codici
degli impiegati della sede 1
DELIMITER //
CREATE PROCEDURE p26 ()
BEGIN
DECLARE b INT;
DECLARE a char(4);
DECLARE cur_1 CURSOR FOR SELECT codimp FROM imp WHERE sede='S01';
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
FETCH cur_1 INTO a;
REPEAT
INSERT INTO corsosql.impSede1 VALUES (a);
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
END;
//
DELIMITER ;
TRIGGERS
• Sono oggetti associati a tabelle, che vengono attivati nel momento in cui
un determinato evento si verifica in quella tabella.
• Nella definizione del trigger stabiliamo per quale evento deve essere
attivato, ossia per inserimento di righe, modifiche o cancellazioni, e se
deve essere eseguito prima o dopo tale evento
• Si hanno dunque i seguenti tipi di trigger:
1.
2.
3.
4.
5.
6.
BEFORE INSERT
BEFORE UPDATE
BEFORE DELETE
AFTER INSERT
AFTER UPDATE
AFTER DELETE
Sintassi
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_body
•
La clausola DEFINER specifica se come creatore del trigger deve essere considerato l'utente attuale
(default) o un altro utente specificato nella forma nome@host.
•
trigger_time indica il momento di attivazione del trigger. Esso può essere BEFORE o AFTER la modifica di
ogni riga della tabella a cui è associato il trigger.
•
trigger_event è l’evento che scatena l’esecuzione del trigger. Esso può essere INSERT, UPDATE, DELETE
•
trigger_body rappresenta il codice che viene eseguito all’attivazione del trigger. Normalmente racchiuso
tra BEGIN….END.
Anche se associato ad una tabella il trigger deve avere nome univoco all’interno del database.
Quando si fa riferimento a inserimenti o cancellazioni di righe, non si intende necessariamente una
istruzione INSERT o DELETE, ma qualsiasi operazione dalla quale scaturisca l’evento interessato (ad
esempio il caricamento dei dati da shell)
•
•
Esempio
delimiter //
CREATE TRIGGER upd_check BEFORE UPDATE ON account
FOR EACH ROW
BEGIN
IF NEW.amount < 0 THEN
SET NEW.amount = 0;
ELSEIF NEW.amount > 100 THEN
SET NEW.amount = 100;
END IF;
END;//
delimiter ;
NEW si riferisce alla nuova riga che sta per essere scritta, in questo caso aggiornata, e si
può usare in caso di INSERT e UPDATE.
E’ disponibile anche OLD, che si riferisce ai valori precedenti la modifica, e si può
utilizzare nel caso di UPDATE e DELETE
La modifica con SET è possibile solo per i valori NEWe nei trigger di tipo BEFORE
Attenti con i trigger BEFORE: se l’inserimento non riesce potrebbero lasciare dati
inconsistenti
Esempio
delimiter //
CREATE TRIGGER log_upd AFTER UPDATE ON impiegati
FOR EACH ROW
BEGIN
DECLARE u varchar(40);
select user() into u;
insert into TB_LOG values(NULL, NOW(), 'UPDATE', OLD.stipendio,
NEW.stipendio, u);
END;//
delimiter ;
Transazioni
• L'uso delle transazioni permette di "consolidare" le modifiche
alla base dati solo in un momento ben preciso: dal momento
in cui avviamo una transazione, gli aggiornamenti rimangono
sospesi (e invisibili ad altri utenti) fino a quando non li
confermiamo (commit); in alternativa alla conferma è
possibile annullarli (rollback).
• MySQL gira per default in AUTOCOMMIT mode: tutti gli
aggiornamenti vengono automaticamente consolidati nel
momento in cui sono eseguiti
• Per iniziare una transazione si deve usare allora START
TRANSACTION: da questo punto in poi tutti gli aggiornamenti
rimaranno sospesi
Sintassi
START TRANSACTION
...istruzioni di aggiornamento (1)...
SAVEPOINT sp1;
...istruzioni di aggiornamento (2)...
ROLLBACK TO SAVEPOINT sp1;
...istruzioni di aggiornamento (3)...
COMMIT
•
•
•
•
COMMIT conferma le modifiche e chiude la transazione, mentre
ROLLBACK annulla tutti gli aggiornamenti eseguiti nel corso dell’ultima
transazione
COMMIT AND CHAIN provoca l’immediata apertura di una nuova
transazione, COMMIT RELEASE chiude la connessione al server
SET AUTOCOMMIT=0 disabilita l’autocommit: tutti gli aggiornamenti
rimangono in sospeso fino al commit
I SAVEPOINT sono degli stati intermedi ai quali possiamo tornare con un
ROLLBACK
Osservazioni
• Su MySQL il tutto funziona solo se si usa InnoDB
• NON sono annullabili le operazioni che creano,
eliminano o alterano la struttura di tabelle e
database: è bene evitare di includere in una
transazione tali operazioni, che tra l’altro nella
maggior parte dei casi causano una COMMIT
implicita
SELECT
• In alcuni casi è utile utilizzare due clausole particolari quando si effettua
una select:
SELECT ....FOR UPDATE
SELECT ....LOCK IN SHARE MODE
• La prima stabilisce un LOCK su tutte le righe lette, che impedirà ad altri
utenti di leggerle fino al termine della nostra transazione
• La seconda stabilisce un LOCK che impedisce gli aggiornamenti,
garantendo che il contenuto rimarrà invariato durante la transazione
Livello di isolamento
•
Un aspetto importante relativamente alle transazioni è il livello di isolamento al
quale vengono effettuate. I livelli possibili sono quattro, e li elenchiamo in ordine
crescente:
1. READ UNCOMMITTED: a questo livello sono visibili gli aggiornamenti effettuati da altri
utenti anche se non consolidati: è un comportamento non propriamente transazionale,
che può dare seri problemi di consistenza dei dati; va utilizzato solo quando non ci sono
preoccupazioni di questo tipo e c’è bisogno di velocizzare le letture
2. READ COMMITTED: a questo livello gli aggiornamenti diventano visibili solo dopo il
consolidamento
3. REPETEABLE READ: in questo caso perchè un aggiornamento diventi visibile deve essere
non solo consolidato, ma anche la transazione che legge deve essere terminata; in
pratica, la stessa lettura ripetuta all'interno di una transazione darà sempre lo stesso
risultato; è la modalità di default
4. SERIALIZABLE: come nel caso precedente, ma in più, la semplice lettura di un dato
provoca il blocco degli aggiornamenti fino al termine della transazione; in sostanza è
come se ogni SELECT venisse effettuata con la clausola LOCK IN SHARE MODE
Scheduling
• E’ possibile definire due tipologie di eventi:
– ad una sola esecuzione
– ricorrente.
Nel primo caso si specifica iltimestamp di
esecuzione, mentre nel secondo caso viene
specificato l’intervallo di ripetizione, sotto forma
di un numero di minuti, ore, giorni, fino ad anni.
procediamo con un semplice esempio
di schedulazione,
atto ad illustrate la sintassi da utilizzare.
Scheduling
• Si ha una tabella che servirà da contenitore:
– CREATE TABLE `regeventi` (
`ID_RIGA` int(11) NOT NULL AUTO_INCREMENT,
`data` datetime DEFAULT NULL,
`conteggio` int(11) DEFAULT NULL,
PRIMARY KEY (`ID_RIGA`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
– La tabella oltre alla chiave primaria ha un campo data che
memorizzerà il momento in cui scatta l’evento, e un campo
numerico che fungerà da contatore delle sue repliche.
Scheduling
• La seguente procedura verrà richiamata periodicamente
dall’evento:
•
CREATE PROCEDURE `memScatto`() NOT DETERMINISTIC
SQL SECURITY DEFINER COMMENT ''
BEGIN
DECLARE iConteggio INT;
SET iConteggio = 0;
SELECT IFNULL(max(conteggio)+1, 1) INTO iConteggio from regeventi;
INSERT INTO `regeventi` (`data`, `conteggio`) VALUES (now(), iConteggio);
END;
Scheduling
• Si creerà un evento di tipologia ricorrente, con intervallo di un
minuto, che si occuperà di richiamare la
procedura memScatto:
•
CREATE EVENT `contaMinuti` ON SCHEDULE EVERY 1 MINUTE
ON COMPLETION PRESERVE
ENABLE
COMMENT 'lancia ogni minuto la procedura memScatto'
DO call memScatto;
• Con questi comandi DDL è stato preparato il sistema, ma
occorre attivare il processo di scheduling.
Scheduling
• Questo si ottiene tramite una variabile globale event_scheduler. Si
possono controllare i valori di tutte le variabili settate
utilizzando mysqladmin nel seguente modo:
mysqladmin -uroot -ppassword variables
Se event_scheduler è a OFF, la schedulazione degli eventi è
disattivata e si può utilizzare il seguente comando per l’attivazione:
SET GLOBAL event_scheduler = ON;
Una volta in esecuzione il processo che sovrintende agli eventi, ogni minuto
scatterà contaMinuti, che richiamerà la procedura memScatto e questa
inserirà un record nella tabella regeventi. La query SELECT * FROM `regeventi`
; mostrerà i record inseriti con data, che differisce esattamente di un minuto
uno dall’altro.
CREATE EVENT Syntax
•
CREATE [DEFINER = { user | CURRENT_USER }]
EVENT [IF NOT EXISTS] event_name
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE | DISABLE ON SLAVE]
[COMMENT 'comment']
DO event_body;
schedule: AT timestamp [+ INTERVAL interval] ... | EVERY interval [STARTS
timestamp [+ INTERVAL interval] ...]
[ENDS timestamp [+ INTERVAL interval] ...]
interval: quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | WEEK |
SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE | DAY_SECOND |
HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}
Esempio
• CREATE EVENT myevent
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
DO
UPDATE myschema.mytable SET mycol = mycol + 1;
Viene creato un evento di nome myevent che esegue, una volta dopo
un’ora dalla sua creazione, l’sql specificato dopo il DO.
Altro esempio
• delimiter |
• CREATE EVENT e_daily
ON SCHEDULE EVERY 1 DAY
COMMENT 'Saves total number of sessions then clears the table
each day'
DO
BEGIN
INSERT INTO site_activity.totals (time, total)
SELECT CURRENT_TIMESTAMP, COUNT(*)
FROM
site_activity.sessions;
DELETE FROM site_activity.sessions;
END |
delimiter ;
security
CREATE EVENT requires the EVENT privilege
for the schema in which the event is to be
created.
It might also require the SUPER privilege,
depending on the DEFINER value.
Cancellare un evento
SHOW CREATE EVENT <event_name>\G
DROP EVENT [IF EXISTS] event_name