Clonando bases no ORACLE RAC 10G

Eu sempre fui fã do backup feito na mão. Gosto de ter controle do processo, adaptar um script para demandas específicas etc e tal. Mas quando utilizamos o Oracle RAC, em geral estamos utilizando o ASM e neste caso, a unica forma de se fazer backup físico é pelo RMAN.

Clonar uma base guardada em file system é simples. Você copia os datafiles, gera um controlfile, edita ele, faz um PITR e pronto. Mas com ASM você não pode fazer isso. Você vai ter de utilizar o nosso amigo DUPLICATE.

Bom eu queria escrever algo detalhado, explicando cada passo, mas a preguiça me impediu. Então vamos apenas mostrar o cenário do exemplo e mandar bala logo:

  • Oracle RAC 10.2.0.5 com 2 nós.
  • A base a ser clonada é a ‘producao’ com as instâncias ‘producao1′ e ‘producao2′
  • A base que vai ser atualizada/criada com os dados da’producao’ é a base’teste’ cin as instâncias ‘teste1′ e ‘teste2′.
  • As duas bases tem suas instâncias nos dois servidores, ora1 e ora2 utilizando um SO UNIX like.
  • A base ‘produção’ está utilizando o diskgroup ASMGRP01 e ASMGRP02.
  • A base ‘teste’ utiliza apenas o diskgroup ASMGRP03
  • Estou supondo que a base ‘teste’ já existe, e vai ser atualizada. Se a base não existir, o processo muda. Você não precisa apagar a base antes, mas precisa criar o init e os diretórios nos nós. Não vou abordar estes detalhes aqui.

Preparação

Passo1 - Verificar em quais diskgroups os arquivos da base teste estão

export ORACLE_SID=teste
sqlplus "/ as sysdba"
SELECT name FROM v$datafile;
SELECT * FROM v$logfile;
SELECT VALUE FROM v$parameter
  WHERE name = 'control_files';
EXIT

Passo2 - baixar a base no RAC

srvctl stop database -d teste

Passo3 – Apagar a base no ASM

Tome cuidado aqui. Vamos excluir os arquivos dos  diskgroups observados no passo 1.

export ORACLE_SID=+ASM
asmcmd
cd asmgrp03
rm -r teste
exit

Passo 4 – Editar init

Aqui não estou utilizando SPFILE, pois o 10g tem um bug que ocorre com o SPFILE durante o duplicate no 10g)

cd $ORACLE_HOME/dbs
vi initteste.ora
  • Alterar os seguintes parâmetros :
*.control_files                 = '+ASMGRP03'
*.cluster_database              = FALSE
  • Verifique o nome do parâmetro UNDO_TABLESPACE. O nome da tablespace deve ser idêntico ao UNDO da produção. No caso deste duplicate, tive de fazer um ajuste, pois o parâmetro do UNDO estava errado no init.
  • Configure os parâmetros para alterar o diretório de destino. Neste caso, vamos mandar os datafiles e REDO dos diskgroups ASMGRP01 e ASMGRP02 todos para ASMGRP03:
db_file_name_convert   =
    ('+ASMGRP01/producao','+ASMGRP03/teste',
     '+ASMGRP02/producao','+ASMGRP03/teste',
     '+ASMGRP03/producao','+ASMGRP03/teste')
log_file_name_convert  =
    ('+ASMGRP01/producao','+ASMGRP03/teste',
     '+ASMGRP02/producao','+ASMGRP03/teste',
     '+ASMGRP03/producao','+ASMGRP03/teste')
  • Configure o parâmetro para evitar um bug durante o RESETLOGS do DUPLICATE:
_no_recovery_through_resetlogs=TRUE

Passo 5 – Fazer o backup em disco da base de produção

Se já houver um backup recente, pode pular esta etapa. É fundamental que no backup tenha ocorrido um

SQLALTER SYSTEM archive LOG CURRENT’;

No caso abaixo o

backup database plus archivelog

faz isso implicitamente. Se resolver fazer o backup de outra forma, sembre-se disso.

export ORACLE_SID=producao
rman target / <<EOF
backup database plus archivelog;
exit
EOF

