terça-feira, 17 de junho de 2008

Reorganizando o Tablespace ...

Olá,
Dando prosseguimento ao artigo de Março de 2008 sobre introdução ao conceito de tablespaces, sabemos que o armazenamento dentro de um tablespace é organizado em segmentos e extents. Um extent pode ser alocado para um segmento e, portanto, é considerado usado; caso contrário, quando um extent não é alocado a nenhum segmento, ele é considerado livre. Pode ser um pouco complicado de entender, mas com o tempo, nos tablespaces gerenciados por dicionário, a alocação e desalocação dos extents fazia com que o espaço livre se tornasse fragmentado dando origem a fragmentação do tipo "honeycomb" na qual os extents livres ficavam adjacentes uns aos outros, e a fragmentação do tipo "buble" na qual os extents livres ficavam separados uns dos outros pelo espaço usado. A fragmentação "honeycomb" podia ser removida por um processo chamado aglutinação utilizando o comando ALTER TABLESPACE ... COALESCE para aglutinar o espaço livre contíguo explicitamente em um tablespace. Portanto, esta técnica era utilizada porque apenas o espaço contíguo poderia ser usado para alocar os extents. Já para remover a fragmentação do tipo "buble" era necessário fazer uma reorganização completa do tablespace utilizando os utilitários export (com o parâmetro COMPRESS=Y) e import para reorganizar em um único extent grande aqueles objetos que estavam fragmentados em um número grande de extents pequenos. A partir do Oracle 8i, este trabalho se tornou mais fácil com a inclusão do comando ALTER TABLE ... MOVE para mover a tabela dentro do próprio tablespace ou em outro tablespace. Em resumo, não estou querendo dizer que o comando COALESCE foi aposentado, mas com o advento do tablespace gerenciado localmente, foi eliminada a necessidade de aglutinar as extensões livres porque o gerenciamento local de extensões rastreia automaticamente o espaço livre adjacente. De qualquer forma, agora os tablespaces gerenciados por dicionário são coisas do passado.

O motivo de eu estar escrevendo este artigo partiu de uma discussão no fórum OTN da Oracle sobre algumas dúvidas relacionadas ao uso dos comandos ALTER TABLE ... MOVE (disponível a partir do Oracle 8i) e ALTER TABLE ... SHRINK (disponível a partir do Oracle 10g) no que diz respeito a realocação e compactação de tabelas de forma a poder ajustar a "High Water Mark" das mesmas, bem como a de utilizar de forma eficiente o espaço livre no tablespace sem a necessidade de ter que redimensioná-lo toda vez que não houver espaço suficiente para novas tabelas, etc... Outra coisa importante que irei mostrar é como podemos utilizar estes dois comandos (que tem propósitos diferentes) juntos para ajustar a HWM de um arquivo de dados a fim de poder diminuir o seu tamanho evitando receber as famosas mensagens "ORA-01658: não é possível criar a extensão INITIAL para o segmento no espaço de trabalho " ou "ORA-03297: o arquivo contém dados usados além do valor solicitado de RESIZE" ou até mesmo evitando receber o erro "ORA-01652: não é possível estender o segmento temp. em 8 no [tablespace]" ao tentar mover uma tabela. Na verdade, o comando ALTER TABLE ... MOVE pode ser usado para reorganizar as extensões das tabelas realocando os dados em um segmento novo (normalmente o mais próximo possível do início do datafile) no mesmo tablespace ou em outro tablespace eliminando assim problemas de linhas migradas e/ou encadeadas, além de ajustar a HWM. Portanto, o armazenamento existente da tabela não é dropado até que a tabela seja totalmente realocada, e para isso, é necessário um espaço livre no tablespace do tamanho da tabela que está sendo movida. Se não houver este espaço livre, então o erro ORA-01652 será emitido pelo Oracle. Vale a pena salientar que os usuários podem executar consultas na tabela durante a realocação, entretanto, as operações DML tais como aquelas que inserem, atualizam ou excluem dados, não são permitidas até que a realocação esteja completa.

Com relação ao espaço alocado mas não utilizado resultante de operações de update e delete dentro de um segmento é chamado de espaço livre fragmentado. Para reclamar este espaço para ser utilizado por outras tabelas, poderíamos utilizar técnicas que eu chamo de ETI (exportar, truncar e importar a tabela) ou o método CTAS (create table as select ...), entre outros, mas isso não será necessário se você estiver usando o Oracle 10g, porque é aí que o comando SHRINK entra em ação. Este comando realiza duas tarefas: O primeiro passo faz a compactação do segmento movendo as linhas para os blocos livres o máximo possível para o início do segmento e o segundo passo ajusta a "High Water Mark".

