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.