Enfim o DUPLICATE

Passo 6 - Realizar o DUPLICATE com o nohup

É importante rodar isto com o nohup para evitar surpresas. Na verdade no próprio backup também é importante. Então eu crio o script:

export ORACLE_SID=teste
rman TARGET sys/sua_senha@producao NOCATALOG AUXILIARY sys/sua_senha <<EOF
RUN
{
    ALLOCATE AUXILIARY CHANNEL aux1 DEVICE TYPE DISK;
    DUPLICATE TARGET DATABASE TO teste;
    RELEASE AUXILIARY CHANNEL aux1;
}
EXIT
EOF

e rodo ele com o comando:

nohup atualizaTeste.sh

Verifique o log do nohup.out e veja se está ok.

tail -f nohup.out

Passo 7 –  Verificar o controlfile gerado:

export ORACLE_SID=teste
sqlplus "/ as sysdba" <<EOF
SELECT value
  FROM v$parameter
  WHERE name = 'control_files';
exit
EOF

Passo 8 – Baixar a base

export ORACLE_SID=teste
sqlplus "/ as sysdba" <<EOF
shutdown immediate
exit
EOF

Passo 9 – Editar novamente o init

Colocar o parâmetro ‘control_files’ com o valor encontrado no passo 7.
Colocar o parâmetro ‘cluster_database’ com o valor TRUE.
Copiar o init editado para o nó 2:

scp initteste01.ora ora2:$ORACLE_HOME/dbs/initteste2.ora

Passo 10 – Subir a base no modo NOARCHIVE

export ORACLE_SID=teste
sqlplus "/ as sysdba" <<EOF
startup mount
ALTER database noarchivelog;
ALTER database open;
EOF

Habilitando a base no 2º nó

Se você chegou aqui, parabéns. Eu demorei um tempinho até fazer isso com sucesso pleno. Mas tem um detalhe importante, ao duplicar a base, apenas um nó é duplicado, o outro nó continua inativo.

Passo 11 – Verificar o UNDO

Verifique se o UNDO dos 2 nós estão presentes.

export ORACLE_SID=teste
sqlplus "/ as sysdba" <<EOF
SELECT tablespace_name, status
  FROM dba_tablespaces
  WHERE contents = 'UNDO';
exit
EOF

Se não estiver, criar para o nó 2

Passo 12 – Criar o REDO para o nó 2

Antes verifique como os logs de REDO estão:

SELECT * FROM v$log; -- olhar o tamanho dos logs
SELECT * FROM v$logfile; -- olhar o destino e quantidade dos logs;

Depois crie os logs para a thread 2 segundo as informações encontradas. Neste caso, serão 4 REDOs, de 512M com um membro por grupo utilizando o diskgroup ASMGRP03.

ALTER DATABASE ADD logfile thread 2 ('+asmgrp03') SIZE 512M;
ALTER DATABASE ADD logfile thread 2 ('+asmgrp03') SIZE 512M;
ALTER DATABASE ADD logfile thread 2 ('+asmgrp03') SIZE 512M;
ALTER DATABASE ADD logfile thread 2 ('+asmgrp03') SIZE 512M;

Passo 13 – Habilitar o nó 2

ALTER database enable thread 2;

Passo 14 – baixar a base

shutdown IMMEDIATE

Passo 15 Verificar se a base está cadastrada no cluster

Como a base em geral está sendo atualizada, este problema não ocorre, pois já deverá estar OK. Mas se você estiver criando uma nova, com certeza vai precisar mexer nisso. De qualquer forma, é sempre bom checar.

cd $CRS_HOME/bin/crs_stat

Tem de aparecer algo como:

NAME=ora.teste.db
TYPE=application
TARGET=OFFLINE
STATE=OFFLINE on ora02
 
NAME=ora.teste.teste1.inst
TYPE=application
TARGET=OFFLINE
STATE=OFFLINE on ora01
 
NAME=ora.teste.teste2.inst
TYPE=application
TARGET=OFFLINE
STATE=OFFLINE on ora02

