Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Konstantzeitindex für die Zeichenfolgenspalte in der Oracle-Datenbank

Hash-Cluster können O(1)-Zugriffszeit bereitstellen, aber nicht O(1)-Einschränkungsdurchsetzungszeit. In der Praxis ist die konstante Zugriffszeit eines Hash-Clusters jedoch schlechter als die O(log N)-Zugriffszeit eines regulären B-Tree-Index. Außerdem sind Cluster schwieriger zu konfigurieren und lassen sich für einige Vorgänge nicht gut skalieren.

Hash-Cluster erstellen

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;
/

Stammtabelle erstellen (zum Vergleich)

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;
/

Trace-Beispiel

SQL*Plus Autotrace ist eine schnelle Möglichkeit, den Explain-Plan zu finden und die E/A-Aktivität pro Anweisung zu verfolgen. Die Anzahl der E/A-Anforderungen wird als "konsistente Abrufe" bezeichnet und ist eine anständige Methode, um die Menge der geleisteten Arbeit zu messen. Dieser Code zeigt, wie die Nummern für andere Abschnitte generiert wurden. Die Abfragen müssen oft mehr als einmal ausgeführt werden, um sich aufzuwärmen.

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>

Finde optimale Hashkeys, Kompromisse

Für eine optimale Leseleistung sollten alle Hash-Kollisionen in einen Block passen (alle Oracle-E/A werden pro Block ausgeführt, normalerweise 8 KB). Den idealen Speicher richtig zu finden, ist schwierig und erfordert die Kenntnis des Hash-Algorithmus, der Speichergröße (nicht identisch mit der Blockgröße) und der Anzahl der Hash-Schlüssel (der Buckets). Oracle hat einen Standardalgorithmus und eine Standardgröße, sodass es möglich ist, sich auf nur ein Attribut zu konzentrieren, die Anzahl der Hash-Schlüssel.

Mehr Hash-Schlüssel führen zu weniger Kollisionen. Dies ist gut für die TABLE ACCESS HASH-Leistung, da nur ein Block gelesen werden muss. Unten ist die Anzahl konsistenter Gets für verschiedene Hashkey-Größen aufgeführt. Zum Vergleich ist auch ein Indexzugriff enthalten. Bei genügend Hashkeys verringert sich die Anzahl der Blöcke auf die optimale Anzahl, 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

Mehr Hash-Schlüssel führen auch zu mehr Buckets, mehr Platzverschwendung und einer langsameren Operation TABLE ACCESS FULL.

Table type      Space in MB
HeapTable       24MB
Hashkeys 100    26MB
hashkeys 1000   30MB
hashkeys 10000  81MB

Um meine Ergebnisse zu reproduzieren, verwenden Sie eine Beispielabfrage wie select * from orders_cluster where merchantid = 100001 and transactionid = '1'; und ändern Sie den letzten Wert auf 1, 20, 300, 4000 und 50000.

Leistungsvergleich

Konsistente Gets sind vorhersehbar und leicht zu messen, aber am Ende des Tages zählt nur die Uhrzeit an der Wanduhr. Überraschenderweise ist der Indexzugriff mit viermal konsistenteren Aufrufen immer noch schneller als das optimale Hash-Cluster-Szenario.

--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;
/

Ich habe auch den Test mit variablen Prädikaten ausprobiert, aber die Ergebnisse waren ähnlich.

Skaliert es?

Nein, Hash-Cluster skalieren nicht. Trotz der O(1)-Zeitkomplexität von TABLE ACCESS HASH und der O(log n)-Zeitkomplexität von INDEX UNIQUE SCAN scheinen Hash-Cluster B-Tree-Indizes nie zu übertreffen.

Ich habe den obigen Beispielcode mit 10 Millionen Zeilen ausprobiert. Das Laden des Hash-Clusters war quälend langsam und blieb bei der SELECT-Leistung immer noch hinter dem Index zurück. Ich habe versucht, es auf 100 Millionen Zeilen hochzuskalieren, aber die Einfügung würde 11 Tage dauern.

Die gute Nachricht ist, dass b*trees gut skalieren. Das Hinzufügen von 100 Millionen Zeilen zum obigen Beispiel erfordert nur 3 Ebenen im Index. Ich habe mir alle DBA_INDEXES angeschaut für eine große Datenbankumgebung (Hunderte von Datenbanken und ein Petabyte an Daten) - der schlechteste Index hatte nur 7 Ebenen. Und das war ein pathologischer Index auf VARCHAR2(4000) Säulen. In den meisten Fällen bleiben Ihre B-Tree-Indizes unabhängig von der Tabellengröße flach.

In diesem Fall schlägt O(log n) O(1).

Aber WARUM?

Die schlechte Leistung von Hash-Clustern ist möglicherweise ein Opfer von Oracles Versuch, die Dinge zu vereinfachen und die Art von Details zu verbergen, die erforderlich sind, damit ein Hash-Cluster gut funktioniert. Cluster sind schwierig einzurichten und richtig zu verwenden und würden ohnehin selten einen signifikanten Nutzen bringen. Oracle hat sich in den letzten Jahrzehnten nicht sehr darum bemüht.

Die Kommentatoren haben Recht, dass ein einfacher B-Tree-Index am besten ist. Aber es ist nicht offensichtlich, warum das wahr sein sollte, und es ist gut, über die in der Datenbank verwendeten Algorithmen nachzudenken.