Data

26 nov, 2008

Entendendo o HWM – High Water Mark

Publicidade

Olá, amigos. Um dos principais conceitos sobre arquitetura física do Oracle é a marca Dágua. Uma tradução de HWMHigh Water Mark, ele que indica o limite que uma tabela já ocupou de espaço físico para um determinado datafile no seu banco de dados. Mas vamos um pouco mais a fundo.

O que é uma Marca Dágua (HWMHigh Water Mark)?

A marca dágua é o limite do número de blocos que uma tabela pode estar utilizando; resumindo para um conceito mais simples, toda vez que uma tabela recebe um INSERT (novos registros), essa marca na tabela aumenta dizendo ao Oracle Server a quantidade de blocos que a tabela está utilizando, automaticamente, a quantidade de blocos, multiplicado, pelo tamanho do db_block_size do banco de dados, diz o valor físico real que está sendo utilizado.

Mas esse valor real não é o valor que o Oracle irá alocar, pois irá depender de alguns outros pontos, como:

  • Se a tabela está sendo gerenciada por si própria ou pela tablespace.
  • Irá depender dos tamanhos dos extents, exemplo, INITIAL_EXTENT e NEXT_EXTENT.
  • Também irá depender do tipo de gerenciamento, se é SEGMENT MANAGEMENT AUTO ou UNIFORM.
  • E a quantidade de blocos que um EXTENT pode suportar.

Os conceitos a seguir são muito aplicados para DMTDictionary Management Tablespace, para LMTLocal Management Tablespace, também poderá realizar os testes, mas os conceitos para esse tipo de gerenciamento de tablespace é um pouco mais detalhada.

Vamos ver como funciona a marca dágua na prática em alguns exemplos práticos.

Vou criar uma tabela simples, chamada TSTDBA.

SQL> create table TESTE (a varchar2(100) not null, b number(7) not null);
Tabela criada.

Agora, vamos analisar como está a estrutura para o Oracle, pois a tabela não possui nenhum valor e nenhuma estatística coletada.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;
OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA

Até o momento, tudo sem surpresas para nós.

Então, vamos popular essa tabela com alguns registros, veja o exemplo.

SQL> l
  1  declare
  2     contador integer;
  3  begin
  4     contador := 1;
  5     while contador <= 1000 loop
  6             insert into TSTDBA values (TESTE,contador);
  7             contador := contador  1;
  8     end loop;
  9     commit;
 10* end;
SQL> /

Procedimento PL/SQL concluído com sucesso.

SQL> exec dbms_stats.gather_table_stats (ownname=>RODRIGO,tabname=>TSTDBA,estimate_percent=>null,method_opt=>FOR ALL COLUMNS SIZE AUTO,degree=>6);

Procedimento PL/SQL concluído com sucesso.

Verifiquem que fiz um pequeno bloco PL/SQL para inserir dados em minha tabela, cerca de 1.000 registros. Após isso, preciso dizer ao Oracle, o status da tabela, seu volume e outras coisas mais, então, fiz um analyze na tabela para atualizar as informações estruturais e lógicas dela no dicionário Oracle, ao coletar as estatísticas usando o pacote DBMS_STATS, o resultado do SELECT acima, agora é esse.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;
OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA                                  5            0       1000 06-10-2008 19:42:08

Veja, a nossa tabela está utilizando 5 blocos, o db_block_size do meu banco de dados é de 8KB, então, resumidamente, ele deveria estar utilizando cerca de 40KB, certo?

SQL> select 8192*5 from dual;
    8192*5
----------
     40960

O resultado para o tamanho da tabela TSTDBA é 64KB, porque o INITIAL_EXTENT da tabela é de 64KB, e como os 1.000 registros ocuparam apenas 40KB, um único extent consegui suportar.


SQL> select initial_extent/1024, next_extent from dba_tables where table_name = TSTDBA;
INITIAL_EXTENT/1024 NEXT_EXTENT
------------------- -----------
                           64

Pois bem! Rodrigo, e o tal do HWM, o que têm haver com isso?

