In genere, per qualsiasi database vale la regola per cui maggiore è la quantità di dati immagazzinati in un database, peggiori saranno (inevitabilmente) le sue prestazioni durante la lettura. Al fine di velocizzare le query, esistono delle particolari strutture dati,
dette indici, il cui scopo è quello di permettere un più agevole recupero dei record sotto determinate condizioni. In questa lezione, scopriremo la loro implementazione in OrientDB familiarizzando con i comandi SQL che ne permettono l'uso.
Tipi di indici
OrientDB prevede diversi tipi di indici:
- SB-Tree, derivante dagli indici ad albero binario, è il tipo di default e rappresenta una buona soluzione nella maggior parte dei casi;
- Hash è strutturato come HashMap, struttura dati di tipo chiave/valore. A fronte di un certo rallentamento delle operazioni di scrittura può agevolare notevolmente quelle di lettura purchè di natura puntuale, quindi basate sulla ricerca di uno specifico valore sfruttando una chiave. Non può essere impiegato in range query, quelle mirate cioè all'estrazione di un intervallo di valori;
- Lucene è focalizzato sulle ricerche full-text e dati geospaziali.
La scelta dell'indice viene quindi effettuata in base al tipo di ricerche che si vogliono effettuare. Una volta assegnato, un tipo non può essere più cambiato.
Un'altra classificazione degli indici è basata sulla modalità di aggiornamento che può essere:
- automatica: l'indice è collegato già ad una proprietà di una classe. Anche se si vuole usare la classe in modalità schema-less, almeno la proprietà cui l'indice è agganciato deve essere creata;
- manuale: il sistema non si occupa dell'aggiornamento dell'indice, che diventa cura del programmatore. Tipicamente, ciò viene fatto tramite API di programmazione.
Creazione e cancellazione di un indice
Un indice può essere creato con il comando CREATE INDEX
, che richiede almeno due parametri obbligatori: il nome dell'indice ed il tipo. Per quanto riguarda il nome, esso può essere la concatenazione di un nome di classe e di una proprietà. Ad esempio, se come nome dell'indice viene specificato Persona.cognome, allora sarà definito un indice automatico sulla proprietà cognome della classe Persona.
Per quanto riguarda il tipo di indice, si potrà scegliere tra le seguenti opzioni:
- per gli indici SB-Tree:
UNIQUE
,NOTUNIQUE
,FULLTEXT
eDICTIONARY
, tutti in grado di supportare range query; - per gli indici basati su algoritmo hash si avranno:
UNIQUE_HASH_INDEX
,NOTUNIQUE_HASH_INDEX
,FULLTEXT_HASH_INDEX
eDICTIONARY_HASH_INDEX
.
Qualora il nome scelto per l'indice non fosse sufficiente ad indicare il campo cui è collegato, sarà necessario utilizzare la clausola ON
che permetterà di introdurre nome della classe e della proprietà destinazione dell'indice. Ecco di seguito alcuni esempi:
// indice automatico, a valori unici, su proprietà cognome della classe Persona
CREATE INDEX Persona.cognome UNIQUE
// altro indice su Persona.cognome
CREATE INDEX indiceSuCognome ON Persona (cognome) UNIQUE
Opzionalmente, dopo il tipo dell'indice, si può indicare il tipo di dato della chiave. Nel caso di indici automatici, ciò non sarà necessario in quanto il tipo della proprietà cui l'indice è collegato sarà considerato il tipo della chiave dell'indice. Negli altri casi, il tipo del primo valore che verrà usato in concomitanza dell'indice diverrà il tipo prescelto per la chiave.
Esiste anche la possibilità di creare indici su campi multipli:
CREATE INDEX indiceCampiMultipli ON Studenti (nome, cognome, matricola) UNIQUE
Per la cancellazione di un indice, si usa il comando DROP INDEX
:
// cancellazione di un indice
DROP INDEX indiceCampiMultipli
È possibile anche conoscere tutti gli indici definiti nel database cui siamo connessi tramite due comandi:
LIST INDEXES
o la sua forma abbrevviata:
INDEXES
Utilizzo e manipolazione degli indici
Un indice, come visto, consiste essenzialmente in una struttura dati ove una chiave permette la rapida estrazione dell'ID di un record. Si può visualizzare il contenuto di un indice usando il prefisso INDEX
nell'ambito di un comando SELECT
:
SELECT FROM INDEX:Citta.nome
Il risultato mostrerà una tabella in cui le colonne key e rid costituiranno l'accoppiata chiave-valore. In questo caso, l'output mostrerà il contenuto di un indice definito sul campo nome di una classe Citta. I valori cui le chiavi puntano sono i RID assegnati ai record contenenti le informazioni sulla città in questione:
# |@CLASS|key |rid
----+------+------+------
1 |null |Bari |#18:10
2 |null |Genova|#18:8
3 |null |Milano|#18:6
4 |null |Napoli|#18:7
5 |null |Roma |#18:5
----+------+------+------
Come presumibile, si potrà applicare un filtro tramite clausola WHERE
che permetterà di recuperare un valore riferito ad una sola riga dell'indice:
SELECT FROM INDEX:Citta.nome WHERE key='Milano'
La struttura di un indice può anche essere modificata manualmente tramite inserimenti e cancellazioni. Eccone alcuni esempi:
// inserimento di una nuova entry in un indice avente per chiave la matricola degli studenti
INSERT INTO INDEX:indiceStudenti (key,rid) VALUES ('S12345',#12:45)
// cancellazione di una entry di indice in base alla chiave
DELETE FROM INDEX:indiceStudenti WHERE key = 'S123456'
I comandi appena mostrati sono di carattere esemplificativo ma ovviamente si possono utilizzare le comuni clausole previste da SQL. Ad esempio, il WHERE
collegato al DELETE
può essere corredato di condizioni più complesse includendo altri parametri, o può essere del tutto rimosso comportando così la cancellazione dell'intero indice.
EXPLAIN: valutare le query
Per poter valutare le prestazioni di una query, soprattutto nel caso in cui questa si sia dimostrata piuttosto lenta, possiamo sfruttare le potenzialità dell'istruzione EXPLAIN
. Vediamo un esempio:
EXPLAIN SELECT FROM Citta WHERE nome='Milano'
Il risultato non mostrerà i record recuperati, bensì una serie di informazioni relative alle prestazioni dell'interrogazione. Tra i parametri riportati, troveremo ad esempio:
- elapsed: il tempo impiegato per l'esecuzione della query;
- resultType e resultSize: il primo indica il tipo di risultato e può valere collection, document o number; il secondo indica il numero di elementi ma solo nel caso in cui il resultType sia collection;
- recordReads, il numero di record letti da disco, e documentReads, il numero di documenti letti da disco: i due non sempre coincidono;
Il comando EXPLAIN
restituisce anche altre informazioni, ma in questo contesto ci interessa sottolineare la presenza di due attributi dedicati agli indici: involvedIndexes
, che specifica quale indice è stato coinvolto nell'interrogazione, e indexReads
, il numero di record letti tramite indice. Grazie a questi elementi, EXPLAIN
si dimostra essenziale per evidenziare le query più lente che possono deprimere le prestazioni dei nostri software e quale contributo gli indici definiti hanno apportato alla risoluzione del problema.