Hashclusters kunnen O(1)-toegangstijd bieden, maar niet O(1)-beperkingstijd. In de praktijk is de constante toegangstijd van een hashcluster echter slechter dan de O(log N) toegangstijd van een reguliere b-tree-index. Clusters zijn ook moeilijker te configureren en schalen niet goed voor sommige bewerkingen.
Hashcluster maken
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;
/
Maak een normale tabel (ter vergelijking)
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;
/
Traceervoorbeeld
SQL*Plus Autotrace is een snelle manier om het plan uit te leggen en I/O-activiteit per instructie bij te houden. Het aantal I/O-verzoeken wordt aangeduid als "consistente krijgt" en is een goede manier om de hoeveelheid werk te meten. Deze code laat zien hoe de nummers zijn gegenereerd voor andere secties. De zoekopdrachten moeten vaak meer dan één keer worden uitgevoerd om de boel op te warmen.
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>
Vind optimale hashkeys, compromissen
Voor optimale leesprestaties moeten alle hash-botsingen in één blok passen (alle Oracle I/O wordt per blok gedaan, meestal 8K). Het verkrijgen van de ideale opslag is lastig en vereist kennis van het hash-algoritme, de opslaggrootte (niet hetzelfde als de blokgrootte) en het aantal hash-sleutels (de buckets). Oracle heeft een standaardalgoritme en -grootte, dus het is mogelijk om te focussen op slechts één attribuut, het aantal hash-sleutels.
Meer hash-sleutels leiden tot minder botsingen. Dit is goed voor de HASH-prestaties van TABLE ACCESS, omdat er maar één blok hoeft te worden gelezen. Hieronder vindt u het aantal consistente geten voor verschillende hashkey-formaten. Ter vergelijking is ook een indextoegang opgenomen. Met voldoende hashkeys neemt het aantal blokken af tot het optimale aantal, 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
Meer hash-sleutels leiden ook tot meer buckets, meer verspilde ruimte en een langzamere TABLE ACCESS FULL-bewerking.
Table type Space in MB
HeapTable 24MB
Hashkeys 100 26MB
hashkeys 1000 30MB
hashkeys 10000 81MB
Om mijn resultaten te reproduceren, gebruik je een voorbeeldquery zoals select * from orders_cluster where merchantid = 100001 and transactionid = '1';
en verander de laatste waarde in 1, 20, 300, 4000 en 50000.
Prestatievergelijking
Consistente resultaten zijn voorspelbaar en gemakkelijk te meten, maar uiteindelijk is alleen de tijd van de wandklok van belang. Verrassend genoeg is de indextoegang met 4 keer meer consistente resultaten nog steeds sneller dan het optimale hashclusterscenario.
--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;
/
Ik heb de test ook geprobeerd met variabele predikaten, maar de resultaten waren vergelijkbaar.
Schaalt het?
Nee, hashclusters schalen niet. Ondanks de O(1) tijdcomplexiteit van TABLE ACCESS HASH en de O(log n) tijdcomplexiteit van INDEX UNIQUE SCAN, lijken hashclusters nooit beter te presteren dan b-tree-indexen.
Ik heb de bovenstaande voorbeeldcode geprobeerd met 10 miljoen rijen. Het hashcluster was pijnlijk traag om te laden en presteerde nog steeds onder de index op SELECT-prestaties. Ik heb geprobeerd het op te schalen naar 100 miljoen rijen, maar het invoegen zou 11 dagen duren.
Het goede nieuws is dat b*trees goed schalen. Het toevoegen van 100 miljoen rijen aan het bovenstaande voorbeeld vereist slechts 3 niveaus in de index. Ik heb alle DBA_INDEXES
. bekeken voor een grote database-omgeving (honderden databases en een petabyte aan gegevens) - de slechtste index had slechts 7 niveaus. En dat was een pathologische index op VARCHAR2(4000)
kolommen. In de meeste gevallen blijven uw b-tree-indexen ondiep, ongeacht de tafelgrootte.
In dit geval verslaat O(log n) O(1).
Maar WAAROM?
Slechte hashclusterprestaties zijn misschien het slachtoffer van Oracle's poging om dingen te vereenvoudigen en het soort details te verbergen dat nodig is om een hashcluster goed te laten werken. Clusters zijn moeilijk in te stellen en op de juiste manier te gebruiken en zouden sowieso zelden een significant voordeel opleveren. Oracle heeft er de afgelopen decennia niet veel moeite voor gedaan.
De commentatoren hebben gelijk dat een eenvoudige b-tree-index het beste is. Maar het is niet duidelijk waarom dat waar zou moeten zijn en het is goed om na te denken over de algoritmen die in de database worden gebruikt.