Vamos começar a brincar agora, veja que após o analyze, minha tabela TSTDBA está utilizando 5 blocos de dados, certo? Teoricamente, se eu fizer um TRUNCATE TABLE, eu não vou mais utilizar nenhum bloco, e minha marca dágua deveria baixar, mas acontece isso:

SQL> truncate table TSTDBA;

Tabela truncada.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;

OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA                                  5            0       1000 06-10-2008 19:44:18

A minha tabela continua com se estivesse com 5 blocos, o que pode nos prejudicar:

Esse exemplo é bem simples, mas para tabelas com milhares de registros, poderá influenciar os FULL-TABLES SCANS. Ao realizar um INSERT convencional, ou seja, sem o hint APPEND, ele irá procurar por blocos livres e irá consumir CPU e demorar um tempo para sua execução.

Se minha marca dágua estiver muito alta, ou seja, estiver armazenando um alto valor de blocos utilizados, e você sabe que ele não está utilizando tudo isso, você terá uma alocação de EXTENTS desnecessários no banco de dados, e isso irá ocupar espaço desnecessários.

Caso eu quisesse diminuir o tamanho do meu segmento de tabela, eu não iria conseguir, pois a marca dágua é inferior aos meus 64KB. Pois bem, tente realizar um insert agora de 2.000.000 de registros e vamos ver o que acontece.

 SQL> declare
  2     contador integer;
  3  begin
  4     contador := 1;
  5     while contador <= 2000000 loop
  6             insert into TSTDBA values (TESTE,contador);
  7             contador := contador  1;
  8     end loop;
  9     commit;
 10  end;
 11  /

Procedimento PL/SQL concluído com sucesso.

SQL> exec dbms_stats.gather_table_stats (ownname=>RODRIGO,tabname=>TSTDBA,estimate_percent=>null,method_opt=>FOR ALL COLUMNS SIZE AUTO,degree=>6);

Procedimento PL/SQL concluído com sucesso.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;

OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA                               4654            0    2000000 06-10-2008 22:37:24

SQL> show parameters db_block_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_block_size                        integer     8192

SQL> select (8192*4654)/1024/1024 as "Tamanho" from dual;

   Tamanho
----------
 36,359375

SQL> select segment_name, sum(bytes)/1024/1024 from dba_segments where segment_name = TSTDBA group by segment_name;

SEGMENT_NAME                                                                      SUM(BYTES)/1024/1024
--------------------------------------------------------------------------------- --------------------
TSTDBA                                                                                              37

Se quiser analisar melhor como ficou a distribuição, veja a dba_extents. Abaixo vou mostrar apenas um pequeno resumo da quantidade de extents alocados e seus respectivo tamanho.

SQL> select segment_name, count(extent_id), sum(bytes)/1024/1024
  2  from dba_extents
  3  where segment_name = TSTDBA
  4  group by segment_name;

SEGMENT_NAME         COUNT(EXTENT_ID) SUM(BYTES)/1024/1024
-------------------- ---------------- --------------------
TSTDBA                             52                   37

Bom, vimos que agora temos um valor legal de extents alocados, e mesmo após o TRUNCATE continuo com uma alocação de extents, que totaliza os 37MB da tabela, então, minha marca dágua está posicionada no 51 extent, que seria o limite do numeros de blocos alcançados.

Conseguimos entender como funciona a marca dágua, o que isso pode nos causar?

Inicialmente, podemos imaginar que é uma fragmentação, por possuir alguns blocos vazios, além da marca dágua elevar o número de extents no dicionário, prejudicando muitas vezes os planos de execução e os table full scans, vamos ter também perda de espaço físico para a tablespace, espaço que não puderam ser alocados por outro segmento. Vamos a uma demostração prática de como funciona a fragmentação.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;

OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA                               4654            0    2000000 06-10-2008 22:37:24

SQL> select segment_name, count(extent_id), sum(bytes)/1024/1024
  2  from dba_extents
  3  where segment_name = TSTDBA
  4  group by segment_name;

SEGMENT_NAME         COUNT(EXTENT_ID) SUM(BYTES)/1024/1024
-------------------- ---------------- --------------------
TSTDBA                             52                   37