- ALTER TABLE ... ENABLE ROW MOVEMENT -- habilita a movimentação de linhas (ROWID's)
- ALTER TABLE ... SHRINK SPACE COMPACT -- realiza apenas o primeiro passo
- ALTER TABLE ... SHRINK SPACE -- realiza os dois passos (compactação e ajuste da HWM)

A opção CASCADE pode ser utilizada para compactar objetos dependentes como por exemplo os índices. É importante lembrar que para reorganizar um segmento usando o comando SHRINK é necessário que o tablespace use o gerenciamento automático de segmento ou ASSM (Automatic Segment Space Management). Enfim, para um melhor entendimento sobre estes comandos, vamos a um exemplo prático.

SQL> connect / as sysdba
Conectado.

-- Irei criar um usuário e um tablespace de teste com o tamanho inicial de 1 MB
SQL> create tablespace tbs_teste
  2  datafile '/u01/oradata/BD01/teste01.dbf' size 1m
  3  extent management local
  4  segment space management auto;

Tablespace criado.

SQL> create user teste identified by teste
  2  default tablespace tbs_teste quota unlimited on tbs_teste;

Usuário criado.

SQL> grant connect,resource to teste;

Concessão bem-sucedida.

SQL> connect teste/teste
Conectado.

-- Irei criar e popular uma tabela que chamarei de T
SQL> create table t (id  number);

Tabela criada.

SQL> insert into t select level from dual connect by level <= 45000; 
45000 linhas criadas.
SQL> commit;
Validação completa.
-- Irei deletar todas as linhas desta tabela SQL> delete from t;
45000 linhas deletadas.
SQL> commit;
Validação completa.
-- Irei analisar esta tabela a fim de gerar algumas informações sobre o segmento SQL> analyze table t compute statistics; Tabela analisada. SQL> select 2 a.blocks, b.blocks hwm, b.empty_blocks 3 from 4 user_segments a, user_tables b 5 where 6 a.segment_name=b.table_name and b.table_name='T'; BLOCKS HWM EMPTY_BLOCKS ---------- ---------- ------------ 80 73 7

Podemos ver acima que foram alocados 80 blocos para o segmento T sendo que 73 blocos foram utilizados e 7 não foram utilizados, ou seja, 7 blocos que estão acima da HWM. Podemos concluir que mesmo após o comando DELETE, podemos ver que o espaço nas extensões continuam alocados para o segmento T.


SQL> select extent_id,blocks,block_id from dba_extents
  2  where segment_name='T' and owner='TESTE';

 EXTENT_ID     BLOCKS   BLOCK_ID
---------- ---------- ----------
         0          8          9
         1          8         17
         2          8         25
         3          8         33
         4          8         41
         5          8         49
         6          8         57
         7          8         65
         8          8         73
         9          8         81

10 linhas selecionadas.

Para complementar, executando uma consulta na view DBA_EXTENTS podemos ver que 10 extents foram alocados para segurar os 80 blocos, ou seja, cada extent contém 8 blocos de dados. Para uma melhor visualização, veja como ficou o mapa do tablespace TBS_TESTE



-- Irei agora criar e popular uma tabela que chamarei de X
SQL> create table x (id number);

Tabela criada.

SQL> insert into x select level from dual connect by level <= 20000; 
20000 linhas criadas.
SQL> commit; Validação completa.

Após a criação e inserção de dados na tabela T, podemos ver abaixo no mapa do tablespace TBS_TESTE, que o Oracle alocou novos extents para a tabela X, ou seja, mesmo que a tabela T esteja "vazia", ela ainda assim deterá os 80 blocos para futuras inserções, a menos que a HWM seja ajustada com o comando TRUNCATE, ou com os métodos ETI, CTAS ou com o comando MOVE, ou com o comando SHRINK que irei mostrar mais adiante.




SQL> alter table t move;
alter table t move
*
ERRO na linha 1:
ORA-01658: não é possível criar a extensão INITIAL para o segmento no espaço de
trabalho TBS_TESTE

Como o tablespace está com o AUTOEXTEND desabilitado, podemos ver acima que o comando MOVE falhou porque não há espaço disponível no tablespace para acomodar uma cópia temporária do segmento T que está sendo movido.

SQL> alter table t enable row movement;

Tabela alterada.

SQL> alter table t shrink space;

Tabela alterada.

Após executar o comando SHRINK, podemos ver no mapa do tablespace TBS_TESTE abaixo que a HWM do segmento T foi ajustada, e o espaço livre foi liberado para uso para outros segmentos.




SQL> select extent_id,blocks,block_id from dba_extents where segment_name='T'
  2  and owner='TESTE';

 EXTENT_ID     BLOCKS   BLOCK_ID
---------- ---------- ----------
         0          8          9

SQL> analyze table t compute statistics;

Tabela analisada.

SQL> select
  2      a.blocks, b.blocks hwm, b.empty_blocks
  3    from
  4      user_segments a, user_tables b
  5    where
  6      a.segment_name=b.table_name and b.table_name='T';

    BLOCKS        HWM EMPTY_BLOCKS
---------- ---------- ------------
         8          1            7

Podemos confirmar acima que, realmente, a HWM do segmento T foi resetada.

-- Irei mostrar o que acontecerá se movermos a tabela X
SQL> alter table x move;

Tabela alterada.

Podemos perceber no mapa do tablespace abaixo, que a tabela X foi movida o máximo possível para o início do arquivo de dados, possibilitando assim que o tamanho do mesmo possa ser redimensionado para baixo caso necessário. Por exemplo, de 1 MB para 500 KB.




SQL> drop table t;

Tabela eliminada.

SQL> drop table x;

Tabela eliminada.

Bem, de vez em quando é comum ouvir questões como alguém que não está conseguindo criar uma tabela nova ou algum segmento que não está conseguindo alocar um extent por falta de espaço no tablespace, e para piorar, não é possível aumentar o tamanho do arquivo de dados ou criar um novo arquivo e dados por falta de espaço em disco. Então, de acordo com o exemplo prático acima, mostrarei abaixo o que é possível fazer com estes dois comandos (SHRINK e MOVE) juntos, de forma a tirar melhor proveito do espaço desperdiçado dentro de um tablespace. Também irei mostrar como é possível tentar diminuir o tamanho de um arquivo de dados que está ocupando muito espaço em disco, mesmo quando o erro ORA-03297 é emitido pelo Oracle.

-- Irei habilitar a extensão automática do arquivo de dados
SQL> alter database datafile '/u01/oradata/BD01/teste01.dbf' autoextend on;

Banco de dados alterado.

-- Irei criar uma tabela nova A
SQL> create table a (id number);

Tabela criada.

SQL> insert into a select level from dual connect by level <= 100000; 
100000 linhas criadas.
-- Irei criar uma tabela nova B SQL> create table b as select * from a;
Tabela criada.
-- Irei criar uma tabela nova C SQL> create table c as select * from b; Tabela criada.

Após a criação das 3 tabelas acima, o tablespace ficou com um tamanho final de 6272 KB e abaixo podemos ver como ficou o mapa de blocos do tablespace:




SQL> connect / as sysdba
Conectado.

-- Irei desabilitar a extensão automática do arquivo de dados
SQL> alter database datafile '/u01/oradata/BD01/teste01.dbf' autoextend off;

Banco de dados alterado.

-- Obtendo informações do arquivo de dados para fins de redimensionamento. De agora
-- em diante, farei referência a este SQL usando o nome resize_information;
SQL> set verify off
SQL> column value new_val blksize
SQL> select value from v$parameter where name = 'db_block_size';

       VALUE
------------
        8192

SQL> select a.file_id,
  2         a.file_name,
  3         ceil((nvl(hwm,1)*&&blksize)/1024) smallest,
  4         ceil(blocks*&&blksize/1024) currsize,
  5         ceil(blocks*&&blksize/1024) -
  6         ceil((nvl(hwm,1)*&&blksize)/1024) savings
  7  from dba_data_files a,
  8       (select file_id, max(block_id+blocks-1) hwm
  9        from dba_extents where owner='TESTE'
 10        group by file_id) b
 11  where a.file_id = b.file_id;

 FILE_ID FILE_NAME                            SMALLEST   CURRSIZE    SAVINGS
-------- ---------------------------------- ---------- ---------- ----------
      15 /u01/oradata/BD01/teste01.dbf            6208       6272         64

Podemos verificar acima que o tamanho atual do arquivo de dados é de 6272 KB e que poderemos diminuir o tamanho do mesmo para no máximo 6208 KB, já que 64 KB é o espaço livre disponível acima da HWM do arquivo de dados.

SQL> alter database datafile 15 resize 6208k;

Banco de dados alterado.

Após reduzir o tamanho do arquivo de dados, podemos ver na figura abaixo como ficou o mapa de blocos do tablespace:




-- Irei agora deletar as linhas das tabelas A e B de forma a restar apenas 1 linha
-- para cada tabela
SQL> connect teste/teste
Conectado.

SQL> delete from a where rownum <= 99999; 
99999 linhas deletadas.
SQL> delete from b where rownum <= 99999;
99999 linhas deletadas.
SQL> commit;
Validação completa.
SQL> connect / as sysdba Conectado. -- Obtendo informações do arquivo de dados para fins de redimensionamento SQL> @resize_information; FILE_ID FILE_NAME SMALLEST CURRSIZE SAVINGS -------- ---------------------------------- ---------- ---------- ---------- 15 /u01/oradata/BD01/teste01.dbf 6208 6208 0

Podemos notar como já era esperado, que os blocos utilizados pelas linhas deletadas não foram desalocados após as operações de delete, isso porque que a HWM não é ajustada por comandos DML. Portanto, estes blocos ainda fazem parte das extensões pertencentes a estes segmentos (A e B), então este "espaço livre" só poderá ser utilizado pelos mesmos. Neste caso, como fazer então para que estes blocos livres fiquem disponíveis para outros segmentos? Abaixo irei demonstrar o uso do comando SHRINK.

-- Irei compactar (fazer com que os blocos fiquem adjacentes/contíguos) e
-- opcionalmente ajustar a HWM dos segmentos A e B
SQL> connect teste/teste
Conectado.

SQL> alter table a enable row movement;

Tabela alterada.

SQL> alter table a shrink space cascade;

Tabela alterada.

SQL> alter table b enable row movement;

Tabela alterada.

SQL> alter table b shrink space cascade;

Tabela alterada.

Acima eu utilizei a opção CASCADE apenas para demonstrar que a mesma pode ser utilizada para compactar objetos dependentes, como por exemplo, os índices caso existam. Então, após realizar a compactação dos dados e ajustar a HWM dos segmentos A e B, poderemos ver abaixo como ficou o mapa de blocos do tablespace:



Eu deixei a tabela C por último e propositalmente pelo seguinte motivo: Apesar de eu não ter realizado nenhuma operação DML na tabela C, podemos ver abaixo que a mesma está de alguma forma fragmentada pelo fato de o tablespace ter sido criado com a opção padrão AUTOALLOCATE, o que significa que toda vez que o segmento precisar de espaço para crescer a extensão alocada será de 1 MB, o que significa que foi alocado mais espaço do que o necessário como mostrado abaixo:

SQL> analyze table c compute statistics;

Tabela analisada.

SQL> select
  2      a.blocks, b.blocks hwm, b.empty_blocks
  3    from
  4      user_segments a, user_tables b
  5    where
  6      a.segment_name=b.table_name and b.table_name='C';

    BLOCKS        HWM EMPTY_BLOCKS
---------- ---------- ------------
       256        165           91

Deste modo, poderemos reclamar algum espaço livre compactando a tabela C como mostrado abaixo:

SQL> alter table c enable row movement;

Tabela alterada.

SQL> alter table c shrink space cascade;

Tabela alterada.

SQL> analyze table c compute statistics;

Tabela analisada.

SQL> select
  2      a.blocks, b.blocks hwm, b.empty_blocks
  3    from
  4      user_segments a, user_tables b
  5    where
  6      a.segment_name=b.table_name and b.table_name='C';

    BLOCKS        HWM EMPTY_BLOCKS
---------- ---------- ------------
       168        165            3

Após compactar a tabela C, poderemos ver abaixo como ficou o mapa de blocos do tablespace



Dando prosseguimento ao exemplo prático, e após compactar a tabela C, podemos verificar abaixo que o tamanho atual do arquivo de dados é de 6208 KB e que poderemos diminuir o tamanho do mesmo para no máximo 5504 KB, já que 704 KB é o espaço livre disponível acima da HWM do arquivo de dados.

SQL> connect / as sysdba
Conectado.

SQL> @resize_information;

 FILE_ID FILE_NAME                            SMALLEST   CURRSIZE    SAVINGS
-------- ---------------------------------- ---------- ---------- ----------
      15 /u01/oradata/BD01/teste01.dbf            5504       6208        704

SQL> alter database datafile 15 resize 5504k;

Banco de dados alterado.

Após reduzir o tamanho do arquivo de dados, podemos ver na figura abaixo como ficou o mapa de blocos do tablespace:



Após todas estas operações, irei verificar realmente quanto de espaço alocado existe para as tabelas A, B e C utilizando o SQL abaixo:

SQL> break on report
SQL> compute sum of blocks on report
SQL> compute sum of extents on report
SQL> compute sum of kbytes on report
SQL> select
  2     owner,
  3     segment_name,
  4     blocks,
  5     extents,
  6     bytes/1024 KBYTES
  7  from
  8     dba_segments
  9  where
 10     tablespace_name = 'TBS_TESTE';

OWNER                SEGMENT_NAME               BLOCKS    EXTENTS     KBYTES
-------------------- ---------------------- ---------- ---------- ----------
TESTE                A                               8          1         64
TESTE                B                               8          1         64
TESTE                C                             168         17       1344
                                            ---------- ---------- ----------
sum                                                184         19       1472

De acordo com o resultado acima, podemos notar que as tabelas A, B e C estão utilizando um total de 1472 KB de dados em 184 blocos. Será possível reduzir o tamanho do arquivo de dados para 1472 KB?

SQL> alter database datafile 15 resize 1472k;
alter database datafile 15 resize 1472k
*
ERRO na linha 1:
ORA-03297: o arquivo contém dados usados além do valor solicitado de RESIZE
Apesar de haver espaço livre suficiente no arquivo de dados para podermos reduzir o seu tamanho, vimos que não foi possível fazê-lo pelo fato deste espaço livre estar fragmentado dentro do arquivo de dados. Realmente, o que está impedindo a realização desta tarefa é o último bloco (HWM) utilizado pela tabela C. Neste caso, não adiantará utilizar novamente o comando SHRINK pelo fato de a mesma já estar totalmente compactada. É agora que entra em ação o comando ALTER TABLE ... MOVE para resolver este problema e mover os segmentos para o mais próximo possível do início do arquivo de dados.

SQL> alter table a move;

Tabela alterada.

SQL> alter table b move;

Tabela alterada.

SQL> alter table c move;

Tabela alterada.

SQL> @resize_information;

 FILE_ID FILE_NAME                            SMALLEST   CURRSIZE    SAVINGS
-------- ---------------------------------- ---------- ---------- ----------
      15 /u01/oradata/BD01/teste01.dbf            2432       5504       3072
Após realocarmos as tabelas, podemos verificar acima que o tamanho atual do arquivo de dados é de 5504 KB e que poderemos diminuir o tamanho do mesmo para no máximo 2432 KB, já que 3072 KB é o espaço livre disponível acima da HWM do arquivo de dados.



Podemos notar na figura acima, que as tabelas foram realocadas para o início do arquivo de dados, mas por algum motivo a tabela C não ficou alocada como eu esperava. Bem, isto apenas comprova que não temos controle sobre onde o Oracle alocará os blocos. Neste caso, irei reduzir o tamanho do arquivo de dados o máximo possível com o tamanho de 2432 KB.

SQL> alter database datafile 15 resize 2432k;

Banco de dados alterado.

SQL> @resize_information;

 FILE_ID FILE_NAME                            SMALLEST   CURRSIZE    SAVINGS
-------- ---------------------------------- ---------- ---------- ----------
      15 /u04/oradata/BD01/teste01.dbf            2432       2432          0

Pronto, podemos verificar que pelo menos conseguimos reduzir o tamanho do arquivo de dados de 6272 KB para 2432 KB apenas utilizando os comandos SHRINK e MOVE. Ao final de tudo, o mapa de blocos do tablespace ficou como mostrado na figura abaixo:



Para finalizar, no Oracle Enterprise Manager Database Control existe um utilitário chamado Supervisor de Segmento que pode ser utilizado para fornecer dicas sobre o fato de um objeto ser ou não um bom candidato para uma operação de compactação com base no nível de fragmentação de espaço de um objeto. Neste caso, o supervisor de segmento determina se os objetos possuem espaço não usado que pode ser liberado levando em consideração requisitos de espaço futuro. O cálculo de espaço futuro estimado é baseado em um histórico de tendências. O supervisor também gera relatórios sobre a tendência de crescimento histórico dos segmentos. É possível ainda utilizar usar essas informações para o planejamento da capacidade e para chegar a uma decisão informada sobre quais segmentos deverão ou não ser compactados.

Algumas considerações sobre o comando SHRINK no Oracle 10g:
Só é possível realizar a compactação de tabelas utilizando o comando SHRINK em tablespaces com ASSM.

Os tipos mais comuns de segmentos passíveis de compactação são:

- Tabelas heap e tabelas organizadas por índice
- Índices
- Partições e subpartições
- Views materializadas e logs de views materializadas
- Segmentos LOB

Não é possível compactar:

- Segmentos de UNDO

- Segmentos temporários (No oracle 11g já é possível compactar o tablespace temporário)
- Tabelas clusterizadas
- Tabelas que contenham colunas do tipo LONG

Com relação ao comando ALTER TABLE ... MOVE, vale a pena salientar que após finalizar a realocação da tabela, será necessário reconstruir os índices da tabela (rebuild) , caso os mesmos existam.


Maiores informações podem ser encontradas na documentação oficial da Oracle: Shrinking Database Segments Online e Moving a Table to a New Segment or Tablespace

quarta-feira, 11 de junho de 2008

Um pouco do Oracle Text ...

Olá,
O Oracle Text é uma feature embutida no banco de dados que utiliza a linguagem SQL para indexar, pesquisar e analisar textos e documentos binários armazenados nas tabelas. Nas versões Oracle 11g, 10g e 9i, essa feature é conhecida como Oracle Text. No Oracle 8i é conhecida como Oracle Intermedia e, no Oracle 8, a mesma é conhecida como Oracle Context. Esta tecnologia oferece uma solução completa para pesquisa de textos na qual a mesma permite filtrar e extrair textos de conteúdos de diferentes formatos de documentos. O Oracle Text suporta mais de 150 formatos de documentos, incluindo os mais populares como os documentos Microsoft Office, além de formatos de arquivo do Adobe PDF, arquivos HTML e XML. Neste artigo irei demonstrar como gravar um documento binário (.doc) no banco de dados Oracle 10g Express Edition e o que fazer para poder realizar pesquisas no seu conteúdo. Para maiores informações sobre esta tecnologia, acesse a página oficial do Oracle Text no site da Oracle.

O arquivo que irei carregar para o banco de dados (arquivo.doc) possui o conteúdo mostrado na figura abaixo.


 Então vamos realizar um teste prático ...

C:\>sqlplus /@XE as sysdba

SQL*Plus: Release 10.1.0.2.0 - Production on Qua Jun 11 08:52:26 2008

Copyright (c) 1982, 2004, Oracle.  All rights reserved.

Conectado a:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production

-- Criação de um usuário para teste
SYS> create user scott identified by tiger default tablespace users quota unlimited on users;

Usuário criado.

SYS> grant connect,resource to scott;

Concessão bem-sucedida.

-- Criação de um diretório que indica a localização do arquivo (.doc)
SYS> create or replace directory documentos as 'c:\docs';

Diretório criado.

SYS> grant read on directory documentos to scott;

Concessão bem-sucedida.

SYS> connect scott/tiger@XE
Conectado.

-- Criação de uma tabela de teste
SCOTT> create table teste (
  2    id number,
  3    nome varchar2(20),
  4    documento blob
  5  );

Tabela criada.

-- Criação de uma stored procedure para carregar o arquivo para o banco de dados
SCOTT> create or replace procedure carrega_arquivo (p_file_name in teste.nome%type)
  2    as
  3     v_bfile bfile;
  4     v_blob blob;
  5    begin
  6      insert into teste (id,nome,documento)
  7      values (1,p_file_name,empty_blob())
  8      return documento into v_blob;
  9      v_bfile := bfilename('DOCUMENTOS',p_file_name);
 10      dbms_lob.fileopen(v_bfile, dbms_lob.file_readonly);
 11      dbms_lob.loadfromfile(v_blob,v_bfile,dbms_lob.getlength(v_bfile));
 12      dbms_lob.fileclose(v_bfile);
 13      commit;
 14    end;
 15    /

Procedimento criado.

-- Carregando o arquivo para a tabela
SCOTT> execute carrega_arquivo('arquivo.doc');

Procedimento PL/SQL concluído com sucesso.

SCOTT> select id,nome from teste;

       ID NOME
---------- --------------------
         1 arquivo.doc

-- Verificando o tamanho do arquivo binário carregado
SCOTT> select dbms_lob.getlength(documento) bytes from teste;

BYTES
----------
     19968

-- Criando um índice do tipo context que permitirá a pesquisa textual
SCOTT> create index i_teste_documento on teste (documento)
  2    indextype is ctxsys.context parameters ('sync (on commit)');

Índice criado.

-- Verificando se houve algum erro na criação do índice
SCOTT> select * from ctx_user_index_errors;

não há linhas selecionadas

Abaixo, podemos verificar que após a criação do índice I_TESTE_DOCUMENTO na coluna DOCUMENTO, algumas tabelas e índices adicionais foram criados.

SCOTT> select table_name from user_tables;

TABLE_NAME
------------------------------
TESTE
DR$I_TESTE_DOCUMENTO$I
DR$I_TESTE_DOCUMENTO$K
DR$I_TESTE_DOCUMENTO$N
DR$I_TESTE_DOCUMENTO$R

SCOTT> select index_name,table_name from user_indexes;

INDEX_NAME                     TABLE_NAME
------------------------------ ------------------------------
I_TESTE_DOCUMENTO              TESTE
SYS_IL0000014279C00003$        TESTE
DR$I_TESTE_DOCUMENTO$X         DR$I_TESTE_DOCUMENTO$I
SYS_IL0000014285C00006$$       DR$I_TESTE_DOCUMENTO$I
SYS_IL0000014290C00002$$       DR$I_TESTE_DOCUMENTO$R
SYS_IOT_TOP_14288              DR$I_TESTE_DOCUMENTO$K
SYS_IOT_TOP_14293              DR$I_TESTE_DOCUMENTO$N

7 linhas selecionadas. 





Apenas por curiosidade, podemos perceber que a tabela abaixo armazena o texto puro extraído do documento binário que foi carregado na coluna DOCUMENTOS da tabela TESTE.

SCOTT> select token_text from DR$I_TESTE_DOCUMENTO$I;

TOKEN_TEXT
----------------------------------------------------
EDUARDO
ISTO
LEGATTI
ORACLE
POR
TESTE
TEXT
UM
É

9 linhas selecionadas.
 
Realizando a Pesquisa Textual

SCOTT> select id,nome from teste where contains(documento, 'legatti', 1) > 0;

       ID NOME
---------- --------------------
         1 arquivo.doc

SCOTT> select id,nome from teste where contains(documento, 'Oracle', 1) > 0;

       ID NOME
---------- --------------------
         1 arquivo.doc

SCOTT> select id,nome from teste where contains(documento, 'Eduardo', 1) > 0;

       ID NOME
---------- --------------------
         1 arquivo.doc

SCOTT> select id,nome from teste where contains(documento, 'nao existe', 1) > 0;

não há linhas selecionadas 

No mais, para manutenção do índice caso necessário, poderemos utilizar alguns procedimentos como CTX_DDL.SYNC_INDEX para sincronização do índice porque nem todos os índices são atualizado automaticamente quando registros são adicionados ou deletados, ou CTX_DDL.OPTIMIZE_INDEX para reconstruir o índice quando necessário.


Voltando ao Passado ...


Apenas fazendo uma comparação, se estivéssemos utilizando o já obsoleto Oracle 8 (8.0.5), esta tarefa seria um pouco mais complicada, pois teríamos que instalar e configurar o Oracle Context Cartridge e ainda realizar as tarefas abaixo:

Iniciar o serviço do Oracle Context

windows  -> ctxsvr80 -user ctxsys/ctxsys -personality qdml (m -> reindexa em background)
linux    -> ctxsrv -user ctxsys/ctxsys -personality m &

-- Criar o índice
execute ctx_ddl.create_policy('documento','teste.documento');
execute ctx_ddl.create_index('documento');

segunda-feira, 2 de junho de 2008

READ ONLY table? Abordando uma nova funcionalidade do Oracle 11g ...

Olá,
Para permitir o acesso somente leitura a uma tabela no banco de dados Oracle, normalmente utilizamos a sintaxe GRANT SELECT ON [tabela] TO [usuario], ou seja, somente o privilégio de objeto SELECT será dado pelo usuário proprietário da tabela a um outro usuário do banco de dados. Bem, e se quisermos que o proprietário da tabela também tenha acesso somente leitura a esta tabela? Por padrão, o proprietário (owner) da tabela não possui nenhuma restrição de escrita em suas próprias tabelas e, neste caso, para que o proprietário da tabela tenha acesso somente leitura à tabela, seria necessário criar uma TRIGGER de banco de dados que restringiria operações de INSERT, UPDATE e DELETE na tabela, ou até mesmo uma outra solução mais simples, como a de criar uma restrição CHECK no estado DISABLE VALIDATE como demonstrado abaixo:

oracle11g@linux-abr6:~> sqlplus / as sysdba

SQL*Plus: Release 11.1.0.6.0 - Production on Sex Mai 2 19:15:56 2008

Copyright (c) 1982, 2007, Oracle.  All rights reserved.

Conectado a:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> conn scott/tiger
Conectado.

SQL> create table test (id number);

Tabela criada.

SQL> insert into test values (1);

1 linha criada.

SQL> alter table test add constraint chk_read_only check(1=1) DISABLE VALIDATE;

Tabela alterada.

SQL> insert into test values (2);
insert into test values (2)
*
ERRO na linha 1:
ORA-25128: Não há inserção/atualização/deleção na tabela com restrição
(SCOTT.CHK_READ_ONLY) desativada e validada

SQL> update test set id=2;
update test set id=2
*
ERRO na linha 1:
ORA-25128: Não há inserção/atualização/deleção na tabela com restrição
(SCOTT.CHK_READ_ONLY) desativada e validada

SQL> delete from test;
delete from test
*
ERRO na linha 1:
ORA-25128: Não há inserção/atualização/deleção na tabela com restrição
(SCOTT.CHK_READ_ONLY) desativada e validada

SQL> truncate table test;
truncate table test
*
ERRO na linha 1:
ORA-25128: Não há inserção/atualização/deleção na tabela com restrição
(SCOTT.CHK_READ_ONLY) desativada e validada

Em resumo, se houver uma restrição DISABLE VALIDATE, então nenhuma modificação será permitida nas colunas restringidas, ou seja, qualquer comando DML e/ou DDL (truncate) não será permitido. Para informações adicionais, no artigo de Fevereiro de 2008, eu descrevo sobre algumas funcionalidades dos estados das restrições de integridade.

Aliás, se você percebeu, eu estou conectado a um banco de dados Oracle 11g (11.1.0.6.0) e isso não é por acaso. Nas versões anteriores ao Oracle 11g, para criarmos uma tabela somente leitura teríamos que usar um dos métodos mencionados acima, mas agora poderemos utilizar a facilidade do comando ALTER TABLE que foi aprimorado no Oracle 11g para permitir alterar o estado de uma tabela do modo READ WRITE para o modo READ ONLY e vice versa.

Para demonstrar esta nova funcionalidade, irei criar uma nova tabela de teste.


SQL> drop table test;

Tabela eliminada.

SQL> create table test (id number);

Tabela criada.

SQL> insert into test values (1);

1 linha criada.

-- Acessando a view USER_TABLES, podemos verificar que a tabela não está no modo
-- somente leitura
SQL> select table_name, read_only from user_tables;

TABLE_NAME                     READ_ONLY
------------------------------ ---------
TEST                           NO

-- Alterando a tabela para o modo somente leitura
SQL> alter table test READ ONLY;

Tabela alterada.

SQL> select table_name, read_only from user_tables;

TABLE_NAME                     READ_ONLY
------------------------------ ---------
TEST                           YES

SQL> insert into test values (2);
insert into test values (2)
     *
ERRO na linha 1:
ORA-12081: operação de atualização não permitida na tabela "SCOTT"."TEST"

SQL> update test set id=2;
update test set id=2
*
ERRO na linha 1:
ORA-12081: operação de atualização não permitida na tabela "SCOTT"."TEST"

SQL> delete from test;
delete from test
     *
ERRO na linha 1:
ORA-12081: operação de atualização não permitida na tabela "SCOTT"."TEST"

SQL> truncate table test;
truncate table test
        *
ERRO na linha 1:
ORA-12081: operação de atualização não permitida na tabela "SCOTT"."TEST"

Então uma vez que a tabela esteja no modo somente leitura (READ ONLY), nenhum comando DML ou comando DDL (truncate) que modifiquem os dados da tabela não poderão ser executados. Inclusive, os comandos MERGE ou até sentenças SELECT FOR UPDATE não serão permitidos. No meu ponto de vista, esta funcionalidade acrescentada ao comando ALTER TABLE é de muita utilidade quando por algum motivo precisarmos de forma fácil e rápida restringir o acesso de escrita aos usuários nas tabelas do banco de dados.

-- Alterando o modo da tabela para permitir operações de escrita
SQL> alter table test READ WRITE;

Tabela alterada.

SQL> select table_name, read_only from user_tables;

TABLE_NAME                     READ_ONLY
------------------------------ ---------
TEST                           NO

SQL> drop table test;

Tabela eliminada.