quarta-feira, 4 de dezembro de 2013

Oracle ACE

Olá,






Após alguns anos compartilhando e disseminando meus conhecimentos sobre o banco de dados Oracle neste blog e, mais recentemente, em outros canais (OTN, GPO, etc.), é com grande alegria e orgulho que recebo a informação da aprovação da minha nomeação como Oracle ACE. Esse título é concedido pela Oracle às pessoas que são reconhecidas pela comunidade técnica Oracle, não só pela proficiência técnica, mas também por serem, em especial, evangelistas, entusiastas e disseminadores de conhecimento das tecnologias Oracle. Esta nomeação envolve um processo criterioso no qual o candidato é avaliado sobre seus conhecimentos na tecnologia Oracle, certificações e principalmente sobre a qualidade e frequência de suas contribuições para a comunidade Oracle no seu país, cidade e até pelo mundo.

Gostaria de agradecer a todos aqueles que fizeram isso ser possível como, vocês leitores do meu blog, aos demais Oracle ACEs aqui do Brasil (valeu pessoal) e em especial aos amigos Francisco Munoz Alvarez (Oracle ACE Director), Victor Armbrust (Oracle ACE) e ao colega Thierry Vanden Broucke (DBA) que me proporcionaram uma motivação a mais. Por mais que o trabalho do dia a dia se torne cansativo e estressante em alguns momentos e a falta de tempo se torne perceptível e evidente em virtude da priorização de outras atividades (estudo, família, lazer), mesmo assim não nos desanimam a continuar escrevendo, compartilhando, disseminando o conhecimento e ajudando não só as pessoas que buscam o aprendizado, como também a nossa comunidade Oracle espalhada pelo Brasil e pelo mundo. Muitos desafios estão ainda por vir e espero continuar atuando cada vez mais em favor da disseminação do conhecimento.


Muito obrigado.



Eduardo Legatti


 
http://apex.oracle.com/pls/apex/f?p=19297:4:105575139418233::NO:4:P4_ID:7340



Oracle ACE Award



domingo, 1 de dezembro de 2013

Desmistificando alguns conceitos do Oracle: COMMIT vs. CHECKPOINT e LOGGING vs. NOLOGGING

Olá,

Fazendo algumas leituras aqui e ali em forums, blogs, entre outros sites, percebo que muitos profissionais, sejam eles iniciantes ou veteranos na tecnologia do banco de dados Oracle, ainda não entendem corretamente a dinâmica de funcionamento de alguns conceitos que envolvem alguns processos de segundo plano do Oracle como o Log Writer (LGWR), o Database Writer (DBWn), o Checkpoint (CKPT), além de confundir ou interpretar de forma equivocada o significado e a consequência em alterar uma tabela para o modo NOLOGGING. De vez em quando recebo algumas questões referentes aos conceitos ACID - (A)tomicidade, (C)onsistência, (I)solamento e (D)urabilidade, na qual muito se pergunta se é somente durante o COMMIT que os dados que estão em memória (Database Buffer Cache) serão gravados permanentemente nos Data Files. No mais, o objetivo desse artigo será fazer um breve resumo através de um quadro comparativo para algumas dúvidas que são bem comuns em forums e grupos de discussão sobre Oracle. Dentre muitas dúvidas, listo algumas abaixo:
  1. Quando efetuamos COMMIT em uma transação os dados são gravados diretamente nos Data Files?
  2. Em que momento o processo Database Writer (DBWn) grava informações nos Data Files?
  3. O buffer de redo (log buffer) armazena apenas dados comitados? 
  4. Qual o objetivo principal do processo Checkpoint (CKPT)?
  5. Quando alteramos uma tabela para NOLOGGING toda operação realizada na mesma não gerará nenhum dado de redo?
  6. Uma operação de carga direta (direct insert ou CTAS) será mais rápida em uma tabela modo NOLOGGING?
  7. Uma tabela no modo NOLOGGING pode se recuperar de uma falha de instância?
Bom, para começar, no banco de dados Oracle um grupo de redo é um conjunto de no mínimo dois ou mais arquivos que tem como função primária registrar todas as alterações feitas no banco de dados, incluindo as alterações com e sem commit. As entradas de redo são armazenadas temporariamente nos buffers de registro de redo (Log Buffer) da SGA (System Global Area) onde o processo de segundo plano log writer (LGWR) grava essas entradas sequencialmente em um arquivo de registro de redo online.

Estas gravações do buffer de redo para os arquivos ocorrem nas seguintes cinco situações: (1) a cada 3 segundos, (2) quando 1/3 do buffer estiver cheio, (3) quando o comando commit for emitido, (4) quando as entradas de redo no buffer atingir 1 MB, (5) antes do processo de segundo plano DBWn gravar as alterações do cache de banco de dados nos data files. Os arquivos de redo log online são utilizados de forma cíclica, por exemplo, se dois arquivos constituem o registro de redo online, o primeiro arquivo é preenchido, o segundo arquivo é preenchido, o primeiro arquivo é reutilizado e preenchido, o segundo arquivo é reutilizado e preenchido e assim por diante.

Cada vez que um arquivo é preenchido, ele recebe um número de seqüência de registro para identificar o conjunto de entradas de redo. As informações de um arquivo de redo log online são usadas apenas para proteger e recuperar o banco de dados em caso de uma falha de instância que evite que os dados em memória sejam gravados nos data files. Assim, caso uma falha evite que os dados modificados em memória (comitados) sejam gravados permanentemente nos data files, as alterações perdidas possam ser obtidas dos arquivos de redo log online através de um processo de recuperação (recover). A figura abaixo nos mostra um pouco da arquitetura de uma instância Oracle.




Em resumo, comitar uma transação significa apenas a garantia de sua recuperação em caso de falha, e não a gravação dos dados diretamente nos data files como muitos pensam. Esta é a 4ª propriedade dentre as conhecidas sobre segurança e integridade dos dados (ACID) que trata da DURABILIDADE e persistência das atualizações confirmadas, mesmo que haja falhas no sistema.

Em relação ao processo Checkpoint (CKPT), o mesmo ajuda a reduzir a quantidade de tempo necessário para a recuperação da instância. Durante um checkpoint, o processo CKPT atualiza o cabeçalho do control file e dos data files para refletir o último SCN (System Change Number) bem-sucedido. O processo CKPT tem como objetivo sinalizar os processos DBWn de forma que os mesmos possam gravar os buffers sujos (dirty buffers) que são aqueles blocos alterados em memória que ainda não foram gravados nos data files. Ele também atualiza os cabeçalhos dos data files e dos control files com algumas informações para que o processo SMON saiba por onde começar uma operação de recover no caso de uma queda da instância (crash recovery). A frequência com que as gravações que o processo DBWn são feitas dependem de algumas condições, mas a que mais tem influência é a métrica MTTR (Mean Time To Recovery) que pode ser alterada pelo parâmetro FAST_START_MTTR_TARGET. O valor padrão deste parâmetro é 0 (zero) e recomendo que o mesmo não seja alterado. Em relação ao Database Buffer Cache, vale a pena salientar que os buffers sujos são descarregados para os data files, independente de uma transação ser ou não comitada. Mesmo que um buffer não esteja sujo (clean buffers) isso não significa que este dado teve sua transação comitada.