A minha tabela TSTDBA continua com seus 2.000.000 de registros, após o analyze acima, vimos que está a atual estrutura da tabela, e se realizarmos diversos DELETES em grandes quantidades, o que poderemos ter?

SQL> delete from TSTDBA where b between 10000 and 20000;

10001 linhas deletadas.

SQL> delete from TSTDBA where b between 50000 and 200000;

150001 linhas deletadas.

SQL> delete from TSTDBA where b between 400000 and 700000;

300001 linhas deletadas.

SQL> delete from TSTDBA where b between 1000000 and 1300000;

300001 linhas deletadas.

SQL> commit;

Commit concluído.

SQL> select segment_name, count(extent_id), sum(bytes)/1024/1024
  2  from dba_extents
  3  where segment_name = TSTDBA
  4  group by segment_name;

SEGMENT_NAME         COUNT(EXTENT_ID) SUM(BYTES)/1024/1024
-------------------- ---------------- --------------------
TSTDBA                             52                   37

Vamos passar um analyze para validar toda a estrutura da tabela.

SQL> exec dbms_stats.gather_table_stats (ownname=>RODRIGO,tabname=>TSTDBA,estimate_percent=>null,method_opt=>FOR ALL COLUMNS SIZE AUTO,degree=>6);

Procedimento PL/SQL concluído com sucesso.

Veja o resultado para os novos valores.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;

OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA                               4654            0    1239996 06-10-2008 23:09:01

A quantidade de extents não alterou depois de apagarmos diversos registros, mesmo após calcularmos a quantidade de registro exato da tabela.

SQL> select segment_name, count(extent_id), sum(bytes)/1024/1024
  2  from dba_extents
  3  where segment_name = TSTDBA
  4  group by segment_name;

SEGMENT_NAME         COUNT(EXTENT_ID) SUM(BYTES)/1024/1024
-------------------- ---------------- --------------------
TSTDBA                             52                   37

Para resolvermos esse problema, que vamos entender como “fragmentação”, basta reconstruirmos o mapa binário da tabela, para isso, apenas use um MOVE sem mencionar a tablespace que resolve nosso problema.

SQL> alter table TSTDBA move;

Tabela alterada.

SQL> select owner, table_name, blocks, empty_blocks, num_rows, to_char(last_analyzed,DD-MM-RRRR HH24:MI:SS) as "ANALYZE"
  2  from dba_tables
  3  where table_name = TSTDBA;

OWNER      TABLE_NAME                         BLOCKS EMPTY_BLOCKS   NUM_ROWS ANALYZE
---------- ------------------------------ ---------- ------------ ---------- -------------------
RODRIGO    TSTDBA                               4654            0    1239996 06-10-2008 23:09:01

SQL> select segment_name, count(extent_id), sum(bytes)/1024/1024
  2  from dba_extents
  3  where segment_name = TSTDBA
  4  group by segment_name;

SEGMENT_NAME         COUNT(EXTENT_ID) SUM(BYTES)/1024/1024
-------------------- ---------------- --------------------
TSTDBA                             38                   23

PRONTO! Veja que após nosso “rebuild” na tabela, liberamos cerca de 15MB para a tablespace, fazendo apenas uma reconstrução dos extents da tabela.

Existem muitos outros conceitos envolvidos sobre a alocação de extents, sem mencionar os freelists, gerenciamento das tablespaces (para tablespaces LMTLocal Management Tablespace, é um conceito diferente) e diferenças entre os segmentos de tabela e índice. Tudo isso foi apenas um modo de ilustrar o conceito de gerência de armazenamento e marca dágua no Oracle Server.

Existe um post no site ORA-600 , do autor Jose Laurindo Chiappa, que diz alguns mitos e “meias-verdades” sobre a fragmentação no Oracle Server, que recomendo a leitura para enteder com mais detalhes o gerenciamento por completo.

A idéia principal do post foi iniciar desde o conceito de HWM (High Water Mark) até o gerenciamento dos extents, passando por várias fases, para fornecer um melhor entendimento de como a arquitetura Oracle funciona. Mais informações, no meu blog.

Abraços,