Query\update Query plan Execution Engine richieste di indici, record e file Index/file/record Manager comandi su pagine Lettura/scrittura pagine Architettura di un DBMS Utente/Applicazione Query Compiler Buffer Manager Storage Manager metadati, statistiche metadati, indici, statistiche transazioni Transaction Manager Logging/ Recovery Buffers pagine di log Amministratore Comandi DDL Concurrency control Lock Table DDL Compiler metadati dischi 2
Introduzione Notazione: tre alternative per le etichette k - (1) record di dati con valore della chiave di ricerca k - (2) <k, rid del record di dati con valore della chiave di ricerca k> - (3) <k, lista delle rid dei record con valore delle chiavi di ricerca k> Scelta indipendente dalla tecnica di indicizzazione Gli indici ad albero supportano sia equality search sia range search Indexed Sequential Access Method (ISAM): struttura ad albero statica Albero B+: struttura ad albero dinamica 3 Costruire un indice Problema: calcolare velocemente la risposta alla domanda: Quali sono le persone con età maggiore di 20? Possibile soluzione: - Se i dati sono ordinati, fare una ricerca binaria per trovare la prima persona che soddisfa la condizione e poi continuare con uno scan Il costo della ricerca binaria può essere alto Idea: costruire un indice e fare su di esso una ricerca binaria 4
Indexed Sequential Access Method (ISAM) L indice può diventare grande, ma si può applicare l idea ripetutamente Vengono creati nodi che puntano ad altri nodi Lo scan non è più necessario Le pagine terminali contengono le etichette per recuperare i record di dati Una volta creato l albero, non modifica più la sua struttura: vengono soltanto aggiunte pagine di overflow 5 ISAM: commenti Creazione dell indice: le pagine terminali vengono allocate sequenzialmente, poi le pagine dell indice, ed infine le pagine di overflow Etichette: <valore della chiave di ricerca, pageid> dirigono la ricerca dei dati nell indice Ricerca: partire dalla radice, confrontare le chiavi per arrivare alle pagine terminali. Costo: (F= # figli per nodo, N= # pagine terminali) log F N È minore del costo di una ricerca binaria ( log 2 N ), se F>2 Inserimento: trovare la pagina terminale a cui l etichetta appartiene ed aggiornare la pagina Cancellazione: trovare la pagina terminale e cancellare l etichetta. Se si svuota una pagina di overflow, liberala Nota bene: l inserimento e la cancellazione non cambiano la struttura dell albero 6
ISAM: esempio Ogni nodo contiene due etichette. Nessun bisogno di puntatori alla successiva pagina terminale perché l albero viene creato sulla lista ordinata dei record e non viene più cambiato Vediamo possibili interrogazioni... Le etichette sono denotate con un asterisco 7 Inserimento Inserimento di 23*, 48*, 41*, 42* 8
Cancellazione Cancellazione di 42*, 51*, 67* 9 Albero B+: l indice più usato Un albero B+ di ordine m soddisfa le seguenti proprietà: Ogni nodo ha al più m figli Ogni nodo (eccetto la radice) ha almeno m/2 figli La radice ha almeno 2 figli I nodi terminali sono sullo stesso livello e sono linkati sequenzialmente Un nodo nonterminale con j+1 figli contiene j chiavi Albero B+ di ordine 7. I nodi terminali sono al livello 3. Ogni nodo nonterminale contiene 2, 3, 4, 5 o 6 chiavi. Le etichette sono rappresentate con l alternativa 2 (pag. 3) 10
Formato di un nodo nonterminale Un nodo nonterminale che contiene j chiavi e j+1 puntatori ai figli è rappresentato come: p P_0 P_1 P_2 P_(j-1) P_j K_1 K_2 K_j Dove K_1< <K_j e p_i punta al sottoalbero contenente le chiavi con valori tra K_i e K_(i+1) 11 Come implementare un nodo? All interno di un nodo nonterminale, ogni coppia chiave-puntatore può essere vista come un record: K_i p_i Un nodo è un insieme di record Un albero B+ è quindi un file di record Due alternative: - (1) Le etichette sono le pagine di dati: dati e indice contenuti nello stesso file (notaz. 1 pag. 3) - (2) Le etichette puntano alle pagine di dati: dati e indice contenuti in file diversi (notaz. 2 e 3 pag. 3) 12
Albero B+: esempio Possibili interrogazioni: cercare 5*, 15*, oppure tutte le pagine terminali con chiave maggiore di 24*... 13 Ricerca in un Albero B+ Partendo dalla radice, cercare la chiave desiderata tra le chiavi K_1, K_2,, K_j (se j è grande, si può usare un ricerca binaria) Se la chiave desiderata ha valore tra K_i e K_(i+1), allora la ricerca continua nel sottoalbero puntato da p_i p_0 e p_j puntano ai sottoalberi contenenti chiavi minori di K_1 e maggiori di K_j, rispettivamente 14
Algoritmo per la ricerca Func tree_search(nodepointer, K) if *nodepointer == termnode nodepoiner if K < K_1 tree_search(p_0,k) if K > K_j tree_search(p_j,k) find i such that K_i <= K < K_(i+1); tree_search(p_i, K) *nodepointer denota il valore puntato dalla variabile puntatore nodepointer Ricerca nel nodo implementabile con uno scan sequenziale o con una ricerca binaria 15 Complessità della ricerca Quanti nodi devono essere acceduti nella ricerca di una chiave in un albero B+ di ordine m? Ossia, come dipende il numero di livelli dal numero di nodi? Ipotesi : ci sono N nodi terminali all ultimo livello l Quindi, ai livelli 1,2,3, ci sono almeno 2, 2(m/2), 2(m/2)^2, nodi Quindi N>=2(m/2)^(l-1), da cui abbiamo: l 1+ log N 2 m ( 2) 16
Alberi B+ in pratica Numero tipico di chiavi in un nodo: 150 Capacità tipiche di un albero B+: - altezza 3: 150^3 = 3.375.000 record - altezza 4: 150^4 = 506.250.000 record Importante: Le pagine non terminali sono nel buffer pool - livello 1: 1 pagina (8Kb) - livello 2: 150 pagine (~1Mb) - livello 3: 22.500 pagine (~175Mb) 17 Albero B+: Inserimento e Cancellazione L idea sottostante ad un inserimento e ad una cancellazione di un etichetta è semplice: - Trovare il nodo terminale corrispondente nell albero B+ attraversandolo ricorsivamente - Inserire/Cancellare l etichetta L albero risultante può non soddisfare tutte le proprietà che caratterizzano gli alberi B+ Vengono compiute alcune azioni che ripristinano tutte queste proprietà 18
Albero B+: inserimento Trovare il nodo terminale corretto Inserire le etichette nel nodo Se il nodo ha abbastanza spazio l inserimento è concluso Altrimenti si deve dividere il nodo (creando un nuovo nodo) Ribilanciare equamente le etichette, copiare al livello superiore la chiave mediana (si crea una copia della chiave perché le etichette devono stare nei nodi terminali, tutti allo stesso livello) Questo può essere fatto ricorsivamente Per dividere un nodo nonterminale, ridistribuire equamente le chiavi, spingere (non c è necessità di copiare la chiave) al livello superiore la chiave mediana Le divisioni fanno crescere l ampiezza dell albero, la divisione della radice fa crescere l altezza dell albero 19 Proc insert(nodepointer, entry, newchildentry) if *nodepointer == nontermnode find i such thatk_i <= entry key< K_(i+1); insert(p_i,entry, newchildentry); if newchildentry == null Algoritmo per l inserimento if *nodepointer has space put *newchildentry on it; newchildentry= null; middlekey=split2(*nodepointer); newchildentry= &([middlekey, pointer tonew node]); if *nodepointer == root newchildentry punta alla coppia [chiave, puntatore] il cui puntatore punta alla pagina creata dallo split create new node with (pointer to*nodepointer, *newchildentry) ; make the root nodepoint to the new node if *nodepointer has space put entry on it; newchildentry= null split (*nodepointer); newchildentry = &([smallest key value on new node created by split, pointer tonew node]); set sibling pointers in *nodepointer and new node create in split Inserimento dell etichetta entry nel sottoalbero puntato da nodepointer *nodepointer è un nonnodo terminale Scelta del sottoalbero Ricorsivamente, inserimento dell etichetta Il punto dove avviene lo split del nodo Dipende dall ordine dell albero *nodepointer è un nodo terminale 20
Inserire 8* 8* dovrebbe stare nel nodo terminale più a sinistra ma questo nodo è pieno Tale pagina viene spezzata, 5 è copiato, 8 è inserito. 5 deve essere inserito nel nodo radice, ma è pieno, quindi tale nodo viene spezzato Notare che 5 viene duplicato mentre 17 non viene duplicato 21 L albero dopo l inserimento di 8* La radice è stata divisa, l albero è cresciuto in altezza Si possono evitare le divisioni con un ribilanciamento delle etichette 22
Ribilanciamento di un albero B+ Distribuire le etichette o le chiavi (nel caso di nodi nonterminali) tra nodi contigui con lo stesso genitore per evitare la divisione Attenzione: Il controllo dello spazio nei nodi adiacenti richiede la loro lettura ed è infrequente che un ribilanciamento eviti un aumento dell altezza dell albero, quindi la ridistribuzione non viene effettuata molto spesso 23 Albero B+: cancellazione Trovare il nodo terminale corretto Cancellare l etichetta Se il nodo è pieno almeno per metà la cancellazione è conclusa Altrimenti provare a ridistribuire, prendendo etichette da un nodo fratello adiacente (es. destro). Viene modificata una chiave nel nodo padre copiando il valore più piccolo tra le chiavi delle etichette ancora presenti nel nodo fratello (se viene usato il fratello sinistro, la chiave copiata è il valore più grande) Se il ribilanciamento fallisce a causa del fatto che il nodo fratello si è svuotato troppo, unire il nodo da cui si è cancellato e il nodo fratello adiacente Ad unione avvenuta, cancellare la chiave che punta al nodo (o al fratello) dal nodo padre Le unioni si possono propagare fino alla radice, diminuendo l altezza dell albero 24
Algoritmo per la cancellazione Proc delete(parentpointer, nodepointer, entry, oldchildentry) if *nodepointer == nontermnode findi such thatk_i <= entrykey< K_(i+1); delete(nodepointer, p_i, entry, oldchildentry); if oldchildentry== null remove *oldchildentry from *nodepointer; if *nodepointer has enough entries oldchildentry= null; read a sibling of *nodepointer; if sibling has enough entries redistribute evenly(through *parentpointer) sibling and *nodepointer; oldchildentry == null; merge *nodepointer and sibling; oldchildentry= &(current entry in *parentpointer for merged node); pull splitting key from *parentpointer down into node on left; move all entries from merged nodes to node on left; discard empty merged nodes Quando due nodi sono fusi, nel nodo padre deve essere cancellato il puntatore (e la chiave) al secondo nodo. oldchildentry punta a questo puntatore Trova ricorsivamente il nodo terminale che contiene l etichetta da cancellare Ridistribuzione e fusione di nodi nonterminali 25 Algoritmo per la cancellazione (continua) if *nodepointer has enough entries Ridistribuzione e fusione remove entry; di nodi teminali oldchildentry== null; get a sibling of *nodepointer; if the sibling has enough entries redistribute beetween sibling and *nodepointer; find entry in *parentpointer for node on right; replace key value in *parentpointer entry by new low-key in node on right; oldchildentry== null; merge *nodepointer and sibling; oldchildentry= &(current entry in *parentpointer for the merged node); move all entries from the merged nodetonode on left; discard empty merged node; adjust sibling pointers; 26
Dopo l inserimento di 8* e la cancellazione di 19* e 20* Cancellare 19* è facile (fig. pag. 22) Cancellare 20* svuota troppo il nodo terminale: se un nodo terminale fratello ha abbastanza etichette, queste possono venire ribilanciate. Notare come l etichetta 27* è stata copiata in alto 27 e dopo la cancellazione di 24* Non si può fare la ridistribuzione delle etichette perché i nodi fratelli non ne contengono abbastanza I due nodi terminali contenenti uno l etichetta 22* e l altro le etichette 27* e 29* vengono uniti. Si può cancellare la chiave 27 nel nodo al livello superiore. Il sottoalbero corrispondente diventa: Il nodo padre dei due nodi terminali contiene solo la chiave 30. Il nodo superiore contiene solo la chiave 17 ed il nodo fratello le chiavi 5 e 13. Vengono uniti e l albero si abbassa di un livello 28
Bulk Loading di un albero B+ Se abbiamo un grande insieme di record, e vogliamo creare un albero B+ su qualche campo, l inserzione di un record alla volta è molto lenta Il Bulk Loading può essere fatto molto più velocemente Inizializzazione: - Ordinare tutte le etichette - Inserire un puntatore in una nuova pagina (radice) alla prima pagina (terminale) Per il seguente esempio assumiamo che ogni pagina possa contenere solo due chiavi (e tre puntatori), ossia che l ordine dell albero sia 1 29 Bulk Loading (continua) Le etichette per le pagine terminali vengono inserite sempre nella pagina dell indice più a destra sopra le pagine terminali Quando questa pagina è piena, viene splittata. Gli split si possono propagare fino alla radice Molto più veloce che inserzioni ripetute: ordinare preventivamente le etichette e poi inserirle nell albero è meglio che inserirle in modo non ordinato 30
Chiavi di Ricerca Duplicate Finora abbiamo ipotizzato che nell albero non siano contenute chiavi duplicate Ipotesi : presenza di più etichette con la stessa chiave - Ricerca: Trovare l etichetta più a sinistra che ha la chiave voluta. Recuperare le altre etichette seguendo i puntatori sui nodi terminali. - Cancellazione: Problema: l algoritmo visto è inefficiente perché deve essere ripetuto per ogni etichetta con la stessa chiave Soluzione: il rid diventa parte della chiave di ricerca. In questo modo non ci sono più chiavi duplicate 31 Sommario Indici ad albero ideali per range query (vanno bene anche per equality query) ISAM statico, B+ tree dinamico Equality query richiede l attraversamento dell albero dalla radice ad un nodo terminale L inserimento può riempire un nodo oltre il limite consentito e richiede il suo split La cancellazione può fare scendere l occupazione sotto la soglia minima. Le etichette del nodo vengono ridistribuite nei nodi figli. L altezza dell albero può diminuire 32