Se não aparecerem as linhas, cadastrar a base no cluster:

srvctl add database -d teste -o $ORACLE_HOME
srvctl add instance -d teste -i  teste1 -n ora01
srvctl add instance -d teste -i  teste2 -n ora02

Legenda:

  • -d = nome da base
  • -o = local do $ORACLE_HOME
  • -I = instância
  • -n = mome do servidor onde a instância está.

Passo 16 – Subir a base nos 2 nós pelo cluster

srvctl start database -d teste

Passo 17 – Verificar nos 2 nós

Você pode ignorar alguns erros logo depois do recover, no momento do resetlogs, mas fique atendo a todo o restante.

Na verdade, durante todo o processo é uma boa idéia ir acompanhando o alerta.

E se a base de testes não está no mesmo servidor, não está em RAC e ASM?

Bom, aí meu caro, você vai ter que tomar alguns cuidados adicionais:

  1. Você vai rodar o duplicate no servidor onde está a base de testes. Então precisa configurar o tnsnames.ora para a produção neste servidor. Configure para se conectar em um nó apenas.
  2. Você vai ter que tomar mais cuidado ainda no FILE_NAME_CONVERT.
  3. Você vai ter de criar os logs de REDO no próprio comando DUPLICATE.
  4. Você provavelmente vai pegar o backup da fita, então pegue a string de conexão do backup, mas altere o canal (channel em inglês) para auxiliar.
  5. Você vai poder apagar o tablespace UNDO do nó 2 que você não vai precisar.
  6. Você vai ter de tomar um mega cuidado com os archives. Quando o DUPLICATE roda com a produção e testes no mesmo servidor, nós configuramos o archive_dest da base teste igual ao da base de produção e não nos preocupamos mais com isso. Agora estão em servidores diferentes…

Exemplo 1

Ao invés de ficar aqui explicando tudo novamente, vou mostrar 2 exemplos de DUPLICATE de uma base em RAC para uma base de testes sem RAC:

rman TARGET sys/sua_senha@producao NOCATALOG AUXILIARY / <<EOF
run {
allocate auxiliary channel 'dev_0' TYPE 'sbt_tape'
parms 'SBT_LIBRARY=/opt/omni/lib/libob2oracle8_64bit.so,
    ENV=(OB2BARTYPE=Oracle8,
    OB2APPNAME=producao01,
    OB2BARLIST=PRODUCAO_DIARIO)';
send device TYPE 'sbt_tape' 'OB2BARHOSTNAME=ora1';
     DUPLICATE TARGET DATABASE TO teste
	UNTIL TIME "TRUNC(SYSDATE) + 3/24 + 40/1440"
        DB_FILE_NAME_CONVERT = (
		'+ASMGRP01/producao/datafile/', '/u01/oradata/teste/',
		'+ASMGRP01/producao/tempfile/', '/u01/oradata/teste/')
        LOGFILE
            GROUP 1 ('/u01/oradata/teste/redo01.log') SIZE 128M REUSE,
            GROUP 2 ('/u01/oradata/teste/redo02.log') SIZE 128M REUSE,
            GROUP 3 ('/u01/oradata/teste/redo03.log') SIZE 128M REUSE,
	    GROUP 4 ('/u01/oradata/teste/redo04.log') SIZE 128M REUSE;
RELEASE auxiliary channel 'dev_0';
}
EXIT
EOF

Algumas observações:

  • Este backup está vindo de uma fita, então estou passando alguns parâmetros específicos para um software de backup, que não vem ao caso aqui. Mas fique atento que sem os dados de um log de backup para fita da sua ferramenta de backup, você não tem como pegar os parâmetros em “params” logo no começo;
  • O parâmetro “UNTIL TIME” está ajustado para um horário bem específico, referente à janela de backup da unidade de fitas, e é um horário depois do término do backup normal e antes do horário em que eu rodei um backup dos archives na mão;
  • Eu coloquei o DB_FILE_NAME_CONVERT aqui no DUPLICATE e não no INIT. Se estiver configurado este parâmetro no init também, vale o do DUPLICATE.
  • O LOGFILE é um dos segredos do sucesso. Ao invés de configurar o LOG_FILE_NAME_CONVERT, eu digo logo como ele tem de ficar. Me poupa o trabalho de arrumar isso depois.
  • Você não precisa, mas seria bom remover o tablespace de UNDO do nó 2, que não estará sendo utilizado.

