I cluster hash possono fornire il tempo di accesso O(1), ma non il tempo di imposizione del vincolo O(1). Tuttavia, in pratica il tempo di accesso costante di un cluster hash è peggiore del tempo di accesso O(log N) di un normale indice b-tree. Inoltre, i cluster sono più difficili da configurare e non si adattano bene per alcune operazioni.
Crea cluster hash
drop table orders_cluster;
drop cluster cluster1;
create cluster cluster1
(
MerchantID number,
TransactionID varchar2(20)
)
single table hashkeys 10000; --This number is important, choose wisely!
create table orders_cluster
(
id number,
MerchantID number,
TransactionID varchar2(20)
) cluster cluster1(merchantid, transactionid);
--Add 1 million rows. 20 seconds.
begin
for i in 1 .. 10 loop
insert into orders_cluster
select rownum + i * 100000, mod(level, 100)+ i * 100000, level
from dual connect by level <= 100000;
commit;
end loop;
end;
/
create unique index orders_cluster_idx on orders_cluster(merchantid, transactionid);
begin
dbms_stats.gather_table_stats(user, 'ORDERS_CLUSTER');
end;
/
Crea una tabella normale (per il confronto)
drop table orders_table;
create table orders_table
(
id number,
MerchantID number,
TransactionID varchar2(20)
) nologging;
--Add 1 million rows. 2 seconds.
begin
for i in 1 .. 10 loop
insert into orders_table
select rownum + i * 100000, mod(level, 100)+ i * 100000, level
from dual connect by level <= 100000;
commit;
end loop;
end;
/
create unique index orders_table_idx on orders_table(merchantid, transactionid);
begin
dbms_stats.gather_table_stats(user, 'ORDERS_TABLE');
end;
/
Esempio di traccia
SQL*Plus Autotrace è un modo rapido per trovare il piano di spiegazione e tenere traccia dell'attività di I/O per istruzione. Il numero di richieste di I/O è etichettato come "ottenimento coerente" ed è un modo decente per misurare la quantità di lavoro svolto. Questo codice mostra come sono stati generati i numeri per altre sezioni. Le query spesso devono essere eseguite più di una volta per riscaldare le cose.
SQL> set autotrace on;
SQL> select * from orders_cluster where merchantid = 100001 and transactionid = '2';
no rows selected
Execution Plan
----------------------------------------------------------
Plan hash value: 621801084
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 1 (0)| 00:00:01 |
|* 1 | TABLE ACCESS HASH| ORDERS_CLUSTER | 1 | 16 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("MERCHANTID"=100001 AND "TRANSACTIONID"='2')
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
31 consistent gets
0 physical reads
0 redo size
485 bytes sent via SQL*Net to client
540 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed
SQL>
Trova hashkey e compromessi ottimali
Per prestazioni di lettura ottimali, tutte le collisioni hash dovrebbero rientrare in un blocco (tutto l'I/O Oracle viene eseguito per blocco, in genere 8K). Ottenere lo spazio di archiviazione ideale è complicato e richiede la conoscenza dell'algoritmo hash, della dimensione dello spazio di archiviazione (non uguale alla dimensione del blocco) e del numero di chiavi hash (i bucket). Oracle ha un algoritmo e una dimensione predefiniti, quindi è possibile concentrarsi su un solo attributo, il numero di chiavi hash.
Più chiavi hash portano a meno collisioni. Questo è utile per le prestazioni HASH TABLE ACCESS poiché c'è solo un blocco da leggere. Di seguito è riportato il numero di get coerenti per diverse dimensioni di hashkey. Per il confronto è incluso anche un accesso all'indice. Con un numero sufficiente di hashkey il numero di blocchi diminuisce al numero ottimale, 1.
Method Consistent Gets (for transactionid = 1, 20, 300, 4000, and 50000)
Index 4, 3, 3, 3, 3
Hashkeys 100 1, 31, 31, 31, 31
Hashkeys 1000 1, 3, 4, 4, 4
Hashkeys 10000 1, 1, 1, 1, 1
Più chiavi hash portano anche a più bucket, più spazio sprecato e un'operazione TABLE ACCESS FULL più lenta.
Table type Space in MB
HeapTable 24MB
Hashkeys 100 26MB
hashkeys 1000 30MB
hashkeys 10000 81MB
Per riprodurre i miei risultati, utilizza una query di esempio come select * from orders_cluster where merchantid = 100001 and transactionid = '1';
e cambia l'ultimo valore in 1, 20, 300, 4000 e 50000.
Confronto delle prestazioni
I risultati coerenti sono prevedibili e facili da misurare, ma alla fine della giornata conta solo l'ora dell'orologio da parete. Sorprendentemente, l'accesso all'indice con 4 volte più risultati coerenti è ancora più veloce dello scenario di cluster hash ottimale.
--3.5 seconds for b-tree access.
declare
v_count number;
begin
for i in 1 .. 100000 loop
select count(*)
into v_count
from orders_table
where merchantid = 100000 and transactionid = '1';
end loop;
end;
/
--3.8 seconds for hash cluster access.
declare
v_count number;
begin
for i in 1 .. 100000 loop
select count(*)
into v_count
from orders_cluster
where merchantid = 100000 and transactionid = '1';
end loop;
end;
/
Ho anche provato il test con predicati variabili ma i risultati sono stati simili.
Ridimensiona?
No, i cluster hash non sono scalabili. Nonostante la complessità temporale O(1) di TABLE ACCESS HASH e la complessità temporale O(log n) di INDEX UNIQUE SCAN, i cluster di hash non sembrano mai superare gli indici b-tree.
Ho provato il codice di esempio sopra con 10 milioni di righe. Il cluster hash è stato dolorosamente lento da caricare e ha comunque sottoperformato l'indice sulle prestazioni SELECT. Ho provato a ridimensionarlo fino a 100 milioni di righe, ma l'inserimento avrebbe richiesto 11 giorni.
La buona notizia è che i b*trees si adattano bene. L'aggiunta di 100 milioni di righe all'esempio precedente richiede solo 3 livelli nell'indice. Ho guardato tutti i DBA_INDEXES
per un ambiente di database di grandi dimensioni (centinaia di database e un petabyte di dati) - l'indice peggiore aveva solo 7 livelli. E quello era un indice patologico su VARCHAR2(4000)
colonne. Nella maggior parte dei casi i tuoi indici b-tree rimarranno superficiali indipendentemente dalle dimensioni della tabella.
In questo caso, O(log n) batte O(1).
Ma PERCHE'?
Le scarse prestazioni del cluster di hash sono forse una vittima del tentativo di Oracle di semplificare le cose e nascondere il tipo di dettagli necessari per far funzionare bene un cluster di hash. I cluster sono difficili da configurare e utilizzare correttamente e raramente fornirebbero comunque un vantaggio significativo. Oracle non si è impegnato molto negli ultimi decenni.
I commentatori hanno ragione sul fatto che un semplice indice b-tree è il migliore. Ma non è ovvio perché dovrebbe essere vero ed è bene pensare agli algoritmi utilizzati nel database.