INDICI, IMPLEMENTAZIONE DI OPERATORI ALGEBRICI E QUERY PROCESSING 1 PREMESSA... 2 2 OCCUPAZIONE DI MEMORIA DI UNA RELAZIONE... 2 3 ORGANIZZAZIONI SERIALE E SEQUENZIALE... 2 3.1 Organizzazione seriale (heap)... 2 3.2 Organizzazione sequenziale... 3 4 INDICI... 3 4.1 Indice calcolato - Funzione Hash (statico)... 3 4.2 Indice Tabellato B+-tree... 3 5 IMPLEMENTAZIONE OPERATORI RELAZIONALI CON GLI INDICI... 5 5.1 Selezione con B+Tree Primario... 6 5.2 Selezione con B+Tree Secondario... 6 5.3 Selezioni composte.... 7 5.4 Join... 7 5.4.1 Nested loop... 7 5.4.2 Index Join... 7 5.4.3 Sort-merge... 8 5.4.4 Hash Join (Classic)... 8 6 PIANI DI ESECUZIONE... 9 1
1 Premessa Retrieval time from external memory (e.g. disk) is thousands of times greater than high-speed memory. The goal of physical database optimization is to minimize the number of disk accesses, since disk access takes longer than computations. When a record on external memory is to be accessed, an entire page (or block) of data is transferred at once in main memory. By fetching a disk page that contains many records, we take advantage of the locality of reference -- if a record is being retrieved and we have organized the records based on how they are accessed, then it is likely that the records that are in the fetched block will be retrieved next. 2 Occupazione di memoria di una relazione Data una relazione R formata da Ntuple tuple di lunghezza Ltuple e memorizzata su pagine di lunghezza Lpag, l occupazione di memoria di R è Num tuple per pagina: #TuPag = Lpag/ Ltuple Occupazione memoria: Npag = Ntuple / #TuPag Esempio: Si consideri una relazione R con Ntuple=300.000, ognuna di lunghezza Ltuple=100 byte, memorizzate su pagine di lunghezza Lpag=1024 byte. Le tuple sono indivisibili. Pertanto, il numero di tuple per pagina è 10, ed il numero di pagine necessario per memorizzare R è pari a Npag=300.000/10 = 30.000. Nella seguente tabella sono riportati i simboli utilizzati in questo documento. Simbolo Npag Lpag Ltuple Ntuple #TuPag significato numero di pagine lunghezza di una pagina lunghezza di una tuple Numero di tuple Numero di tuple per pagina 3 Organizzazioni seriale e sequenziale 3.1 Organizzazione seriale (heap) Un heap è un file non ordinato Costi: Costo medio ricerca per chiave σ A=a (R): C= Npag/2, oppure C= Npag, se la chiave non è presente Costo ricerca per intervallo: σ k1 k k2 (R) = Npag 2
3.2 Organizzazione sequenziale In tal caso il file è ordinato. Assumiamo che K sia l attributo di ordinamento. Costi: Costo medio ricerca sequenziale per chiave σ K=k (R): C = Npag/2 (sempre) Costo massimo ricerca binaria per chiave σ K=k (R): C = log(npag) (caso peggiore) 1 Costo ricerca per intervallo σ k1 K k2 (R): C = log(npag) + sel(k1 K k2 )* Npag - 1 dove sel(k1 K k2 ) è la frazione di tuple che soddisfano la condizione di selezione; in particolare, se K è numerico e si assume una distribuzione uniforme, allora sel(k1 K k2 )= (k1-k2)/(kmax-kmin) Esempio: Una ricerca binaria sulla relazione di cui all esempio precedente costa log2(30.000) = 15 accessi a pagine (nel caso peggiore). Siano Kmax=10.000, Kmin=1000, K1=300, K2=500. Il costo della ricerca per intervallo è 15+0,02*30.000= 615 pagine 4 INDICI Un indice è una struttura dati (ausiliaria) che velocizza le ricerche per chiave (di ricerca). Esso può essere calcolato (funzioni hash) o tabellato. In quest ultimo caso, esso è un insieme di coppie (k,p), dove k è un valore della chiave e p è il puntatore alla pagina che contiene il record (o i record) con chiave k. Un indice può essere: primario: l attributo di indicizzazione è chiave (o superchiave) secondario: l attributo di indicizzazione non contiene una chiave (quindi, in generale, ammette duplicati) 4.1 Indice calcolato - Funzione Hash (statico) È una tecnica di ricerca e/o memorizzazione. La funzione tipica è la seguente h(k) = k mod n dove k: codifica numerica della chiave n: numero di pagine del file Sinonimi: chiavi assegnate alla stessa pagina gestione dell overflow 4.2 Indice Tabellato B+- tree Un B+-tree di ordine m 3 è un albero ad m vie definito induttivamente come segue: Base: un nodo N con 1 k m-1 chiavi e k puntatori è un B+-tree ad un livello (h=1); le chiavi sono ordinate in ordine crescente; i puntatori puntano alle pagine della relazione indicizzata (si noti che N è un nodo foglia, oltre che radice) Induzione: siano dati 1) un nodo ordinato N = (p 0, k 1, p 1,, k n, p n ), con 1 n < m, e 2) n+1 B+-tree B 0,, B n di profondità h-1, tali che o tutte le chiavi C(Bi) in Bi sono tali che k i < C(Bi) k i+1 (0 < i < n), C(B 0 ) k 1 e C(B n ) > k n o ogni chiave in N appare in una foglia di un sottoalbero 1 Deve essere supportato l accesso diretto alle pagine; ciò richiede che siano memorizzate sequenzialmente 3
o ogni nodo non foglia in Bi (0 i n) ha un numero di puntatori m/2 p m o ogni nodo foglia in Bi (0 i n) ha un numero di puntatori m/2 m-1 allora l albero che ha radice N e sottoalberi B 0,, B n è un B+tree di profondità h Proprietà di un B+-tree di ordine m: 1. è bilanciato (le foglie sono tutte allo stesso livello) 2. i valori all interno di ogni nodo sono ordinati 3. La radice o è una foglia o ha almeno 2 figli 4. ogni nodo non foglia, tranne la radice, ha un numero di figli m/2 f m 5. se un nodo non foglia ha j figli, ha j-1 valori della chiave 6. ogni foglia ha un numero di coppie (valore, puntatore al record) m/2 m-1, un puntatore alla foglia successiva ed uno a quella precedente; 7. Ogni chiave appare in una foglia 8. Ogni nodo è memorizzato in una pagina NOTA: la struttura dell albero dipende dall ordine di caricamento delle chiavi. L ordine dell albero dipende dalla dimensione delle pagine (in genere tra 1 e16 K), dalle lunghezze delle chiavi e dei puntatori. ESEMPIO. Si supponga che l attributo di indicizzazione sia lungo 9 byte e che il puntatore alle pagine sia lungo 6 byte. Se indichiamo con m l ordine dell albero, ogni nodo interno può contenere al massimo (m-1) chiavi e m puntatori. Pertanto, nell ipotesi che una pagina sia lunga 2K byte, il valore di m si ottiene come segue: (m-1)*9 + m*6 2048 15 m 2057 m= 137. Un B+-tree può essere un indice di ordinamento (clustered) o no (unclustered): o ordinamento (clustered): la relazione è ordinata sull attributo di indicizzazione; ogni chiave in un nodo foglia punta alla pagina che contiene la prima tupla con un dato valore dell attributo o non di ordinamento (unclustered): la relazione non è ordinata sull attributo di indicizzazione. Siccome in generale esistono valori duplicati (più tuple con lo stesso valore della chiave), ogni puntatore delle foglie punta ad un blocco di indici che contiene i puntatori a tutte le tuple con un dato valore della chiave. L altezza (o profondità) di un B+-tree è pari al numero di nodi che si incontrano in un qualsiasi percorso dalla radice ad una foglia. Altezza Minima di un B+-tree. Tutti i nodi contengono m-1 chiavi. Siccome ogni nodo non foglia ha m figli, allora al livello h ci sono 2 Nodi(h) = m h-1 Punt(h)= m* m h-1 = m h (nodi non foglia) 2 Il livello 1 è quello della radice 4
Pertanto, se il B+-tree ha profondità h, allora il numero di chiavi nelle foglie è K max (h) = (m-1)* m h-1 = m h m h-1 m h (quando m è grande) Se la relazione da indicizzare contiene K valori della chiave di indicizzazione, allora sarà necessario un albero di profondità (minima) h, dove h è il più piccolo valore tale che m h K h min (m,k) = log m K Altezza Massima di un B+-tree. Tutti i nodi contengono m/2-1 chiavi (e m/2 puntatori), tranne la radice che contiene una chiave e 2 puntatori. Pertanto, Nodi(h=1) = 1 Punt(h=1) = 2 Nodi(h=2) = 2 Punt(h=2)= 2 m/2 (nodi non foglia) Nodi(h>2) = 2 m/2 h-2 Ne deriva che, se l albero ha profondità h, allora il numero di chiavi nelle foglie è K min (h) = 2 m/2 h-2 * ( m/2-1) = 2 m/2 h-1-2 m/2 h-2 2 m/2 h-1 Se la relazione da indicizzare contiene K valori della chiave di indicizzazione, allora sarà necessario un albero di profondità (massima) h, dove h è il più piccolo valore tale che 2 m/2 h-1 K h max (m,k) = log m/2 K/2 + 1 In conclusione, per indicizzare K chiavi attraverso un albero di ordine m, la profondità h(m,k) dell albero sarà tale che h min (m,k) h(m,k) h max (m,k) ESEMPIO. Assumendo m=137 e K = 1.000.000 h min (m,k) = log m K = log 137 1.000.000 = 3 h max (m,k) = log m/2 K/2 + 1 = log 69 500.000 + 1 = 5 5 Implementazione Operatori Relazionali con gli indici Gli operatori dell algebra relazionale possono essere implementati in modi diversi, a seconda del tipo di organizzazione fisica delle relazioni coinvolte. 5
5.1 Selezione con B+Tree Primario Ipotesi: Sulla chiave primaria della relazione è definito B+-tree di profondità h. Ricerca per chiave: seleziona tutte le tuple con valore k=a della chiave. Il costo C della ricerca è C = h+1 Ricerca per intervallo: seleziona tutte le tuple con valore k della chiave tale che v1 k v2. Sia sel(v1 k v2) il fattore di selettività della condizione di selezione (frazione di tuple che soddisfano la condizione): 1) Indice di ordinamento: C = h + sel(v1 k v2)*npag 2) Indice non di ordinamento: C = h + sel(v1 k v2)*nfoglie 1 +sel(v1 k v2)*ntuple Nel primo caso, la ricerca attraverso l indice è conveniente rispetto alla ricerca binaria se h + sel((v1 k v2))*npag < log 2 Npag+ sel(v1 k v2)*npag/2 Nel secondo caso, la ricerca attraverso l indice è conveniente rispetto alla scansione sequenziale se h + sel(v1 k v2)*nfoglie 1 + sel(v1 k v2)*ntuple < Npag ESEMPIO. La ricerca σ K=k in una relazione 1.000.000 tuple di una chiave primaria, attraverso un albero di ordine m=137, costa 4 accessi nel caso migliore, e 6 nel peggiore. Se si facesse una ricerca binaria, nell ipotesi di Ltuple=100 e Lpag=1024, il costo sarebbe pari a 17 pagine (caso peggiore). NOTA. Se il B+-tree è un indice primario, le foglie possono memorizzare le tuple intere (e non solo la chiave). 5.2 Selezione con B+Tree Secondario Ipotesi: un B+-tree di profondità h è definito su un attributo non chiave primaria della relazione. Ricerca per chiave: seleziona le tuple con valore k=a della chiave. Sia sel(k=a) la selettività della condizione k=a: Indice di ordinamento: C = h + sel(k=a)*npag Indice non di ordinamento: C = h + 1 + sel(k=a)*ntuple Ricerca per intervallo: seleziona tutte le tuple con valore k della chiave tale che v1 k v2. Sia sel(v1 k v2) il fattore di selettività della condizione di selezione: 1) Indice di ordinamento: C = h + sel(v1 k v2)*npag 2) Indice non di ordinamento: C = h + sel(v1 k v2)*nfoglie 1 + sel(v1 k v2)*ntuple*(1+sel(k=a)*ntuple) 6
dove sel(v1 k v2)*nfoglie 1 è il costo di scansione delle foglie, e sel(v1 k v2)*ntuple*(1+sel(k=a)*ntuple) è il costo di accesso ad ogni singola tupla che soddisfa il predicato di selezione (v1 k v2). 5.3 Selezioni composte. Esempio 1: σ (A=a and B=b) (R). Si supponga che su A sia definito un B+tree clustered di profondità h. Una possibile strategia è la seguente: 1) si usa l indice su A per selezionare tutte le tuple tali che A=a; 2) per ogni tupla selezionata, si verifica se anche la condizione B=b è soddisfatta C = C A = h + sel(a=a)*npag Ovviamente, l approccio è vantaggioso rispetto ad una scansione sequenziale se h + sel(a=a)*npag < Npag(R). Esempio 2: σ (A=a or B=b) (R). Si supponga che su A sia definito un B+tree. In tal caso, l indice non è utile, ed il costo è quello della scansione sequenziale. 5.4 Join Ricordiamo che l operatore di join R S è commutativo. 5.4.1 Nested loop Algoritmo NestedLoop (R è relazione esterna) for each page r of R transfer r in main memory for each page s of S transfer s in main memory join the tuples in r with those in s Costo = NPag(R)+NPag(R)*NPag(S) con R relazione esterna Costo = NPag(S)+NPag(R)*NPag(S) con S relazione esterna Confrontando le due equazioni, si evince facilmente che, nel caso del nested loop, conviene usare come relazione esterna quella più piccola (cioè, memorizzata su un numero minore di pagine). 5.4.2 Index Join Assumiamo che la condizione di join sia R.A = S.B e che su B sia definito un qualsiasi indice (hash, B+-tree, ecc.). In tal cso, R funge da relazione esterna. Quindi, per ogni tupla di R, si accede alle tuple di S che hanno quel valore sfruttando l indice. 7
Algoritmo Index join for each page r of R transfer r in main memory for each tuple t in r let t.a be the value of the join attribute X = index_lookup(s,s.b=t.a) //X is the set of pages containing tuples of S s.t. S.B=t.a for each page p in X for each tuple t in p oin t and t A titolo d esempio, supponiamo che l indice sia un B+-tree. I costi del join sono: a. B+-tree primario sull attributo di giunzione B di S (cioè, B è chiave primaria di S): C= NPag(R) + Ntuple(R) * (h+1) b. B+-tree secondario di ordinamento sull attributo di giunzione B di S: C= NPag (R) + Ntuple(R) * (h+ sel(b=r.a) * Npag(S) ) c. B+-tree secondario NON di ordinamento sull attributo di giunzione B di S: C= NPag (R) + Ntuple(R) * (h+1 + sel(b=r.a) * Ntuple(S) ) 5.4.3 Sort- merge R e S sono ordinate rispetto agli attributi di giunzione A e B (la condizione di join è R.A = S.B) Algoritmo SortMerge i <- 1; j <- 1; While (i <= R ) AND (j <= S ) do if R[i].C = S[j].C then outputtuples else if R[i].C > S[j].C then j <- j+1 else if R[i].C < S[j].C then i <- i+1 Procedure outputtuples While (R[i].C = S[j].C) AND (i <= R ) do k <- j; While (R[i].C = S[k].C) AND (k <= S ) do output R[i], S[k] pair; k <- k + 1; i <- i + 1; Il costo è C= NPag (R)+ NPag (S) 5.4.4 Hash Join (Classico) Viene creata una hash table in memoria centrale in cui è memorizzata la relazione più piccola (assumiamo che sia R) usando l attributo di join A di R. Algoritmo HashJoin 1. For each tuple r in the R 1. Add r to the in-memory hash table at the address h(r.a) 2. If the size of the hash table equals the maximum in-memory size: 8
Scan the S and add matching join tuples to the output relation Reset the hash table 2. Do a final scan of S and add the resulting join tuples to the output relation Nel caso migliore, la relazione R è interamente contenuta in memoria centrale. In tal caso, quindi, il costo del join è C = Npag(R) + Npag(S) (caso migliore) ESEMPIO. La relazione R ha 1000 tuple memorizzate su 100 pagine. La relazione S è memorizzata su 10.000 pagine ed ha una funzione hash sulla chiave primaria Ks. Il costo del join tra R e S, a cui S partecipa con Ks, nei vari casi è il seguente: 1) Nested loop con R ext: C1 = Npag(R) + Npag(R)*Npag(S) = 1.000.100 pag 2) Nested loop con S ext: C2 = Npag(S) + Npag(R)*Npag(S) = 1.010.000 pag 3) Index join: C3 = Npag(R) + Ntuple(R) = 100 + 1000 = 1100 pag (usa la funzione hash) 4) Hash join: C4 = Npag(R) + Npag(S) (caso migliore) = 10.100 pag NOTA: l index join (che sfrutta l indice hash) è circa 1000 volte più veloce del nested loop. ESEMPIO. La relazione IMP ha un indice secondario B+-tree sull attributo Dip. La relazione DIP ha un indice primario hash sull attributo Codice. IMP ha 100.000 tuple memorizzate su 20.000 pagine. DIP ha 125 tuple memorizzate su 13 pagine. Il B+-tree ha 3 livelli. Ogni DIP ha in media 100.000/125=800 IMP. IMP non è ordinato su Dip. Quindi il B+-tree non è di ordinamento ed ha un ulteriore livello che contiene i blocchi di puntatori Costi del join: 1) nested loop con IMP ext à C = 2000+(13*20.000) = 262.000 pagine 2) nested loop con DIP ext, à C = 13+(13*20.000) = 260.013 pagine 3) index join: uso hash su DIP con IMP esterno à C = Npag(Imp)+Ntuple(imp) = 20.000 + 100.000 = 120.000 pagine 4) Uso B+tree su IMP con DIP esterno à C = Npag(Dip)+Ntuple(Dip)*(h+1+800) = 13 + 125(3+1 + 800) = 100.513 pag 6 PIANI DI ESECUZIONE Segue un esempio di esecuzione di query con calcolo del relativo costo. Esempio. Si consideri la seguente interrogazione Q in Algebra Relazionale: π codcalc (σ città=milano (HA-GIOCATO SQUADRA)) -- π codcalc (σ città=roma HA-GIOCATO SQUADRA) definita sulla seguente base di dati: CALCIATORE(codCalc, nome, cognome, età) SQUADRA(codSquadra, nome, città) HA-GIOCATO(codCalc, codsquadra) Scegliere un piano di esecuzione di Q e calcolarne il costo. A tal fine, si assumano i seguenti dati: 9
a. Ogni città ha un unica squadra b. CALCIATORE: 10.000 tuple c. HA-GIOCATO: 100.000 tuple; le chiavi codcalc e codsquadra sono entrambe lunghe 10 byte d. SQUADRA: 500 tuple, ognuna lunga 50 byte e. Ogni pagina è lunga 1024 byte e contiene un numero intero di tuple. Pertanto #tupag(h) = 1024/20 = 51 NPag(H) = 100.000/51 = 1961 pages #tupag(s) = 1024/50 = 20 bytes NPag(S)= 500/20 = 25 pages 1) Ottimizzazione logica: anticipazione della selezione rispetto al join π codcalc (HA-GIOCATO σ città=milano SQUADRA) -- π codcalc (HA-GIOCATO σ città=roma SQUADRA) _ π π j j H σ H σ S S Albero di esecuzione 2) Valutazione bottom up del piano di esecuzione a) selezione: (σ città=roma (S): in assenza di indici su città, si esegue una scansione sequenziale C1 = costo(σ città=roma (S)) = 25 pagine (scansione sequenziale) size(s =σ città=roma (S)) = 1 tupla (ipotesi a) b) join: S H: assumendo che ci sia un B-+tree clustered a 3 livelli su codsquadra di H, si valutano due alternative: nested loop e join con indice b.1) nested loop S inner C2 = costo(s H) = 1+1*2000= 2001 pages b.2) Join con indice 10
C2 = costo(s H) = h + fs*npag(h) = 3 + 1/500*2000 = 3 + 4 = 7 pages Viene quindi eseguito il secondo join, in quanto più efficiente. La relazione risultante H ha la seguente dimensione (si noti che le tuple di H sono di 60 bytes): size(h = S H) = 100.000/500 = 200 tuples * 60/1024 = 12 pages c) proiezione: π codcalc H C3= costo(π codcalc H ) = 12 pages Siccome la lunghezza delle tuple di H è pari a 10 byte size (H ) = 200 tuple * 10/1024 = 2 pages Considerato che la query è simmetrica (rispetto all operatore di sottrazione), l esecuzione dell altro sottoalbero ha lo stesso costo e produce una relazione H delle stesse dimensioni di H. Pertanto, il costo della differenza H e H è il seguente d) differenza: H H C4= costo(h -H ) = Npag(H ) + Npag(H )*Npag(H ) = 2 + 2*2= 6 pages Infine, il costo totale di esecuzione è la somma dei singoli costi parziali costototale = C4 + 2*( C1+C2+C3) = 94 pages 11