Enfim, como dito anteriormente, quando ocorre COMMIT em uma transação, o log buffer é descarregado para o disco e não o buffer cache. É importante salientar que o log buffer armazena dados comitados e não comitados. O que diferencia dados comitados de não comitados no buffer log é uma marca especial chamada (Commit Marker), nada mais. Caso seja necessário a realização de um crash recovery, é através dessa marca que o Oracle saberá quais dados precisam ser aplicados nos data files (roll forward) e quais precisam ser desfeitos (roll back). Portanto, podemos perceber que poderemos ter dados comitados nos arquivos de redo log online que não estão ainda gravados nos data files e vice versa. Segue abaixo um teste para comprovar o que foi falado até aqui.

C:\>sqlplus / as sysdba

SQL*Plus: Release 11.2.0.1.0 Production on Dom Dez 1 14:04:54 2013

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

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

SQL> create tablespace tbs_teste 
  2  datafile 'C:\ORACLE\ORADATA\BD01\TBS_TESTE01.DBF'
  3  size 1M;

Tablespace criado.

SQL> create table emp (id varchar2(10)) tablespace tbs_teste;

Tabela criada.

SQL> insert into emp values ('VALOR1');

1 linha criada.

SQL> commit;

Commit concluído.


Acima foi criada a tabela EMP na tablespace TBS_TESTE e inserido um registro com o dado "VALOR1". Podemos ver abaixo que este dado ainda não foi gravado no data file TESTE01.DBF.

SQL> host strings -a TESTE01.DBF