Exemplo 2

Agora vou mostrar um script que utilizo para uma base D-1. Ou seja, uma base que faz o restore do backup da produção todo dia:

export ORACLE_HOME=/opt/app/oracle/product/10.2.0/db_1
export PATH=/usr/local/bin:/bin:/usr/bin:$ORACLE_HOME/bin
export ORACLE_SID=teste
sqlplus "/ as sysdba" &lt;&lt;EOF
shutdown abort
startup nomount
EOF
 
rm -fr /u01/oradata/teste/
 
rman TARGET sys/sua_senha@producao NOCATALOG AUXILIARY sys/sua_senha@teste <<EOF
CONFIGURE DEVICE TYPE SBT_TAPE PARALLELISM 1 BACKUP TYPE TO BACKUPSET;
CONFIGURE CHANNEL DEVICE TYPE 'SBT_TAPE' PARMS     
    'ENV=(OB2BARTYPE=Oracle8,
     OB2APPNAME=producao1,
     OB2BARLIST=PRODUCAO FULL)';
CONFIGURE DEFAULT DEVICE TYPE TO 'SBT_TAPE';
RUN
{
     DUPLICATE TARGET DATABASE TO teste
	UNTIL TIME "TRUNC(SYSDATE) + 2/24 + 50/1440"
        OPEN RESTRICTED
        DB_FILE_NAME_CONVERT = (
		'+ASMGRP01/producao/datafile/', '/u01/oradata/teste/',
		'+ASMGRP02/producao/datafile/', '/u01/oradata/teste/',
		'+ASMGRP01/producao/tempfile/', '/u01/oradata/teste/')
        LOGFILE
            GROUP 1 ('/u01/oradata/teste/redo01.log') SIZE 128M REUSE,
            GROUP 2 ('/u01/oradata/teste/redo02.log') SIZE 128M REUSE,
            GROUP 3 ('/u01/oradata/teste/redo03.log') SIZE 128M REUSE,
	    GROUP 4 ('/u01/oradata/teste/redo04.log') SIZE 128M REUSE;
}
EXIT
EOF

Comentários:

  • Este script é uma forma simplificada de um script que fica agendado no crontab e roda toda noite.
  • Note que a base abre no modo restrito.

Exemplo 3

Agora um exemplo diferente, um clone de uma base sem ASM e sem RAC. O backup via RMAN é feito toda a noite para disco e utilizamos este backup para o clone. Só que a base de produção e testes estão em servidores diferentes.

Para isso criei um arquivo no servidor de produção para ajudar em ~/script/copia_backup.sh com:

find /u01/backup/producao/ -name 'backup_db_*' -daystart -mtime -1
 -exec rsync -av  \{\} 192.168.0.x:/u01/backup/producao  \;

Este arquivo serve só para copiar o backup da produção para o servidor de teste.

Agora vamos ao script para a atualização da base:

export ORACLE_SID=teste
export ORACLE_SID_ORIGEM=producao
export ORACLE_HOME=/opt/app/oracle/product/10.2.0/db_1
export PATH=$PATH:$ORACLE_HOME/bin
 
sqlplus "/ as sysdba" << EOF
shutdown abort
exit 0
EOF
 
if [ $? -ne 0 ]; then
  echo " ERRO: Banco de dados invalido !"
  exit;
fi
 
echo "Limpando a área de backup"
find /u01/backup/$ORACLE_SID_ORIGEM/ 'backup_db_*' -mtime +2 -exec rm -fv \{\} \;
 
echo "Limpando a área de archive"
find /u01/archive/$ORACLE_SID_ORIGEM/ '*.arc' -mtime +1 -exec rm -fv \{\} \;
 
echo "Backup dos archives"
rman target sys/sua_senha@$ORACLE_SID_ORIGEM <<EOF
backup archivelog all;
exit
EOF
 
