Oracle
 sql >> Database >  >> RDS >> Oracle

Indice di tempo costante per la colonna di stringhe nel database Oracle

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.