Os clusters de hash podem fornecer o tempo de acesso O(1), mas não o tempo de aplicação da restrição O(1). No entanto, na prática, o tempo de acesso constante de um cluster de hash é pior do que o tempo de acesso O(log N) de um índice regular de b-tree. Além disso, os clusters são mais difíceis de configurar e não são bem dimensionados para algumas operações.
Criar cluster de 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;
/
Criar tabela normal (para comparação)
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;
/
Exemplo de rastreamento
O SQL*Plus Autotrace é uma maneira rápida de encontrar o plano de explicação e rastrear a atividade de E/S por instrução. O número de solicitações de E/S é rotulado como "obtidas consistentes" e é uma maneira decente de medir a quantidade de trabalho realizado. Este código demonstra como os números foram gerados para outras seções. As consultas geralmente precisam ser executadas mais de uma vez para aquecer as coisas.
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>
Encontre Hashkeys e Compensações ideais
Para um desempenho de leitura ideal, todas as colisões de hash devem caber em um bloco (toda a E/S do Oracle é feita por bloco, geralmente 8K). Acertar o armazenamento ideal é complicado e requer conhecer o algoritmo de hash, o tamanho do armazenamento (não o mesmo que o tamanho do bloco) e o número de chaves de hash (os buckets). O Oracle possui um algoritmo e tamanho padrão, portanto, é possível focar em apenas um atributo, o número de chaves de hash.
Mais chaves de hash levam a menos colisões. Isso é bom para o desempenho do TABLE ACCESS HASH, pois há apenas um bloco para ler. Abaixo está o número de obtenções consistentes para diferentes tamanhos de hashkey. Para comparação, um acesso de índice também está incluído. Com hashkeys suficientes, o número de blocos diminui para o número ideal, 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
Mais chaves de hash também levam a mais buckets, mais espaço desperdiçado e uma operação TABLE ACCESS FULL mais lenta.
Table type Space in MB
HeapTable 24MB
Hashkeys 100 26MB
hashkeys 1000 30MB
hashkeys 10000 81MB
Para reproduzir meus resultados, use uma consulta de amostra como
select * from orders_cluster where merchantid = 100001 and transactionid = '1';
e altere o último valor para 1, 20, 300, 4000 e 50000. Comparação de desempenho
Obturações consistentes são previsíveis e fáceis de medir, mas no final do dia apenas a hora do relógio de parede importa. Surpreendentemente, o acesso ao índice com 4 vezes mais obtenções consistentes ainda é mais rápido do que o cenário de cluster de hash ideal.
--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;
/
Eu também tentei o teste com predicados variáveis, mas os resultados foram semelhantes.
É dimensionado?
Não, os clusters de hash não são dimensionados. Apesar da complexidade de tempo O(1) do TABLE ACCESS HASH e da complexidade de tempo O(log n) do INDEX UNIQUE SCAN, os clusters de hash nunca parecem superar os índices b-tree.
Eu tentei o código de exemplo acima com 10 milhões de linhas. O cluster de hash foi dolorosamente lento para carregar e ainda apresentou desempenho inferior ao índice no desempenho do SELECT. Tentei escalá-lo para 100 milhões de linhas, mas a inserção levaria 11 dias.
A boa notícia é que as árvores b* escalam bem. Adicionar 100 milhões de linhas ao exemplo acima requer apenas 3 níveis no índice. Eu olhei para todos os
DBA_INDEXES
para um ambiente de banco de dados grande (centenas de bancos de dados e um petabyte de dados) - o pior índice tinha apenas 7 níveis. E isso era um índice patológico em VARCHAR2(4000)
colunas. Na maioria dos casos, seus índices b-tree permanecerão superficiais, independentemente do tamanho da tabela. Neste caso, O(log n) supera O(1).
Mas POR QUÊ?
O desempenho ruim do cluster de hash talvez seja uma vítima da tentativa da Oracle de simplificar as coisas e ocultar o tipo de detalhes necessários para fazer um cluster de hash funcionar bem. Os clusters são difíceis de configurar e usar corretamente e raramente forneceriam um benefício significativo de qualquer maneira. A Oracle não colocou muito esforço neles nas últimas décadas.
Os comentaristas estão corretos que um índice simples de árvore b é o melhor. Mas não é óbvio por que isso deveria ser verdade e é bom pensar nos algoritmos usados no banco de dados.