Strings v2.51
Copyright (C) 1999-2013 Mark Russinovich

}|{z
BD01
TBS_TESTE

Irei agora simular um checkpoint através do comando "alter system checkpoint" e, então, poderemos ver que os dados serão descarregados do Database Buffer Cache na (SGA) para o disco, ou seja, para o data file TESTE01.DBF.

SQL> alter system checkpoint;

Sistema alterado.

SQL> host strings -a TESTE01.DBF

Strings v2.51
Copyright (C) 1999-2013 Mark Russinovich

}|{z
BD01
TESTE
VALOR1

Para ajudar a guardar estes conceitos, segue abaixo um quadro comparativo dos possíveis eventos que podem ocorrer e quais arquivos sofrem gravações:




Já em relação aos modos LOGGING e NOLOGGING de uma tabela no Oracle, percebo que tem muita gente que ainda confunde ou não entende corretamente os seus conceitos. Quando criamos uma tabela e não especificamos LOGGING ou NOLOGGING, esta opção será herdada da tablespace na qual a tabela residirá. Por padrão todas as tablespaces no Oracle, exceto a TEMP são configuradas no modo LOGGING. Toda alteração DML ou DDL realizada em uma tabela no modo LOGGING será registrada nos arquivos de redo log online, ou seja, podemos dizer que a tabela está protegida e poderá ser recuperada em caso de falha da instância. Já o modo NOLOGGING minimiza a geração de dados de redo para algumas operações.

Tenho a impressão que muitas pessoas têm a percepção de que o modo NOLOGGING elimina completamente a geração de redo para todas as operações DML (insert, delete, merge, update). Isto não está correto. O modo NOLOGGING não afeta a geração de redo para uma operação INSERT regular, DELETE, MERGE e UPDATE. Em relação à operações DML, somente operações de INSERT de carga direta (direct path insert) tiram vantagens enquanto uma tabela está no modo NOLOGGING. A velocidade do INSERT é maior exatamente por ter uma minimização na geração de redo. A desvantagem é que em caso de alguma falha da instância, a operação não poderá ser recuperada. Portanto, recomenda-se que o seu uso seja restrito para operações na qual a perda de dados possa ser aceita, ou seja, se o dado é crítico, não use o modo NOLOGGING. Nada impede também que um backup seja realizado após a carga de dados. Em relação a velocidade do INSERT, veja a demonstração abaixo usando o método CTAS utilizando como fonte dos dados a tabela (EMP) com aproximadamente 16 milhões de registros:

SQL> set timing on
SQL> create table t1_dados_logging as select * from emp;

Tabela criada.
Decorrido: 00:00:39.76

SQL> create table t1_dados_nologging NOLOGGING as select * from emp;

Tabela criada.
Decorrido: 00:00:21.28

Podemos ver acima que a diferença foi de 18 segundos. Para grandes cargas a diferença poderá ser de muitas horas. Houve uma vez que eu consegui diminuir o tempo de carga em uma rotina de 12 horas para 3 horas, apenas adicionando a cláusula NOLOGGING na criação da tabela ao usar o método CTAS.

Em resumo, seguem abaixo algumas operações que tiram vantagem em relação à performance no modo NOLOGGING:
  • Direct Load INSERT (através do hint APPEND)
  • DIRECT LOAD (SQL*Loader)
  • CREATE TABLE ... AS SELECT
  • CREATE INDEX
  • ALTER TABLE MOVE
  • ALTER TABLE ... MOVE PARTITION
  • ALTER TABLE ... SPLIT PARTITION
  • ALTER TABLE ... ADD PARTITION
  • ALTER TABLE ... MERGE PARTITION
  • ALTER TABLE ... MODIFY PARTITION
  • ALTER INDEX ... SPLIT PARTITION
  • ALTER INDEX ... REBUILD
  • ALTER INDEX ... REBUILD PARTITION

Para comprovar que menos dado de redo é gerado, veja o exemplo abaixo na qual utilizo o hint APPEND durante a inserção de dados em uma tabela criada como NOLOGGING:

SQL> create table t1_dados_nologging (id number) NOLOGGING;

Tabela criada.

SQL> select name,value as redo1 from v$sysstat where name='redo size';

NAME                    REDO1
----------------------- ---------
redo size               882169876

SQL> insert into t1_dados_nologging select * from emp;

16777216 linhas criadas.

SQL> select name,value as redo2 from v$sysstat where name='redo size';

NAME                    REDO2
----------------------- ----------
redo size               1110731824

SQL> insert /*+ APPEND */ into t1_dados_nologging select * from emp;

16777216 linhas criadas.

SQL> select name,value as redo3 from v$sysstat where name='redo size';

NAME                    REDO3
----------------------- ----------
redo size               1110819664


No exemplo acima, podemos notar que o redo gerado no primeiro insert foi de 228.561.948 (REDO2 - REDO1), enquanto que no insert utilizando o hint APPEND (direct insert) foi de apenas 87.840 (REDO3 - REDO2), ou seja, um mínimo de redo foi gerado.

No mais, vale a pena salientar que se um banco de dados estiver operando no modo NOARCHIVELOG, então para cargas diretas (Direct Path Insert), alterar uma tabela para o modo NOLOGGING se torna desnecessário. Segue abaixo um quadro comparativo das operações envolvidas para cada tipo de cenário:
 

Por fim, caso uma falha de mídia ocorra após uma tabela no modo NOLOGGING ter sido populada e nenhum backup tenha sido realizado após essa carga de dados, após a operação de recovery e abertura do banco de dados, poderemos verificar a presença do erro abaixo:

SQL> select count(*) from T1_DADOS_NOLOGGING;
select count(*) from T1_DADOS_NOLOGGING
                     *
ERRO na linha 1:
ORA-01578: bloco de dados ORACLE danificado (arquivo núm. 1, bloco núm. 31913)
ORA-01110: 1 do arquivo de dados: 'C:\ORACLE\ORADATA\BD02\SYSTEM01.DBF'
ORA-26040: O bloco de dados foi carregado por intermédio da opção NOLOGGING
 

sábado, 2 de novembro de 2013

Pesquisando uma palavra ou texto em um schema do banco de dados Oracle

Olá,

Você como DBA ou desenvolvedor PL/SQL já se deparou em uma situação onde alguém lhe perguntou se existiria a possibilidade de pesquisar uma palavra ou texto em todas as colunas de todas as tabelas de um schema de um banco de dados Oracle? Bom, o objetivo desse artigo será demonstrar a utilização de uma stored procedure criada especificamente para esse propósito. A stored procedure deverá receber dois parâmetros. O primeiro parâmetro será a palavra ou texto a ser pesquisado e o segundo parâmetro será o schema no banco de dados que será alvo da pesquisa. A stored procedure utilizará uma instrução SELECT para realizar a pesquisa nas colunas das tabelas utilizando o operador LIKE, ou seja, neste caso poderão ser utilizados wildcards como o sinal de porcentagem ('%') e o underscore ('_'). Vale a pena salientar que a pesquisa será realizada apenas em colunas do tipo VARCHAR2, VARCHAR, CHAR, CLOB e DATE. Dependendo do número de registros envolvidos nas tabelas, a pesquisa poderá levar horas e consumir um tempo de processamento considerável. Para minimizar a espera pela execução completa da pesquisa, irei criar uma tabela que chamarei de TEMP_PESQUISA que será utilizada para armazenar os resultados parciais durante a execução da stored procedure. A tabela TEMP_PESQUISA conterá 3 colunas que armazenarão o nome da tabela na qual foram encontradas ocorrências da palavra ou texto pesquisado, o nome da coluna em que foi encontrada a palavra ou texto pesquisado e o número de ocorrências encontradas, por coluna. Enfim, irei criar abaixo duas tabelas no schema de banco de dados SCOTT a fim de simular o uso da stored procedure SP_PESQUISA_DADOS que irei criar mais abaixo:

C:\>sqlplus scott/tiger

SQL*Plus: Release 11.2.0.1.0 Production on Sáb Nov 2 11:34:37 2013

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

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

SQL> create table T1
  2  (
  3     id        number,
  4     t1_col1   varchar2 (100),
  5     t1_col2   varchar (20),
  6     t1_col3   char (50),
  7     t1_col4   clob,
  8     t1_col5   date
  9  );

Tabela criada.

SQL> create table T2
  2  (
  3     id        number,
  4     t2_col1   varchar2 (100),
  5     t2_col2   varchar (20),
  6     t2_col3   char (50),
  7     t2_col4   clob,
  8     t2_col5   date
  9  );

Tabela criada.

Após a criação das tabelas acima, irei popular as mesmas com os registros abaixo:

SQL> insert into T1
  2  values (1,'teste1','Teste 03/11/2013','Oracle','Teste 123 oracle blog','03/11/2013');

1 linha criada.

SQL> insert into T1
  2  values (2,'teste2','Blog','123 teste','123 oracle blog 03/11/2012','02/11/2013');

1 linha criada.

SQL> insert into T2
  2  values (1,'Teste1','Teste 03/11/2013','Oracle','Teste345 oracle blog','03/11/2013');

1 linha criada.

SQL> insert into T2
  2  values (2,'TESTE2','Blog Oracle','123teste456 2013','123 Oracle blog 2011','12/10/2013');

1 linha criada.

SQL> commit;

Commit concluído.
 
Agora irei criar a tabela auxiliar TEMP_PESQUISA e a stored procedure SP_PESQUISA_DADOS no usuário SYSTEM.

SQL> connect system/manager
Conectado.

SQL> create table temp_pesquisa
  2  (  table_name varchar2(30),
  3     column_name varchar2(30),
  4     count number
  5  );

Tabela criada.


SQL> CREATE OR REPLACE PROCEDURE sp_pesquisa_dados (p_information   IN VARCHAR2,
  2                                                 p_owner         IN VARCHAR2)
  3  IS
  4  BEGIN
  5     DECLARE
  6        v_information   VARCHAR2 (100);
  7        v_owner         VARCHAR2 (30);
  8        v_count         NUMBER := 0;
  9     BEGIN
 10        v_information := UPPER (p_information);
 11        v_owner := UPPER (p_owner);
 12
 13        FOR rec
 14           IN (  SELECT b.table_name, b.column_name
 15                   FROM all_tables a, all_tab_columns b
 16                  WHERE     a.table_name = b.table_name
 17                        AND a.owner = b.owner
 18                        AND b.owner = v_owner
 19                        AND data_type IN
 20                               ('VARCHAR2', 'VARCHAR', 'CHAR', 'CLOB', 'DATE')
 21               ORDER BY b.table_name, b.column_name)
 22        LOOP
 23           EXECUTE IMMEDIATE
 24                 'select count(*) from '
 25              || p_owner
 26              || '.'
 27              || rec.table_name
 28              || ' where upper('
 29              || rec.column_name
 30              || ') like '''
 31              || v_information
 32              || ''''
 33              INTO v_count;
 34
 35           IF v_count > 0
 36           THEN
 37              INSERT INTO temp_pesquisa
 38                   VALUES (rec.table_name, rec.column_name, v_count);
 39
 40              COMMIT;
 41           END IF;
 42        END LOOP;
 43     END;
 44  END;
 45  /

Procedimento criado.
 
Com os objetos devidamente criados, irei simular uma pesquisa no schema SCOTT. No exemplo abaixo irei pesquisar quais tabelas e colunas que possuam em seu conteúdo a palavra 'teste', seja ela no início, meio ou fim de uma cadeia de caracteres. Vale a pena salientar que a pesquisa também será "case insensitive", ou seja, tanto faz informar a palavra 'teste', 'TESTE', 'Teste', etc.

SQL> exec sp_pesquisa_dados('%teste%','SCOTT');

Procedimento PL/SQL concluído com sucesso.

SQL> select * from temp_pesquisa;

TABLE_NAME                     COLUMN_NAME                         COUNT
------------------------------ ------------------------------ ----------
T1                             T1_COL1                                 2
T1                             T1_COL2                                 1
T1                             T1_COL3                                 1
T1                             T1_COL4                                 1
T2                             T2_COL1                                 2
T2                             T2_COL2                                 1
T2                             T2_COL3                                 1
T2                             T2_COL4                                 1

8 linhas selecionadas. 

Após a execução da stored procedure, podemos verificar acima que o resultado da pesquisa encontrou 10 ocorrências. Segue abaixo outra pesquisa na qual irei pesquisar a palavra 'oracle', seja ela no início, meio ou fim de uma cadeia de caracteres.

SQL> truncate table temp_pesquisa;

Tabela truncada.

SQL> exec sp_pesquisa_dados('%oracle%','SCOTT');

Procedimento PL/SQL concluído com sucesso.

SQL> select * from temp_pesquisa;

TABLE_NAME                     COLUMN_NAME                         COUNT
------------------------------ ------------------------------ ----------
T1                             T1_COL3                                 1
T1                             T1_COL4                                 2
T2                             T2_COL2                                 1
T2                             T2_COL3                                 1
T2                             T2_COL4                                 2

5 linhas selecionadas. 

 
Para finalizar, irei pesquisar o valor '2013' de forma a demonstrar que também será possível pesquisar caracteres em uma coluna do tipo DATE quando for necessário realizar algum tipo de pesquisa que envolvam datas.

SQL> truncate table temp_pesquisa;

Tabela truncada.

SQL> alter session set nls_date_format='dd/mm/yyyy';

Sessão alterada.

SQL> exec sp_pesquisa_dados('%2013%','SCOTT');

Procedimento PL/SQL concluído com sucesso.

SQL> select * from temp_pesquisa;

TABLE_NAME                     COLUMN_NAME                         COUNT
------------------------------ ------------------------------ ----------
T1                             T1_COL2                                 1
T1                             T1_COL5                                 2
T2                             T2_COL2                                 1
T2                             T2_COL3                                 1
T2                             T2_COL5                                 2

5 linhas selecionadas.

quarta-feira, 2 de outubro de 2013

Abordando colunas do tipo auto-incremento (IDENTITY) e valores DEFAULT com SEQUENCES no Oracle 12c

Olá,

No artigo de Novembro/2008 eu abordei o uso do objeto SEQUENCE, disponível no banco de dados Oracle, com o objetivo de simular a criação de colunas do tipo auto-incremento. Realmente, até a versão 11g, o Oracle não possui um tipo de dado auto-incremento, mas, no Oracle 12c, isso mudou. Neste artigo irei abordar duas novas características que foram implementadas no Oracle 12c e mencionadas no artigo de Junho/2013.
  • No Oracle 12c é possível especificar as pseudo-colunas CURRVAL e NEXTVAL de uma sequence como valor DEFAULT para uma coluna de uma tabela.
  • No Oracle 12c existe suporte à colunas do tipo auto-incremento (IDENTITY).
     

Utilizando valores DEFAULT com SEQUENCES de banco de dados


Até a versão 11g, o Oracle não permite definir um objeto SEQUENCE na cláusula DEFAULT de uma coluna de tabela, como demonstrado abaixo:
C:\>sqlplus scott/tiger

SQL*Plus: Release 11.2.0.1.0 Production on Qua Out 2 19:00:08 2013

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

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

SQL> create sequence seq_teste nocache;

Seqüência criada.

SCOTT> desc minha_tabela
 Nome                   Nulo?    Tipo
 ---------------------- -------- ----------------------
 ID                     NOT NULL NUMBER
 DESCRICAO              NOT NULL VARCHAR2(100)

SQL> alter table minha_tabela modify id DEFAULT SEQ_TESTE.NEXTVAL;
alter table minha_tabela modify id DEFAULT SEQ_TESTE.NEXTVAL
*
ERRO na linha 1:
ORA-00984: coluna não permitida aqui
 
À partir do Oracle 12c, é possível especificar uma SEQUENCE de banco de dados e utilizar as pseudo-colunas NEXTVAL e CURRVAL na cláusula DEFAULT de uma coluna de tabela como demonstrado abaixo:

 
C:\>sqlplus scott/tiger

SQL*Plus: Release 12.1.0.1.0 Production on Qua Out 2 19:03:54 2013

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

Horário do último log-in bem-sucedido: Qua Out 2 2013 18:54:03 -03:00

Conectado a:
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

SQL> create sequence seq_teste nocache;

Sequência criada.

SQL> create table minha_tabela (
  2  id number constraint pk_minha_tabela primary key,
  3  nome varchar2(60) not null);

Tabela criada.

SQL> alter table minha_tabela modify id DEFAULT SEQ_TESTE.NEXTVAL;

Tabela alterada.

SQL> select table_name,
  2         column_name,
  3         data_default
  4    from user_tab_columns 
  5   where table_name='MINHA_TABELA';

TABLE_NAME          COLUMN_NAME      DATA_DEFAULT
------------------- ---------------- -----------------------------
MINHA_TABELA        ID               "SCOTT"."SEQ_TESTE"."NEXTVAL"
MINHA_TABELA        NOME

SQL> insert into minha_tabela (nome) values ('Nome_1');

1 linha criada.

SQL> insert into minha_tabela (nome) values ('Nome_2');

1 linha criada.

SQL> insert into minha_tabela (nome) values ('Nome_3');

1 linha criada.

SQL> select * from minha_tabela order by 1;

        ID NOME
---------- -----------------------------------------
         1 Nome_1
         2 Nome_2
         3 Nome_3

SQL> insert into minha_tabela values (10,'Nome_10');

1 linha criada.

SQL> select * from minha_tabela order by 1;

        ID NOME
---------- -----------------------------------------
         1 Nome_1
         2 Nome_2
         3 Nome_3
        10 Nome_10

No exemplo acima, podemos ver que é possível inserir uma valor de ID explícito, como por exemplo ID=10. Vale a pena salientar que, caso a coluna aceite valores nulos, e um valor NULL for inserido, então o valor DEFAULT não será utilizado. Para que isso não aconteça, a coluna deverá ser criada utilizando a cláusula ON NULL conforme exemplo do comando abaixo:

create table t1 (
  id    number DEFAULT ON NULL SEQ_TESTE.NEXTVAL,
  nome  varchar(10)   
);

No mais, vale a pena salientar que, se tentarmos droparmos a sequence, o Oracle não irá fazer uma verificação de dependência entre a tabela e a sequence, ou seja, conseguiremos dropar a sequence sem o menor problema. Consequentemente, as operações de INSERT irão falhar com a emissão do erro ORA-02289 conforme demonstração abaixo:
SQL> drop sequence seq_teste;

Sequência eliminada.

SQL> insert into minha_tabela (nome) values ('Nome_4');
insert into minha_tabela (nome) values ('Nome_4')
*
ERRO na linha 1:
ORA-02289: a sequência não existe


Colunas do tipo auto-incremento (IDENTITY)


Uma outra novidade do Oracle 12c se refere à possibilidade de criar colunas do tipo auto-incremento. Na minha visão, não foi criado um novo tipo de dado (DATA TYPE) no Oracle, e sim uma engenharia interna que oferece esse recurso. A figura abaixo mostra as novas cláusulas que poderão ser utilizadas na criação de uma coluna em uma tabela:



Segue abaixo alguns exemplos práticos dos tipos de coluna IDENTITY que poderemos criar à partir do Oracle 12c. Como primeiro exemplo, podemos perceber abaixo que ao criar uma tabela contendo uma coluna IDENTITY, será necessário que o owner da tabela tenha o privilégio de sistema CREATE SEQUENCE.

SQL> create table t1_always (
  2    id number GENERATED ALWAYS AS IDENTITY,
  3    nome varchar2(20)
  4  );
create table t1_always (
*
ERRO na linha 1:
ORA-01031: privilégios insuficientes

SQL> select * from session_privs;

PRIVILEGE
----------------------------------------
SET CONTAINER
CREATE TABLE
CREATE SESSION

SQL> connect / as sysdba
Conectado.

SQL> grant create sequence to scott;

Concessão bem-sucedida.

SQL> connect scott/tiger
Conectado.

SQL> create table t1_always (
  2    id number GENERATED ALWAYS AS IDENTITY,
  3    nome varchar2(20)
  4  );

Tabela criada.

SQL> desc t1_always
 Nome              Nulo?    Tipo
 ----------------- -------- -----------------------
 ID                NOT NULL NUMBER
 NOME                       VARCHAR2(20)

Podemos notar pelo resultado do comando DESCRIBE acima, que uma coluna IDENTITY é obrigatória (NOT NULL). Abaixo podemos ver que o Oracle automaticamente criou uma SEQUENCE de banco de dados com o nome de ISEQ$$_19860.

SQL> select sequence_name,
  2         increment_by,
  3         cache_size,
  4         last_number
  5    from user_sequences;

SEQUENCE_NAME     INCREMENT_BY CACHE_SIZE LAST_NUMBER
----------------- ------------ ---------- -----------
ISEQ$$_19860                 1         20           1

Se por acaso tentarmos dropar essa SEQUENCE, o Oracle irá emitir o erro ORA-32794, ou seja, diferentemente de especificar uma SEQUENCE como DEFAULT em uma coluna, onde não há qualquer dependência entre a SEQUENCE e a coluna da tabela, no caso de uma coluna do tipo IDENTITY a SEQUENCE que é criada automaticamente faz parte dessa engenharia.

SQL> drop sequence ISEQ$$_19860;
drop sequence ISEQ$$_19860
*
ERRO na linha 1:
ORA-32794: não é possível eliminar uma sequência gerada pelo sistema

Consultado as views DBA/ALL/USER_TAB_COLUMNS podemos identificar as colunas que são do tipo IDENTITY conforme demonstrado abaixo:


SQL> select table_name,column_name,data_default,identity_column from user_tab_columns;

TABLE_NAME   COLUMN_NAME   DATA_DEFAULT                    IDE
------------ ------------- ------------------------------- ---
T1_ALWAYS    ID            "SCOTT"."ISEQ$$_19860".NEXTVAL  YES
T1_ALWAYS    NOME                                          NO

No mais, segue abaixo os tipos de colunas IDENTITY que poderemos criar à partir do Oracle 12c.

GENERATED ALWAYS AS IDENTITY

Uma coluna IDENTITY criada com a cláusula ALWAYS sempre irá forçar a geração do valor sequencial. Caso algum valor seja informado na coluna IDENTITY, o erro ORA-32795 será emitido.

SQL> create table t1_always (
  2    id number GENERATED ALWAYS AS IDENTITY,
  3    nome varchar2(20)
  4  );

Tabela criada.

SQL> insert into t1_always values (1,'teste1');
insert into t1_always values (1,'teste1')
*
ERRO na linha 1:
ORA-32795: não é possível inserir em uma coluna de identidade sempre gerada

SQL> insert into t1_always (nome) values ('teste1');

1 linha criada.

SQL> select * from t1_always;

        ID NOME
---------- --------------------
         1 teste1

SQL> insert into t1_always (nome) 
  2  select 'teste'||level from dual
  3  connect by level <= 10;

10 linhas criadas.

SQL> select * from t1_always;

        ID NOME
---------- --------------------
         1 teste1
         2 teste1
         3 teste2
         4 teste3
         5 teste4
         6 teste5
         7 teste6
         8 teste7
         9 teste8
        10 teste9
        11 teste10

11 linhas selecionadas.

GENERATED BY DEFAULT AS IDENTITY

Uma coluna IDENTITY criada com a cláusula BY DEFAULT não irá forçar a geração do valor sequencial, ou seja, caso algum valor seja informado na coluna IDENTITY, o mesmo será inserido ao invés do valor sequencial.

SQL> create table t2_by_default (
  2    id number GENERATED BY DEFAULT AS IDENTITY,
  3    nome varchar2(20)
  4  );

Tabela criada.

SQL> insert into t2_by_default (nome) values ('teste1');

1 linha criada.

SQL> insert into t2_by_default values (11,'teste11');

1 linha criada.

SQL> select * from t2_by_default;

        ID NOME
---------- --------------------
         1 teste1
        11 teste11

GENERATED BY DEFAULT ON NULL AS IDENTITY

Uma coluna IDENTITY criada com a cláusula BY DEFAULT ON NULL não irá forçar a geração do valor sequencial, ou seja, caso algum valor seja informado na coluna IDENTITY, o mesmo será inserido ao invés do valor sequencial. Caso NULL seja informado, então o valor sequencial será gerado para a coluna.

SQL> create table t3_by_default_on_null (
  2    id number GENERATED BY DEFAULT ON NULL AS IDENTITY,
  3    nome varchar2(20)
  4  );

Tabela criada.

SQL> insert into t3_by_default_on_null (nome) values ('teste1');

1 linha criada.

SQL> insert into t3_by_default_on_null (id,nome) values (11,'teste11');

1 linha criada.

SQL> insert into t3_by_default_on_null (id,nome) values (NULL,'teste200');

1 linha criada.

SQL> select * from t3_by_default_on_null;

        ID NOME
---------- --------------------
         1 teste1
        11 teste11
         2 teste200

Por fim, poderemos utilizar a view de dicionário de dados DBA/ALL/USER_TAB_IDENTITY_COLS para visualizar todas as tabelas que possuem colunas do tipo IDENTITY, conforme demonstrado abaixo:

SQL> select table_name,
  2         column_name,
  3         generation_type,
  4         identity_options
  5  from   user_tab_identity_cols;

TABLE_NAME              COLUMN_NAME GENERATION  IDENTITY_OPTIONS
----------------------- ----------- ----------- ----------------------------------------
T1_ALWAYS               ID          ALWAYS      START WITH: 1, INCREMENT BY: 1, MAX_VALU
                                                E: 9999999999999999999999999999, MIN_VAL
                                                UE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, OR
                                                DER_FLAG: N

T2_BY_DEFAULT           ID          BY DEFAULT  START WITH: 1, INCREMENT BY: 1, MAX_VALU
                                                E: 9999999999999999999999999999, MIN_VAL
                                                UE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, OR
                                                DER_FLAG: N

T3_BY_DEFAULT_ON_NULL   ID          BY DEFAULT  START WITH: 1, INCREMENT BY: 1, MAX_VALU
                                                E: 9999999999999999999999999999, MIN_VAL
                                                UE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, OR
                                                DER_FLAG: N

segunda-feira, 2 de setembro de 2013

Abordando o TRUNCATE TABLE CASCADE do Oracle 12c

Olá,

Possivelmente quase todo desenvolvedor ou mesmo DBA, já se deparou com a situação na qual executar o comando DDL (TRUNCATE TABLE) em algumas tabelas seria muito mais rápido e prático do que executar o comando DML (DELETE) nessas mesmas tabelas. Isso porque o comando DELETE além de gerar dados de UNDO, poderia tomar um tempo considerável dependendo do número de linhas nas tabelas envolvidas. Já o comando TRUNCATE, além de não gerar dados de UNDO, é praticamente instantâneo, pois apenas a (HWM) (High Water Mark) da tabela é afetada. No Oracle, caso queiramos truncar tabelas que estão sendo referenciadas através de chaves estrangeiras (Foreign Keys) em outras tabelas, precisaremos antes de mais nada, desabilitar essas constraints antes da execução do comando TRUNCATE. Como exemplo, poderíamos utilizar o resultado gerado pelas sentenças SQL abaixo para realizar essa operação.

-- Habilitar todas as constraints foreign key do usuário
select 'alter table '|| table_name ||' enable constraints '|| constraint_name||';'
from user_constraints
where constraint_type='R';

-- Desabilitar todas as constraints foreign key do usuário
select 'alter table '|| table_name ||' disable constraints '|| constraint_name||';'
from user_constraints
where constraint_type='R';

Com o resultado das instruções acima, poderemos desabilitar as constraints (FK), executar os comandos TRUNCATE nas tabelas, e então habilitar novamente as constraints (FK). No Oracle 12c, uma das novidades lançada, inclusive mencionada no artigo de Junho/2013, se refere à inclusão da cláusula CASCADE no comando TRUNCATE TABLE, conforme figura abaixo:


Bom, eu acredito que mesmo no Oracle 12c, os comandos SQL apresentados mais acima continuarão sendo úteis. Imagine um modelo de dados onde temos uma tabela MASTER e outra tabela DETAIL. A tabela DETAIL possui uma foreign key (NO ACTION) para a tabela MASTER. No Oracle 12c, se tentarmos truncar a tabela MASTER utilizando a cláusula CASCADE, o seguinte erro irá ocorrer:

SQL> truncate table master CASCADE;
truncate table master CASCADE
               *
ERRO na linha 1:
ORA-14705: chaves exclusiva ou primaria na tabela referenciadas por chaves
externas ativadas na tabela "SCOTT"."DETAIL" 
 
O erro acima ocorreu porque a foreign key definida na tabela DETAIL não era do tipo (ON DELETE CASCADE). Portanto, conclui-se que somente foreign keys que sejam do tipo (ON DELETE CASCADE) poderão ser afetadas pela cláusula CASCADE do comando TRUNCATE TABLE do Oracle 12c. No mais, segue abaixo um cenário de demonstração.
 

Na figura acima temos um modelo de dados contendo 4 tabelas e seus relacionamentos. O objetivo será truncar as 4 tabelas apenas executando o comando TRUNCATE TABLE ... CASCADE na tabela TABELA_A.

C:\>sqlplus scott/tiger

SQL*Plus: Release 12.1.0.1.0 Production on Seg Set 2 21:02:55 2013

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

Horário do último log-in bem-sucedido: Seg Set 2 2013 21:02:48 -03:00

Conectado a:
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

SQL> select table_name, constraint_name, delete_rule
  2    from user_constraints
  3   where constraint_type = 'R';

TABLE_NAME           CONSTRAINT_NAME    DELETE_RU
-------------------- ------------------ ---------------
TABELA_B             FK_B_A             CASCADE
TABELA_C             FK_C_A             CASCADE  
TABELA_D             FK_D_C             CASCADE

No resultado acima, podemos verificar que as constraints foreign keys existentes nas tabelas B, C e D são do tipo (ON DELETE CASCADE). Abaixo, irei popular alguns registros nas tabelas.

SQL> insert into tabela_a values (1);

1 linha criada.

SQL> insert into tabela_b values (1);

1 linha criada.

SQL> insert into tabela_c values (1);

1 linha criada.

SQL> insert into tabela_d values (1);

1 linha criada.

SQL> commit;

Commit concluído.

Caso eu tente truncar a tabela TABELA_A sem utilizar a cláusula CASCADE, o erro ORA-02266 será emitido conforme demonstrado abaixo:

SQL> truncate table TABELA_A;
truncate table TABELA_A
               *
ERRO na linha 1:
ORA-02266: chaves exclusiva/primária na tabela referenciadas por chaves
externas ativadas

Para que a tabela TABELA_A e as tabelas dependentes sejam truncadas, bastará utilizar a cláusula CASCADE conforme demonstrado abaixo:

SQL> truncate table TABELA_A CASCADE:

Tabela truncada.

SQL> select * from TABELA_A;

não há linhas selecionadas

SQL> select * from TABELA_B;

não há linhas selecionadas

SQL> select * from TABELA_C;

não há linhas selecionadas

SQL> select * from TABELA_D;

não há linhas selecionadas

segunda-feira, 5 de agosto de 2013

Movendo ou renomeando arquivos de bancos de dados no Oracle (11g vs 12c)

Olá,

Para melhor gerenciar o tamanho de um arquivo de dados ou até mesmo otimizar o desempenho geral de E/S do banco de dados, pode ser necessário mover um ou mais arquivos de dados (data files) de uma tablespace para um local diferente. Não só arquivos de dados, como também arquivos de controle (control files) e arquivos de redo log online (redo log files) podem sem movidos para localizações diferentes quando necessário. O propósito deste artigo é demonstrar os passos e comandos necessários para mover os arquivos de bancos de dados para outras localizações. Estes mesmos passos poderão ser executados também para renomear um arquivo de bancos de dados. Irei abordar os comandos necessários para mover não só os data files, como também os control files e os redo log files. Ao final, irei demonstrar um novo comando do Oracle 12c (ALTER DATABASE MOVE) que permite a movimentação ONLINE dos arquivos de dados. Esse novo comando é uma das novidades do Oracle 12c que foi mencionada no artigo de Junho/2013.



Movendo arquivos de controle (control files)

  1. Alterar o arquivo de inicialização spfile com a nova localização do control file (ALTER SYSTEM).
  2. Realizar shutdown do banco de dados.
  3. Mover o control file para a nova localização.
  4. Iniciar a instância e abrir o banco de dados (STARTUP).
Neste cenário irei abordar os arquivos de controle (control files). Abaixo podemos ver que os control files estão localizados em /oradata/BD01 conforme demonstrado tanto pelo resultado da view dinâmica de desempenho V$CONTROLFILE quanto pela infomação do arquivo de parâmetro de inicialização spfile. O objetivo será mover os control files para a nova localização em /oradata2/BD01.

$ sqlplus / as sysdba

SQL*Plus: Release 11.2.0.3.0 Production on Seg Ago 5 19:32:35 2013

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

Conectado a:
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - Production
With the Partitioning option

SQL> select * from v$controlfile;

STATUS  NAME                           IS_  BLOCK_SIZE FILE_SIZE_BLKS
------- ------------------------------ ---- ---------- --------------
        /oradata/BD01/control01.ctl    NO       16384            594
        /oradata/BD01/control02.ctl    NO       16384            594

SQL> show parameter control_files;

NAME                TYPE       VALUE
------------------- ---------- ----------------------------
control_files       string     /oradata/BD01/control01.ctl,
                               /oradata/BD01/control02.ctl

SQL> alter system set control_files = '/oradata2/BD01/control01.ctl',
                                      '/oradata2/BD01/control02.ctl' scope=SPFILE;

Sistema alterado.

SQL> shutdown immediate
Banco de dados fechado.
Banco de dados desmontado.
Instância ORACLE desativada.

[oracle@linux1 /]$ mv /oradata/BD01/control*.ctl /oradata2/BD01/

SQL> startup
Instância ORACLE iniciada.

Total System Global Area  313860096 bytes
Fixed Size                  1344652 bytes
Variable Size             109054836 bytes
Database Buffers          197132288 bytes
Redo Buffers                6328320 bytes
Banco de dados montado.
Banco de dados aberto.

SQL> select * from v$controlfile;

STATUS  NAME                           IS_  BLOCK_SIZE FILE_SIZE_BLKS
------- ------------------------------ ---- ---------- --------------
        /oradata2/BD01/control01.ctl    NO       16384            594
        /oradata2/BD01/control02.ctl    NO       16384            594  


Movendo arquivos de dados (data files) com o banco de dados fechado

  1. Realizar shutdown do banco de dados.
  2. Iniciar a instância e montar o banco de dados (STARTUP MOUNT).
  3. Copiar ou renomear o arquivo de dados para a nova localização.
  4. Atualizar o control file com a nova localização do arquivo de dados (ALTER DATABASE RENAME).
  5. Abrir o banco de dados (ALTER DATABASE OPEN).
  6. Deletar o arquivo de dados da localização antiga.

Neste cenário abordarei a movimentação dos arquivos de dados (data files). Abaixo podemos ver que o data file users01.dbf pertencente à tablespace USERS encontra-se localizado em /oradata/BD01 conforme demonstrado pela view de dicionário de dados DBA_DATA_FILES. O objetivo será mover o data file users01.dbf para a nova localização em /oradata2/BD01.

SQL> select file_id,file_name,tablespace_name
  2    from dba_data_files
  3   where tablespace_name='USERS';

   FILE_ID FILE_NAME                          TABLESPACE_NAME
---------- ---------------------------------- ------------------
         4 /oradata/BD01/users01.dbf          USERS

SQL> shutdown immediate
Banco de dados fechado.
Banco de dados desmontado.
Instância ORACLE desativada.

SQL> startup mount
Instância ORACLE iniciada.

Total System Global Area  313860096 bytes
Fixed Size                  1344652 bytes
Variable Size             109054836 bytes
Database Buffers          197132288 bytes
Redo Buffers                6328320 bytes
Banco de dados montado.

SQL> !cp -a  /oradata/BD01/users01.dbf /oradata2/BD01/users01.dbf

SQL> alter database rename file 
  2  '/oradata/BD01/users01.dbf'
  3  to
  4  '/oradata2/BD01/users01.dbf';

Banco de dados alterado.

SQL> alter database open;

Banco de dados alterado.

SQL> !rm /oradata/BD01/users01.dbf

SQL> select file_id,file_name,tablespace_name
  2    from dba_data_files
  3   where tablespace_name='USERS';

   FILE_ID FILE_NAME                          TABLESPACE_NAME
---------- ---------------------------------- ------------------
         4 /oradata2/BD01/users01.dbf          USERS

Vale a pena salientar que neste cenário foi utilizado o comando ALTER DATABASE para mover o arquivo de dados. Esse método é útil quando precisamos mover arquivos de dados de tablespaces de sistema como as tablespaces SYSTEM, SYSAUX, UNDO e TEMP, pois essas tablespaces e seus data files não podem ser colocados OFFLINE enquanto o banco de dados estiver aberto.


Movendo arquivos de dados (data files) com o banco de dados aberto

  1. Colocar a tablespace do arquivo de dados no estado OFFLINE.
  2. Copiar ou renomear o arquivo de dados para a nova localização.
  3. Atualizar o control file com a nova localização do arquivo de dados (ALTER TABLESPACE RENAME).
  4. Colocar a tablespace do arquivo de dados no estado ONLINE.
  5. Deletar o arquivo de dados da localização antiga.

Neste cenário abordarei a movimentação dos arquivos de dados usando o comando ALTER TABLESPACE RENAME. Como já foi mencionado anteriormente, se o arquivo de dados que desejamos mover faz parte de uma outra tablespace alé da SYSTEM, SYSAUX, UNDO e TEMP, é preferível usar o método ALTER TABLESPACE para mover os data files por um motivo crucial: O banco de dados, exceto pela tablespace cujo arquivo de dados será movido, permanecerá disponível para acesso durante toda a operação.

SQL> alter tablespace users offline;

Tablespace alterado.

SQL> !cp -a /oradata/BD01/users01.dbf /oradata2/BD01/users01.dbf

SQL> alter tablespace users rename datafile
  2  '/oradata/BD01/users01.dbf'
  3  to
  4  '/oradata2/BD01/users01.dbf';

Tablespace alterado.

SQL> alter tablespace users online;

Tablespace alterado.

SQL> !rm /oradata/BD01/users01.dbf

SQL> select file_id,file_name,tablespace_name
  2    from dba_data_files
  3   where tablespace_name='USERS';

   FILE_ID FILE_NAME                          TABLESPACE_NAME
---------- ---------------------------------- ------------------
         4 /oradata2/BD01/users01.dbf          USERS 

Movendo arquivos de redo log online (redo log files)

  1. Realizar shutdown do banco de dados.
  2. Copiar ou renomear o arquivo de redo log online para a nova localização.
  3. Iniciar a instância e montar o banco de dados (STARTUP MOUNT).
  4. Atualizar o control file com a nova localização do arquivo de redo log online (ALTER DATABASE RENAME).
  5. Abrir o banco de dados (ALTER DATABASE OPEN).
  6. Deletar o arquivo da localização antiga.

Neste cenário abordarei a movimentação de redo log files usando o comando ALTER DATABASE RENAME. Embora seja possível mover indiretamente arquivos de redo log online descartando grupos de redo log inteiros e adicionando-os novamente em um local diferente, essa solução poderá não ser bem sucedida caso o redo log file a ser descartado insistir em ficar com o status ACTIVE. Com esse status, o arquivo de redo log online não poderá ser excluído. No mais, o método usado abaixo é similar ao método usado para mover os arquivos de dados (data files). Abaixo podemos ver que os arquivos de redo log online encontram-se localizados em /oradata/BD01 conforme demonstrado pela view dinâmica de desempenho V$LOGFILE. O objetivo será mover o arquivo de redo log online redo01.log para a nova localização em /oradata2/BD01.

SQL> select * from v$logfile;

    GROUP# STATUS  TYPE    MEMBER                   IS_
---------- ------- ------- ------------------------ ---
         1         ONLINE  /oradata/BD01/redo01.log NO
         2         ONLINE  /oradata/BD01/redo02.log NO
         3         ONLINE  /oradata/BD01/redo03.log NO


SQL> shutdown immediate
Banco de dados fechado.
Banco de dados desmontado.
Instância ORACLE desativada. 
SQL> startup mount
Instância ORACLE iniciada.

Total System Global Area  313860096 bytes
Fixed Size                  1344652 bytes
Variable Size             109054836 bytes
Database Buffers          197132288 bytes
Redo Buffers                6328320 bytes
Banco de dados montado.

SQL> !cp -a /oradata/BD01/redo01.log /oradata2/BD01/redo01.log

SQL> alter database rename file
  2  '/oradata/BD01/redo01.log'
  3  to
  4  '/oradata2/BD01/redo01.log';

Banco de dados alterado.

SQL> alter database open;

Banco de dados alterado.

SQL> !rm /oradata/BD01/redo01.log

SQL> select * from v$logfile;

    GROUP# STATUS  TYPE    MEMBER                    IS_
---------- ------- ------- ------------------------- ---
         1         ONLINE  /oradata2/BD01/redo01.log NO
         2         ONLINE  /oradata/BD01/redo02.log  NO
         3         ONLINE  /oradata/BD01/redo03.log  NO 


Movendo arquivos de dados (data files) no Oracle 12c


No Oracle 12c, além de utilizarmos os métodos mostrados anteriormente, poderemos mover um arquivo de dados de forma ONLINE, ou seja, mesmo estando em uso pelo Oracle, fazendo uso do comando ALTER DATABASE MOVE. Com este método não precisaremos colocar a tablespace OFFLINE, nem mesmo copiar o arquivo utilizando comandos do sistema operacional. O comando ALTER DATABASE MOVE no Oracle 12c é capaz inclusive de criar o diretório da nova localização caso o mesmo não exista, ou seja, ele cria automaticamente os diretórios caso necessário e move o arquivo de dados. No exemplo abaixo, irei mover o arquivo de dados users01.dbf para a nova localização em /oradata2/BD01. Vale a pena salientar que o diretório /oradata2/BD01 não será criado previamente por mim de forma que o Oracle faça esse trabalho.

$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.1.0 Production on Seg Ago 5 19:48:47 2013

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

Conectado a:
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

SQL> alter database move datafile 
  2  '/oradata/BD01/users01.dbf'
  3  to
  4  '/oradata2/BD01/users01.dbf';

Banco de dados alterado.

SQL> select file_id,file_name,tablespace_name
  2    from dba_data_files
  3   where tablespace_name='USERS';

   FILE_ID FILE_NAME                          TABLESPACE_NAME
---------- ---------------------------------- ------------------
         4 /oradata2/BD01/users01.dbf          USERS

No resultado acima, podemos verificar que o arquivo de dados foi movido com sucesso para a nova localização em /oradata2/BD01. Portanto, podemos verificar que o Oracle 12c criou o diretório BD01 em /oradata2 e moveu o arquivo de dados users01.dbf.

Para finalizar, segue abaixo um script que utilizo com uma certa frequência, quando preciso mover todos os arquivos de dados e redo log files de um banco de dados. O script detecta a origem dos arquivos e gera o comando ALTER DATABASE RENAME para todos eles. No exemplo abaixo, a origem dos arquivos é /oradata/BD01 e a nova localização na qual precisa ser informada é /oradata2

SQL> SELECT    'alter database rename file '''
  2         || name
  3         || ''' to ''/oradata2/'
  4         || (SELECT name FROM v$database)
  5         || '/'
  6         || SUBSTR (name, INSTR (name, '/', -1) + 1)
  7         || ''';'
  8            cmd
  9    FROM v$datafile
 10  UNION ALL
 11  SELECT    'alter database rename file '''
 12         || name
 13         || ''' to ''/oradata2/'
 14         || (SELECT name FROM v$database)
 15         || '/'
 16         || SUBSTR (name, INSTR (name, '/', -1) + 1)
 17         || ''';'
 18    FROM v$tempfile
 19  UNION ALL
 20  SELECT    'alter database rename file '''
 21         || member
 22         || ''' to ''/oradata2/'
 23         || (SELECT name FROM v$database)
 24         || '/'
 25         || SUBSTR (member, INSTR (member, '/', -1) + 1)
 26         || ''';'
 27    FROM v$logfile;

CMD
-------------------------------------------------------------------------------------------
alter database rename file '/oradata/BD01/system01.dbf' to '/oradata2/BD01/system01.dbf';
alter database rename file '/oradata/BD01/undotbs01.dbf' to '/oradata2/BD01/undotbs01.dbf';
alter database rename file '/oradata/BD01/sysaux01.dbf' to '/oradata2/BD01/sysaux01.dbf';
alter database rename file '/oradata/BD01/users01.dbf' to '/oradata2/BD01/users01.dbf';
alter database rename file '/oradata/BD01/temp01.dbf' to '/oradata2/BD01/temp01.dbf';
alter database rename file '/oradata/BD01/redo01.log' to '/oradata2/BD01/redo01.log';
alter database rename file '/oradata/BD01/redo02.log' to '/oradata2/BD01/redo02.log';
alter database rename file '/oradata/BD01/redo03.log' to '/oradata2/BD01/redo03.log';