echo "Copiando o backup da produção"
ssh 192.168.0.y ~/script/copia_backup.sh
 
echo "Limpando a base $ORACLE_SID"
rm -Rfv /u01/oradata/${ORACLE_SID}/*.dbf
 
echo "Subindo a base $ORACLE_SID no modo NOMOUNT"
sqlplus  "/ as sysdba" << EOF
startup nomount;
exit
EOF
 
echo "Iniciando o DUPLICATE"
rman target 'sys/sua_senha@$ORACLE_SID_ORIGEM' auxiliary / <<EOF
CONFIGURE CHANNEL DEVICE TYPE DISK FORMAT
    '/u01/backup/$ORACLE_SID_ORIGEM/backup_db_%U_%T' MAXPIECESIZE 8192 M;
run {
  allocate auxiliary channel dsk11 device type disk;
  duplicate target database to $INSTANCE_NAME until time "sysdate - 12/24";
  release auxiliary channel dsk11;
}
exit
EOF
 
sqlplus  "/ as sysdba" << EOF
shutdown immediate;
startup mount;
ALTER DATABASE NOARCHIVELOG;
ALTER DATABASE OPEN;
exit
EOF

Comentários:

  • A troca de chaves SSH foi realizada para podermos rodar comandos SSH sem senha;
  • O backup via RMAN tem de ser copiado do servidor de produção para o servidor de teste para exatamente o mesmo diretório.
  • Sempre cuidado com os archives. Fazemos um backup via RMAN dos archives e copiamos ele junto antes de iniciar o DUPLICATE;
  • Colocamos a base no modo noarchive, algumas pessoas fazem isso em bases de testes.

 

Referências

 

O guro Oracle Thomas Kyte mostra os argumentos para usar o PostgreSQL

Sim, é verdade! Estava lendo uma thread no blog do respeitável Thomas Kyte sobre comparações do Oracle com outros bancos de dados aqui.  Claro, o Thomas não cita o PostgreSQL em momento algum, ele cita apenas o Informix, Ingres, Sybase, DB2 e o MS SQL Server.

Bom, vale a pena ler o artigo. Você verá que nos comentários o PostgreSQL começa a aparecer, e com boas avaliações. Mais interessante ainda é que o Thomas ignora ao máximo o PostgreSQL nos comentários. Claro, ele não é estúpido, não seria inteligente nem diplomático da parte dele. O máximo que ele chega a citar é que a Oracle tem o XE, que quem conhece sabe que é algo limitado e certamente foi lançado (a exemplo do que fez a MicroSoft e IBM) para retardar o avanço dos bancos de dados livres.

O post é de 2001 e os comentários perduram até 2010. De qualquer forma, fica claro, que os mesmos argumentos que ele utiliza para mostrar a superioridade do Oracle, serviriam para o PostgreSQL…. vale a pena ler.

Comparison between Oracle and Others

SQL para DBAs

Quem me conhece, sabe que eu sempre implico com os vícios dos DBAs que iniciam a sua carreira no desenvolvimento. Mas os DBAs que começam como sysadmin também tem seus vícios. Um dos pontos fracos costuma ser a baixa familiaridade com o SQL.

Bom, vou mostrar aqui um alguns exemplos de SQLs que tive que desenvolver recentemente. Alguns foram feitos para ajudar a equipe de desenvolvimento, outros para resolverem problemas de performance e tem um que foi feito por solicitação dos  sysadmins. Na verdade, eu deveria mesmo é organizar estes scripts de uma forma lógica, mas são coisas realmente simples para merecerem tanto coidado…

Então chega de blá, blá, blá e vamos a eles:

  • Conversão de NOT IN em NOT EXISTS numa carga de dados. É muito comum você precisar carregar periodicamente uma tabela com os dados de uma outra. Isso ocorre muito na integração entre sistemas. Eu sei, eu sei, usar visões materializadas pode ser um processo bem mais inteligente. Infelizmente nem sempre estas decisões estão na nossa mão. Vejamos o caso:
INSERT INTO tabela2
    SELECT * FROM tabela1 t1
        WHERE campo_pk NOT IN(SELECT campo_pk FROM tabela_origem);

Você tem de carregar a tabela2, com os dados da tabela1 sem repetir os dados que já estejam lá. Utilizar o NOT IN é bem intuitivo, mas quando a tabela2 é grande, se torna um pesadelo, pois a subconsulta uma vez para cada registro na tabela1 e não consegue utilizar índices para isso. Um verdadeiro pesadelo. Então vamos a solução clássica:

INSERT INTO tabela2
    SELECT * FROM tabela1 t1
        WHERE NOT EXISTS (SELECT NULL FROM tabela2 t2 WHERE t2.campo_pk = t1.campo_pk );
  • Pegar apenas o primeiro e o último nome de um campo com nome completo:
SELECT
        SUBSTR(nome,0,INSTR(TRIM(nome),' ')) primeiro_nome
        SUBSTR(nome,INSTR(TRIM(nome),' ',-1) + 1) ultimo_nome,
    FROM tabela_cadastro;
  • Limpar caracteres inválidos de telefones. Sim, tem muita gente que gosta de armazenar telefones, CEP, RG, CPF com caracteres não numéricos. Não é o correto, mas muita gente faz. Então, para fazer uma limpeza, vamos utilizar um pouco de expressões regulares, que são pouco exploradas por muitos DBAs e até mesmo desenvolvedores:
SELECT SUBSTR(LTRIM(regexp_replace(telefone,'[^0-9]'),0),0,10) fone_tratado
    FROM tabela_cadastro;
  • Selecionar apenas e-mails válidos. Eu sei, a validação deveria ser feita na carga dos dados, mas nem sempre acontece. Aqui, mais um caso clássico de uso de expressões regulares:
SELECT TRIM(email) email
    FROM tabela_cadastro
    WHERE REGEXP_LIKE (TRIM(email),'^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$','i');
  • Agora uma forma de saber se o número de sessões num nó do Oracle RAC estão relativamente bem balanceadas. Aqui estou utilizando funções de janelas (que já abordamos aqui antes, mas com o Postgres), algo muito bacana, utilizado exaustivamente em consultas complexas com BI:
SELECT
        i.instance_name,
        l.sessions_current,
        ROUND(l.sessions_current * COUNT(l.inst_id) OVER () / SUM(l.sessions_current) OVER (),2) var
    FROM gv$license l, gv$instance i
    WHERE l.inst_id = i.inst_id;

Aqui, um valor entre digamos 0,9 e 1,1 para o campo VAR significa que o nó está relativamente bem balanceado. Esta consulta foi utilizada para uma ferramenta de monitoramento do Oracle RAC.

OBS: Os scripts aqui foram escritos e testados em Oracle 10g e 11g, sorry.

Rodando scrips de um usuário específico com segurança

A história é recorrente: o desenvolvedor mandou para você  um mega script (ou um pacote com dezenas deles) para rodar no banco de dados Oracle. Em 99% dos casos o desenvolvedor vai dizer que o script precisa ser executado com você conectado no banco de dados utilizando o usuário XYZ, que é o dono dos objetos que vão ser criados/alterados/apagados.

Aí o que o DBA faz? Pega a senha do usuário numa listinha “cuidadosamente” guardada e roda o script em questão. Então, pare e pense: qual é o problema nisso?

  • Você tem que guardar a senha dos usuários da base. Isso vai lhe levar a uma das seguintes situações:
    • Você vai colocar senhas fáceis (como o mesmo nome do usuário ou a mesma senha para todos usuários), o que representa uma brecha de segurança;
    • Você vai anotar num papel, txt ou algo do tipo todas as senhas. E esse papel, txt, etc não vai estar guardado num cofre, pois você precisa disso com frequência. Nova falha de segurança.
    • Você vai criar um banco de dados para guardar as senhas… e ter mais uma base para administrar, gênio.
    • Você vai deixar o próprio desenvolvedor/fornecedor rodar o script para você. Tá louco? Jamais faça isso num ambiente de produção!!!
  • Você vai ter que conceder permissões para este usuário além do necessário. Se precisa criar uma tabela, precisa conceder permissão de CREATE TABLE. Precisa criar uma sequência? CREATE VIEW. E por aí vai. Não é raro o DBA usar logo uma daquelas mega permissões como DBA, RESOURCE, etc. Afinal, perder tempo com segurança é coisa de DBA chato, certo?

Ok, você é um DBA esperto… muitos anos de praia, já sabe que existem outras alternativas:

  • Rodar o script como DBA, mas antes abrir o script num editor e sair colocando o nome do esquema antes do nome do objeto. Isso dá certo, mas dá muito trabalho. Não é algo que se possa automatizar sem cometer alguns erros. Acredite, há casos em que isso pode levar horas e está sujeito a muitos erros.
  • Você pode pegar a senha criptografada em DBA_USERS, alterar a senha do usuário, rodar o script e depois voltar a senha original. É também uma forma de bloquear o acesso deste usuário enquanto você está atualizando os objetos. Claro que idealmente um usuário dono de objetos não deveria nunca ser utilizado para acesso pela aplicação… mas não é o que os desenvolvedores tem a mania de fazer. Por mim esse usuário não deveria sequer ter permissão para se conectar na base. Eu realmente não acho esta uma solução elegante, e você precisa manter o usuário com um monte de privilégios desnecessários da mesma forma que antes. Já vi também pessoas colocando permissões de DBA enquanto rodam o script e depois que terminam removem a permissão. Não, não é uma boa idéia. Você também tem de anotar com cuidado a senha criptografada para alterar a senha de volta para a original qualquer caracter bizarro que você erre, já era. Por fim você  tem conhecer o impacto de alterar a senha, mesmo que provisóriamente. Experimenta por exemplo trocar a senha do usuário APPS no EBS para ver o que acontece…

Então qual é o procedimento que 10 entre 10 DBAs experientes utilizam? Simples:

ALTER SESSION SET current_schema=foo;

Simples assim. O esquema padrão passa a ser foo. Todos os objetos criados, alterados e apagados serão neste esquema.

Mas calma, existem alguns problemas sim: alguns comandos não trabalham bem com outro CURRENT_SCHEMA. Um deles é a criação de DB Links. Não é possível criar um DB Link para outro esquema, você tem de estar realmente conectado com o usuário em questão para criar este DB Link. Claro que você não cria DB Links no ambiente de produção com frequência (pela sua sanidade mental, eu espero que não). DB Links públicos não tem esse problema, é claro. Outro problema é a criação de JOBs com o DBMS_JOB. Você pode utilizar o DBMS_IJOB, para contornar este problema ou pelo bem da humanidade migrar para o Scheduler.

Bom, de qualquer forma é sempre obrigação do DBA revisar os scripts, verificar os parâmetros de Storage, etc.

OBS: Este post é dedicado a um DBA velho de guerra que ainda guarda velhos hábitos dos tempos que era desenvolvedor e insiste em me trazer mais problemas do que soluções.

OBS2: No PostgreSQL existe uma não conformidade com o padrão SQL (você pode trabalhar no padrão se quiser, mas não vejo vantagem) que permite que os usuários e os esquemas não sejam diretamente relacionados.  Então esta cultura ruim do Oracle não é tão comum entre os desenvolvedores/DBAs do PostgreSQL. Além disso o PostgreSQL tem o SEARCH_PATH que é mais refinado que o CURRENT_SCHEMA, e os comandos DDL permitem a troca do dono de qualquer objeto (que não seja um objeto de sistema, claro).

  • ATUALIZAÇÃO (em 16/02/2011):

Para aqueles que ainda precisam utilizar o DBMS_JOB e precisam criar JOBs para outro usuário, segue uma dica de como fazer, utilizando o DBMS_IJOB.SUBMIT. O DBMS_IJOB.REMOVE é fácil de utilizar, mas para criar um novo JOB, você vai precisar passar todos os parâmetros, como em:

DECLARE
    job_num NUMBER;
    nlsvar varchar2(4000);
    envvar raw(32);
BEGIN
    SELECT nls_env,misc_env
        INTO nlsvar,envvar
        FROM dba_jobs
        WHERE
            rownum&lt;2 AND
            nls_env IS NOT NULL AND
            misc_env IS NOT NULL;
    SELECT MAX(job)+1
        INTO job_num
        FROM dba_jobs;
    sys.dbms_ijob.submit(         job=&gt;job_num,
        luser=&gt;'MEU_USUARIO',
        puser=&gt;'MEU_USUARIO',
        cuser=&gt;'MEU_USUARIO',
        what=&gt;'meu_usuario.roda_procedure_xyz;',
        next_date=&gt;TRUNC(SYSDATE),
        INTERVAL=&gt;'TRUNC(SYSDATE) + 1',
        broken=&gt;FALSE,
        nlsenv=&gt;nlsvar,
        env=&gt;envvar);
    dbms_output.put_line(job_num);
END;
/
COMMIT;

Já aqueles que desejam criar um DB LINK privado, a única alternativa é recorrer ao DBMS_SYS_SQL, que assim como o DBMS_IJOB não está na documentação oficial:

DECLARE
    uid NUMBER;
    sqltext varchar2(1000) := 'CREATE DATABASE LINK test_dblink
CONNECT TO dblink_user IDENTIFIED BY dblink_user_password
USING ''nome_da_base_no_tnsnames''';
    myint INTEGER;
BEGIN
    SELECT user_id INTO uid FROM all_users WHERE username LIKE 'SCOTT';
    myint:=sys.dbms_sys_sql.open_cursor();
    sys.dbms_sys_sql.parse_as_user(myint,sqltext,dbms_sql.native,UID);
    sys.dbms_sys_sql.close_cursor(myint);
END;
/

Note que você pode utilizar o DBMS_SYS_SQL para rodar qualquer comando SQL como outro usuário. No exemplo, estamos utilizando o usuário SCOTT.

Habilitar e desabilitar todos os JOBS do Oracle

Ao migrar uma base via DUMP (seja com expdp/impdp ou exp/imp) ou realizar algumas manutenções como atualizações de aplicação, é sempre uma boa idéia parar todos os JOBS que estão rodando no banco antes de começar o trabalho. Não basta matar os processos ativos(ou mesmo reiniciar a base), você tem de cuidar para que os JOBs não sejam iniciados no meio do processo e este é um erro muito comum.

Para facilitar este trabalho criei o pequeno script abaixo que cria dois arquivos, um para desabilitar os JOBs e outro para habilitar novamente. Outro erro comum é desabilitar os JOBs e depois não saber quais estavam ativos antes, na hora de habilita-los.

Um último adendo, aqui eu estou utilizando o DBMS_IJOB (que não está bem documentado, mas permite trabalhar com jobs de outros usuários) e o DBMS_SCHEDULER para aqueles que utilizam este recurso introduzido no 10g.

sqlplus -s / AS sysdba <<EOF
SET heading off
SET trimspool ON
SET term off
SET echo off
SET feed off
 
SPOOL 'jobs_to_broken.sql'
SELECT 'EXEC dbms_ijob.broken(' || job || ',TRUE);' 
    FROM dba_jobs 
    WHERE broken = 'N';
 
SELECT 'EXEC dbms_scheduler.disable (''' || owner || '.' || job_name || ''');' 
    FROM dba_scheduler_jobs 
    WHERE enabled = 'TRUE'
    ORDER BY owner, job_name;
 
SPOOL OFF
SPOOL 'jobs_to_run.sql'
SELECT 'EXEC dbms_ijob.broken(' || job || ',FALSE);' 
    FROM dba_jobs 
    WHERE broken = 'N';
 
SELECT 'EXEC dbms_scheduler.enable (''' || owner || '.' || job_name || ''');' 
    FROM dba_scheduler_jobs 
    WHERE enabled = 'TRUE'
    ORDER BY owner, job_name;
SPOOL OFF
-- rodar '@jobs_to_broken' para desabilitar os JOBs'
-- rodar '@jobs_to_run' para restaurar os JOBs
EOF