Monitoramento básico de objetos no Oracle

Bom, todo DBA que se prese deve acompanhar o crescimento da base conforme o tempo. Prever o espaço em disco necessário nos próximos meses, saber qual época ocorre maior crescimento da base. Acompanhar de perto objetos críticos que ocupam mais espeço, etc. É claro que a própria Oracle e outros fornecedores possuem ferramentas bastente sofisticadas para fazer algumas destas coisas. É bem verdade que boa parte delas faz em maior ou menor grau algo bem semelhante ao que vou mostrar adiante. No entanto, eu gosto da minha solução, pois eu consigo entendê-la facilmente e modificar para necessidades específicas. No mínimo é um bom exercício de aprendizado.

Vejamos alguns requisitos que eu montei:

  • Ser compatível com pelo menos o Oracle 8 em diante;
  • Armazenar todas informações num tablespace separado, para que a coleta de dados não influencie nos demais tablespaces;
  • Utilizar um esquema separado para a criação de todos os objetos envolvidos. O usuário em questão deverá ser bloqueado e ter o mínimo de privilégios necessários;
  • Criar uma tabela para registrar a data de duração no disparo de cada script e outra para os erros que por ventura venham a ocorrer;
  • Coletar as seguintes informações com as respectivas periodicidades:
    • Dados sobre o tamanho dos tablespaces uma vez por mês;
    • Dados sobre a quantidade e tipo de objetos por esquema uma vez por dia, atualizando apenas as mudanças ocorridas;
    • Nome dos objetos inválidos auma vez por dia, atualizando apenas as mudanças ocorridas;
    • Tamanhode objetos que ocupem mais de 64MB ou tenham mais de 50 extents ou mais de um milhão de registros uma vez por semana, atualizando apenas as mudanças ocorridas;

Objetos

CREATE TABLESPACE 'DBA_LOG_DADOS'
  DATAFILE '/u01/oradata/nome_da_base/dba_log_dados_01.dbf'
  SIZE 100M LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;
 
CREATE USER dba_log IDENTIFIED BY dba
    DEFAULT TABLESPACE dba_log_dados
    QUOTA UNLIMITED ON dba_log_dados
    ACCOUNT LOCK;
 
GRANT CREATE PROCEDURE TO dba_log;
GRANT CREATE TABLE TO dba_log;
 
-- Executar como SYSDBA
GRANT SELECT ON dba_objects TO dba_log;
GRANT SELECT ON dba_segments TO dba_log;
GRANT SELECT ON dba_data_files TO dba_log;
GRANT SELECT ON dba_free_space TO dba_log;
GRANT SELECT ON dba_tables TO dba_log;
 
CREATE SEQUENCE dba_log.log_seq;
 
CREATE TABLE dba_log.log(
    id_log      NUMBER(10),
    rotina      varchar2(100),
    usuario     varchar2(30) DEFAULT USER,
    inicio      DATE DEFAULT SYSDATE,
    fim         DATE,
    CONSTRAINT  log_pk PRIMARY KEY(id_log)
);
 
CREATE TABLE dba_log.erros (
    id_log      NUMBER(10),
    cod_erro    NUMBER(10),
    mensagem     varchar2(64),
    DATA        TIMESTAMP DEFAULT SYSTIMESTAMP
);
 
CREATE TABLE dba_log.tablespace (
    nome        varchar2(30),
    maximo      NUMBER(8) NOT NULL,
    alocado     NUMBER(8) NOT NULL,
    utilizado   NUMBER(8) NOT NULL,
    livre       NUMBER(8) NOT NULL,
    DATA        DATE DEFAULT SYSDATE,
    CONSTRAINT tablespaces_pk PRIMARY KEY (nome,DATA)
);
 
CREATE OR REPLACE PROCEDURE dba_log.tablespace_load AS
  v_log_seq NUMBER(10);
  v_code NUMBER(10);
  v_errm varchar2(64);
BEGIN
 
  SELECT dba_log.log_seq.NEXTVAL INTO v_log_seq FROM dual;
  INSERT INTO dba_log.log (id_log, rotina) VALUES (v_log_seq,'tablespace_load');
 
  INSERT INTO dba_log.tablespace (nome, maximo, alocado, utilizado, livre)
    SELECT
      u.tablespace_name,
      m.maximo,
      m.alocado,
      u.utilizado,
      l.livre
      FROM
        (SELECT tablespace_name, CEIL (SUM (bytes) / 1048576) utilizado
           FROM dba_segments
           GROUP BY tablespace_name) u,
        (SELECT
           tablespace_name,
           CEIL (SUM (bytes) / 1048576) alocado,
           CEIL (SUM (DECODE (autoextensible, 'NO', bytes, maxbytes)) / 1048576) maximo
           FROM dba_data_files
           GROUP BY tablespace_name) m,
        (SELECT
           tablespace_name,
           CEIL (SUM (bytes) / 1048576) livre
           FROM dba_free_space
           GROUP BY tablespace_name) l
      WHERE
        l.tablespace_name = u.tablespace_name AND
        l.tablespace_name = m.tablespace_name
    ;
  UPDATE dba_log.log SET fim = SYSDATE WHERE id_log = v_log_seq;
  COMMIT;
 
  EXCEPTION
    WHEN OTHERS THEN
      v_code := SQLCODE;
      v_errm := SUBSTR(SQLERRM, 1 , 64);
      INSERT INTO dba_log.erros (id_log, cod_erro, mensagem) VALUES (v_log_seq, v_code, v_errm);
END;
/
 
CREATE TABLE dba_log.objeto_qt (
    tipo        varchar2(19),
    esquema     varchar2(30),
    STATUS      varchar2(7),
    qt          NUMBER(5) NOT NULL,
    DATA        DATE DEFAULT SYSDATE,
    CONSTRAINT objeto_qt_pk PRIMARY KEY (tipo, esquema, STATUS, DATA)
);
 
CREATE OR REPLACE PROCEDURE dba_log.objeto_qt_load AS
  v_log_seq NUMBER(10);
  v_code NUMBER(10);
  v_errm varchar2(64);
BEGIN
  SELECT dba_log.log_seq.NEXTVAL INTO v_log_seq FROM dual;
  INSERT INTO dba_log.log (id_log, rotina) VALUES (v_log_seq,'objeto_qt_load');
 
  INSERT INTO dba_log.objeto_qt (tipo, esquema, STATUS, qt)
    SELECT b.tipo, b.esquema, b.STATUS, b.qt
      FROM
        (SELECT object_type tipo, owner esquema, STATUS FROM dba_objects
           MINUS
           SELECT tipo, esquema, STATUS FROM dba_log.objeto_qt) a,
        (SELECT object_type tipo, owner esquema, STATUS, COUNT(*) qt
           FROM dba_objects
           GROUP BY owner, object_type, STATUS) b
      WHERE
        a.tipo = b.tipo AND
        a.esquema = b.esquema AND
        a.STATUS = b.STATUS
      ORDER BY esquema, tipo, STATUS
   ;
 
  INSERT INTO dba_log.objeto_qt (tipo, esquema, STATUS, qt)
    SELECT o.tipo, o.esquema, o.STATUS, o.qt
      FROM
        dba_log.objeto_qt q,
        (SELECT object_type tipo, owner esquema, STATUS, COUNT(*) qt
           FROM dba_objects
           GROUP BY owner, object_type, STATUS) o,
        (SELECT tipo, esquema, STATUS, MAX(DATA) DATA
           FROM dba_log.objeto_qt
           GROUP BY tipo, esquema, STATUS) d
      WHERE
        o.tipo = q.tipo AND
        o.tipo = d.tipo AND
        o.esquema = q.esquema AND
        o.esquema = d.esquema AND
        o.STATUS = q.STATUS AND
        o.STATUS = d.STATUS AND
        q.DATA = d.DATA AND
        o.qt != q.qt
        ORDER BY o.esquema, o.tipo, o.STATUS
  ;
  UPDATE dba_log.log SET fim = SYSDATE WHERE id_log = v_log_seq;
  COMMIT;
 
  EXCEPTION
    WHEN OTHERS THEN
      v_code := SQLCODE;
      v_errm := SUBSTR(SQLERRM, 1 , 64);
      INSERT INTO erros (id_log, cod_erro, mensagem) VALUES (v_log_seq, v_code, v_errm);
END;
/
 
CREATE TABLE dba_log.objeto_invalido (
    tipo        varchar2(19),
    esquema     varchar2(30),
    nome        varchar2(128),
    DATA        DATE DEFAULT SYSDATE,
    CONSTRAINT objeto_invalido_pk PRIMARY KEY (tipo, esquema, nome, DATA)
);
 
CREATE OR REPLACE PROCEDURE dba_log.objeto_invalido_load AS
  v_log_seq NUMBER(10);
  v_code NUMBER(10);
  v_errm varchar2(64);
 
BEGIN
  SELECT dba_log.log_seq.NEXTVAL INTO v_log_seq FROM dual;
  INSERT INTO dba_log.log (id_log, rotina) VALUES (v_log_seq,'objeto_invalido_load');
  INSERT INTO dba_log.objeto_invalido (tipo, esquema, nome)
    SELECT object_type tipo, owner esquema, object_name nome
      FROM dba_objects
      WHERE STATUS != 'VALID'
    MINUS
    SELECT tipo, esquema, nome FROM dba_log.objeto_invalido
  ;
  UPDATE dba_log.log SET fim = SYSDATE WHERE id_log = v_log_seq;
  COMMIT;
 
  EXCEPTION
    WHEN OTHERS THEN
      v_code := SQLCODE;
      v_errm := SUBSTR(SQLERRM, 1 , 64);
      INSERT INTO dba_log.erros (id_log, cod_erro, mensagem) VALUES (v_log_seq, v_code, v_errm);
END;
/
 
CREATE TABLE dba_log.objeto_tamanho (
    tipo        varchar2(19),
    tablespace  varchar2(30),
    esquema     varchar2(30),
    nome_part   varchar2(112),
    tamanho     NUMBER(8),
    extents     NUMBER(5),
    num_reg     NUMBER(10),
    DATA        DATE DEFAULT SYSDATE,
    CONSTRAINT objetos_tamanho_pk PRIMARY KEY (tipo, esquema, nome_part, DATA)
);
 
CREATE OR REPLACE PROCEDURE dba_log.objeto_tamanho_load AS
  v_log_seq NUMBER(10);
  v_code NUMBER(10);
  v_errm varchar2(64);
 
BEGIN
 
  SELECT dba_log.log_seq.NEXTVAL INTO v_log_seq FROM dual;
  INSERT INTO dba_log.log (id_log, rotina) VALUES (v_log_seq,'objeto_tamanho_load');
 
  INSERT INTO dba_log.objeto_tamanho 
    (tipo, tablespace, esquema, nome_part, tamanho, extents, num_reg)
    SELECT b.tipo, b.tablespace, b.esquema, b.nome_part, b.tamanho, b.extents, b.num_reg
    FROM
      (SELECT
         segment_type tipo,
         owner esquema,
         NVL2(partition_name, segment_name || '/' || partition_name, segment_name) nome_part
         FROM dba_segments
      MINUS
      SELECT tipo, esquema, nome_part FROM dba_log.objeto_tamanho) a,
      (SELECT
        s.segment_type tipo,
        s.tablespace_name tablespace,
        s.owner esquema,
        NVL2(s.partition_name, s.segment_name || '/' || s.partition_name, s.segment_name) nome_part,
        CEIL(s.bytes/1048576) tamanho,
        s.extents,
        t.num_rows num_reg
        FROM
          dba_segments s,
          dba_tables t
       WHERE
         (s.bytes > 67108864 OR s.extents > 50 OR t.num_rows > 1000000) AND
          s.owner = t.owner (+)AND
          s.segment_name = t.TABLE_NAME (+)) b
    WHERE
      a.tipo = b.tipo AND
      a.esquema = b.esquema AND
      a.nome_part = b.nome_part
  ;    
 
  INSERT INTO dba_log.objeto_tamanho 
    (tipo, tablespace, esquema, nome_part, tamanho, extents, num_reg)
    SELECT o.tipo, o.tablespace, o.esquema, o.nome_part, o.tamanho, o.extents, o.num_reg
      FROM
        dba_log.objeto_tamanho l,
        (SELECT tipo, esquema, nome_part, MAX(DATA) DATA
          FROM dba_log.objeto_tamanho
          GROUP BY tipo, esquema, nome_part) d,
        (SELECT
          s.segment_type tipo,
          s.tablespace_name tablespace,
          s.owner esquema,
          NVL2(s.partition_name, s.segment_name || '/' || s.partition_name, s.segment_name) nome_part,
          CEIL(s.bytes/1048576) tamanho,
          s.extents,
          t.num_rows num_reg
          FROM
            dba_segments s,
            dba_tables t
          WHERE
            (s.bytes > 67108864 OR s.extents > 50 OR t.num_rows > 1000000) AND
            s.owner = t.owner (+)AND
            s.segment_name = t.TABLE_NAME (+)) o
      WHERE
        l.tipo = d.tipo AND
        l.tipo = o.tipo AND
        l.esquema = d.esquema AND
        l.esquema = o.esquema AND
        l.nome_part = d.nome_part AND
        l.nome_part = o.nome_part AND
        l.DATA = d.DATA AND
        (o.tamanho != CEIL(l.tamanho) OR l.extents != o.extents OR l.num_reg != o.num_reg)
      ORDER BY o.esquema, o.tablespace, o.tipo DESC
  ;
  UPDATE dba_log.log SET fim = SYSDATE WHERE id_log = v_log_seq;
  COMMIT;
 
  EXCEPTION
    WHEN OTHERS THEN
      v_code := SQLCODE;
      v_errm := SUBSTR(SQLERRM, 1 , 64);
      INSERT INTO dba_log.erros (id_log, cod_erro, mensagem) VALUES (v_log_seq, v_code, v_errm);
END;
/

Agendamento

Se você estiver utilizando o Oracle 10g ou superior, deve preferir usar o SCHEDULER:

BEGIN
 
  DBMS_SCHEDULER.CREATE_WINDOW(
    window_name=>'SYS.MONTH_START_WINDOW',
    resource_plan=>'SYSTEM_PLAN',
    start_date=>SYSTIMESTAMP,
    duration=>numtodsinterval(240, 'minute'),
    repeat_interval=>'FREQ=MONTHLY;BYMONTHDAY=1;BYHOUR=3',
    end_date=>null,
    window_priority=>'LOW',
    comments=>'Start of the month window for maintenance task'
  );
 
  DBMS_SCHEDULER.CREATE_JOB(
    job_name => 'DBA_LOG.TABLESPACE_LOAD_MENSAL',
    job_type => 'STORED_PROCEDURE',
    job_action => 'DBA_LOG.TABLESPACE_LOAD',
    schedule_name => 'SYS.MONTH_START_WINDOW',
    enabled => TRUE
  );
 
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => '"DBA_LOG"."TABLESPACE_LOAD_MENSAL"',
    attribute => 'job_priority',
    VALUE => 4
  );
 
  DBMS_SCHEDULER.CREATE_JOB(
    job_name => 'DBA_LOG.OBJETO_TAMANHO_LOAD_SEMANAL',
    job_type => 'STORED_PROCEDURE',
    job_action => 'DBA_LOG.OBJETO_TAMANHO_LOAD',
    schedule_name => 'SYS.WEEKEND_WINDOW',
    enabled => TRUE
  );
 
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => 'DBA_LOG.OBJETO_TAMANHO_LOAD_SEMANAL',
    attribute => 'job_priority',
    VALUE => 4
  );
 
  DBMS_SCHEDULER.CREATE_JOB(
    job_name => 'DBA_LOG.OBJETO_INVALIDO_LOAD_DIARIO',
    job_type => 'STORED_PROCEDURE',
    job_action => 'DBA_LOG.OBJETO_INVALIDO_LOAD',
    schedule_name => 'SYS.WEEKNIGHT_WINDOW',
    enabled => TRUE
  );
 
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => 'DBA_LOG.OBJETO_INVALIDO_LOAD_DIARIO',
    attribute => 'job_priority',
    VALUE => 4
  );
 
  DBMS_SCHEDULER.CREATE_JOB(
    job_name => 'DBA_LOG.OBJETO_QT_LOAD_DIARIO',
    job_type => 'STORED_PROCEDURE',
    job_action => 'DBA_LOG.OBJETO_QT_LOAD',
    schedule_name => 'SYS.WEEKNIGHT_WINDOW',
    enabled => TRUE
  );
 
  DBMS_SCHEDULER.SET_ATTRIBUTE(
    name => 'DBA_LOG.OBJETO_QT_LOAD_DIARIO',
    attribute => 'job_priority',
    VALUE => 4
  );
 
END;
/

Se estiver utilizando o Oracle 9i ou inferiro, terá que utilizar os JOBs para agendar a coleta de dados:

VARIABLE jobno NUMBER;
BEGIN
  DBMS_JOB.SUBMIT(:jobno, 'BEGIN DBA_LOG.OBJETO_QT_LOAD; END;',
    TRUNC(SYSDATE) + 1/24, 'TRUNC(SYSDATE) + 1/24 + 30');
  COMMIT;
END;
/
 
VARIABLE jobno NUMBER;
BEGIN
  DBMS_JOB.SUBMIT(:jobno, 'BEGIN DBA_LOG.TABLESPACE_LOAD; END;',
    TRUNC(SYSDATE) + 1/24, 'TRUNC(SYSDATE + 30,''MONTH'') + 1/24');
  COMMIT;
END;
/
 
VARIABLE jobno NUMBER;
BEGIN
  DBMS_JOB.SUBMIT(:jobno, 'BEGIN DBA_LOG.OBJETO_TAMANHO_LOAD; END;',
    TRUNC(SYSDATE) + 1/24, 'NEXT_DAY(TRUNC(SYSDATE), ''SATURDAY'') + 1/24');
  COMMIT;
END;
/
 
VARIABLE jobno NUMBER;
BEGIN
  DBMS_JOB.SUBMIT(:jobno, 'BEGIN DBA_LOG.OBJETO_INVALIDO_LOAD; END;',
    TRUNC(SYSDATE) + 1/24, 'TRUNC(SYSDATE) + 25/24');
  COMMIT;
END;
/
 
VARIABLE jobno NUMBER;
BEGIN
  DBMS_JOB.SUBMIT(:jobno, 'BEGIN DBA_LOG.OBJETO_QT_LOAD; END;',
    TRUNC(SYSDATE) + 1/24, 'TRUNC(SYSDATE) + 25/24');
  COMMIT;
END;
/

Conclusão

Com os dados coletados nas tabelas, você só precisa agora exercitar um pouco do seu conhecimento de SQL para fazer consultas criativas e gerar relatórios dos mais diversos e entregar para o seu chefe no final do ano. Não, adianta nada criar os objetos agora e tentar fazer mágica. Após um anos, você poderá observar com alguma precisão a sazonalidade das aplicações e fazer boas projeções. Que tal começar o ano com um mínimo de coleta de dados na sua base? Quando a turma do ITIL bater na sua porta, algumas coisas já estarão encaminhadas para o seu lado.

Apagando todos objetos de um esquema

É muito comum ter várias aplicações numa única base do Oracle organizadas por esquemas. Se for este o seu caso, você se verá com alguma frequência para migrar um esquema inteiro da base de produção para homologação, do teste para homologação e por aí vai. Exportar os objetos e importar é muito simples, tanto com utilizando o IMP e EXP comuns até a versão 9i como com o Data Pump no 10g em diante:

Com o imp e exp:

exp usuario@nome_base_origem file=dump_esquema.dmp owner=nome_esquema
imp usuario@nome_base_destino file=dump_esquema.dmp fromuser=nome_esquema touser=nome_esquema

Com o data pump:

expdp usuario@nome_base_origem dumpfile=data_pump_dir dumpfile=dump_esquema.dmp schemas=nome_esquema
impdp usuario@nome_base_destino dumpfile=data_pump_dir dumpfile=dump_esquema.dmp schemas=nome_esquema

Bom, até aí não há muito motivo para espanto, é algo trivial. O detalhe é quando você precisa apagar todos os objetos já existentes antes de importar os novos. Para isso, um pouco de SQL sempre ajuda:

SELECT 'DROP TABLE ' || owner || '.' || TABLE_NAME || ' CASCADE CONSTRAINTS;'
  FROM dba_tables
  WHERE owner = 'NOME_ESQUEMA'';
 
SELECT 'DROP ' || object_type || ' ' || owner || '.' || object_name || ';'
  FROM dba_objects
  WHERE
    object_type != 'PACKAGE BODY' AND
    owner = 'NOME_ESQUEMA';

É claro que você poderia criar uma função para fazer tudo isso (limpar os objetos e importar o dump) numa tacada só. Mas eu diria que dois comandos SQLs não são motivo para tanto. :-)

Shell Scrip para backup manual no Oracle no Linux

… ou YASHTBODB: Yep, Another Shell Script To Backup Oracle DataBase!

Já faz algum tempo que utilizo Shell Script para fazer backup no Oracle. Na verdade eu prefiro utilizar outras linguagens de programação, particularmente o PERL, para não precisar ficar chamando o SQL*Plus toda hora. Mas como existe uma barreira cultural por aqui, acabou ficando tudo em Shell Script mesmo. Bom, ocorre que chegou a hora de aposentá-los em favor do RMAN. Sim, com o uso do RAC junto com o ASM, backup manual ou “user managed backup” como a Oracle gosta de dizer, perdeu completamente o sentido. Além disso, no Oracle 10g, o Database Control implementou uma série de alertas sofisticados bem mais eficientes que o singelo script aqui em questão. Estou terminando de implantar o RMAN para todos os últimos servidores e então os últimos scripts cairão finalmente no ostracismo. Então estou publicando-os aqui, para que sirvam de referência futura.

Requisitos

Rotinas executadas a cada 30 minutos:

  • Cópia dos archives para outro servidor;
  • Criação de alerta se alguma partição possuir mais de 80% e 95% de ocupação.

Rotinas executadas a cada 24 horas:

  • Rodar o analyze em todos objetos;
  • Fazer backup lógico full;
  • Copiar backup lógico para outro servidor;
  • Verificar a integridade dos datafiles;
  • Fazer cópia on-line dos datafiles permanentes para outro servidor;
  • Verificar o volume de origem e o copiado dos datafiles;
  • Fazer cópia do control_file em modo binário e lógico;
  • Fazer cópia do spfile;
  • Apagar archives com mais 8 dias;
  • Verificar se algum tablespace está com mais de 80% de ocupação;
  • Verificar todos os erros ocorridos no alert;
  • Renomear o alert para um nome com o SID e data

Rotinas executadas a cada 7 dias:

  • Cópia off-line dos datafiles permanentes para outro servidor;
  • Cópia do spfile

Rotina mensal (realizadas no último dia de cada mês):

  • Rodar scripts específicos de aplicações
  • Arquivar todos os logs (adump, bdump, cdump, udump e backup logs) em uma pasta ‘old’

Outros requisitos:

  • Criar um log contendo todos os erros ocorridos durante todas operações de backup;
  • Registrar no log a duração de todas operações longas;
  • Registrar no log o espaço disponível em cada partição de disco;
  • Enviar um e-mail com o log ao término de cada operação de backup;
  • Parametrizar o script de forma a utilizar o mesmo script para vários servidores distintos.

Preparação

Antes de mais nada, é preciso criar um usuário no SO e no Oracle. É preciso se assegurar que o usuário em questão tenha acesso às pastas que ele irá copiar. Existem pelo menos umas 4 formas de se fazer isso:

  • Utilizar o próprio usuário Oracle. A vantagem de usar o usuário Oracle do SO, é não precisar colocar senha no script, por outro lado a pessoa que administra o script acaba tendo autorização para acessar o banco diretamente sem senha e com poderes de SYSDBA. Em termos de segurança, isto não é recomendado. No entanto, os backups off-lines e backups de spfile exigem o usuo de um usuário com permissões de SYSDBA ou SYSOPER, então acaba fazendo sentido utilizar o usuário Oracle do SO para estas operações;
  • Alterar as permissões nas pastas do banco de dados para permitir a leitura para que qualquer usuário do SO (o terceiro dígito no sistema octal de permissões). Não gosto desta solução por problemas de segurança, uma vez que não apenas o usuário de backup como outros também terão acesso aos arquivos do Oracle. Se for para fazer isso, ainda é melhor usar o usuário Oracle do SO mesmo para tudo;
  • Alterar os grupos donos dos arquivos e pastas a serem utilizados. Também não é uma boa idéia, uma vez que ao criar um novo datafile, ele é criado com o grupo oinstall por padrão. A cada vez que um novo datafile é criado, você teria de alterar o grupo do datafile;
  • Adicionar o usuário de backup ao grupo oinstall. Pode não ser uma solução muito segura, mas veja que os datafiles são criados com a permissão 640 por padrão. O que significa que o usuário em questão só poderá ler os datafiles sem poder alterá-los, o que me parece bem razoável. Mesmo assim, você deverá acertar o acesso de algumas pastas e arquivos para que você possa gravar nelas, como a pasta onde o próprio script vai ficar e onde ele vai gerar os logs e os backups lógicos. Esta será a opção utilizada para a maioria das operações de backup. A excessão, como citado anteriormente fica para o backup do spfile e backup off line.

Para criar um usuário no SO que faça parate do grupo ‘oinstall’ (realmente estou supondo que você usou o nome dos usuários e grupos de usuáros padrões da instalação):

/usr/sbin/useradd -m -p senha -g oinstall -c 'Usuário que dispara os scripts de backup' backup

O mesmo usuário do SO deverá ter um correspondente dentro do banco de dados:

CREATE USER backup IDENTIFIED BY senha
  DEFAULT TABLESPACE users
  QUOTA UNLIMITED ON users
;

Se você quiser utilizar um usuário a parte mas não quer colocar a senha do usuário nos scripts (que é o que foi feito no nosso exemplo), pode criar o usuário com o comando abaixo:

CREATE USER backup IDENTIFIED EXTERNALLY
  DEFAULT TABLESPACE users
  QUOTA UNLIMITED ON users
;

Mas para utilizar usuários autenticados externamente (pelo SO) não esqueça de setar no ‘init.ora’ os seguintes parâmetros:

remote_os_authent=FALSE
os_authent_prefix=''

O parâmetro ‘os_authent_prefix’ é opcional e permite que o nome no SO seja idêntico ao nome no banco de dados. Já o parâmetro ‘remote_os_authent’ é vital para garantir a segurnaça do banco de dados. O padrão do parâmetro é ‘FALSE’ e ninguém em sã consciência utiliza este parâmetro como ‘TRUE’.

Depois de criar o usuário precisamos conceder as permissões adequadas para ele:

GRANT CREATE SESSION TO backup;
GRANT ALTER SESSION TO backup;
GRANT ALTER DATABASE TO backup;
GRANT ALTER SYSTEM TO backup;
GRANT MANAGE TABLESPACE TO backup;
GRANT ANALYZE ANY TO backup;
GRANT ANALYZE ANY DICTIONARY TO backup;
GRANT EXP_FULL_DATABASE TO backup;

Lembre-se de O usuário também deve ter um tablespace com alguma quota para criar as tabelas de logs do backup lógico (coisas do data pump a partir do 10g). O comando CREATE USER cria o usuário, com a senha: ‘senha’. Depois concedemos privilégio para o usuário se conectar no banco de dados em CREATE SESSION. O privilégio ALTER SESSION será utilizado para ajudar a identificar o backup lógico do control file enquanto o privilégio ALTER DATABASE é utilizado para realizar o backup físico e lógico do control file propriamente dito. O privilégio ALTER SYSTEM é necessário para realizar o checkpoint e o rotacionamento dos logs durante o backup físico on line enquanto o privilégio MANAGE TABLESPACE permite a operação de backup on line propriamente dita. Note que aqui a permissão de leitura dos datafiles pelo usuário do SO é necessária também. Depois temos os privilégios ANALYZE e ANALYZE ANY DICTIONARY necessários para as atualizações de estatísticas das tabelas e índices. Note que o privilégio ANALYZE ANY DICTIONARY surge apenas no Oracle 10g, não existindo nem sendo necessário em versões anteriores. Por último vem o privilégio EXP_FULL_DATABASE que é necessário para o backup lógico.

Se você for utilizar o Data Pump (muito recomendado e utilizado no nosso exemplo), a nova ferramente de backup lógico da Oracle a partir da versão 10g, você deverá utilizar também os seguintes comandos SQL:

CREATE DIRECTORY data_pump_dir_ORACLE_SID AS '/u03/app/oracle/admin/ORA_SID/exp';
GRANT READ, WRITE ON DIRECTORY data_pump_dir_ORA_SID

Onde ‘ORACLE_SID’ é o nome do seu banco de dados oracle. É claro que o ponto de montagem ‘/u03′ é específico do padrão que adoto nos meus servidores. Substitua o caminho do diretório para aquele que você reservou no seu servidor. Lembre-se que este diretório deve ser criado manualmente e o usuário ‘backup’ ou o grupo ‘oinstall’ do SO devem ter permissões de leitura e gravação neste diretório:

mkdir -p /u03/app/oracle/admin/ORA_SID/exp
chown backup:oinstall /u03/app/oracle/admin/ORA_SID/exp
chmod 755/u03/app/oracle/admin/ORA_SID/exp

Outras permissões deverão ser acertadas para o usuário de backup no SO como a permissão para apagar os archives (com uma permissão 770 na pasta por exemplo). Alguns acertos podem ser um pouco chatos e devem ser realizados no servidor de destino onde os arquivos são copiados também.

Um último detalhe é que é preciso configurar o SSH sem senha entre os servidores onde está o servidor Oracle e e o que vai receber todos os backups. Meus testes com NFS mostraram que ele é menos estável que o SSH e a diferença de performance é mínima. Existem vários tutoriais sobre como fazer isso, inclusive este da própria Oracle. No meu caso eu criei um usuário backup e um usuário oracle no servidor de destino e gerei o ssh sem senha entre os usuários com mesmo nome nos servidores de origem e destino dos backups.

Funcionamento do Script

Algumas premissas foram assumidas antes de criar o script:

  • O mesmo script deveria ser utilizado independente do banco de dados que sofreria as operações. Para isso o script deve receber como parâmetro o nome do banco e todas as pastas devem receber o nome do banco também, seguindo as recomendações do OFA. Isto facilita e muito a utilização do mesmo script em vários servidores distintos ou quando temos mais de uma instância no mesmo servidor.
  • O mesmo script deveria ser utilizado para diversas operações diferentes. Para isso o script deve receber como parâmetro o nome da operação.
  • Excepto pelos parâmetros de nome da base e operação, todas as outras variáveis são identicas, sendo declaradas no início do script. Esta configuração é adequada para o caso onde se utiliza apenas um ORACLE_HOME para todas as bases do servidor, mas pode ser um problema em outros casos.
  • Ao invés de colocar todo o código SQL em arquivos separados e chamarmos eles pelo SQL*Plus, optei por colocar todo o códio SQL dentro do script e envia-lo para o SQL*Plus utilizando o redirecionamento conhecido como “here document”, que é algo como um < onde “…” é o código SQL.
  • Todas as chamadas do scripts seriam chamados a partir do crontab do usuário, mas isto não impede a chamada manual.
  • Cada etapa do backup é colocada em uma função separada, de forma a podermos realocar as etapas para diferentes situações em necessidades específicas (como em servidores de teste e produção). A forma como o script está configurado aqui é apenas um exemplo. Em produção, tenho uma série de outras funções específicas de nossas regras de negócio que foram removidas também.
  • O backup lógico aqui está sendo feito com o Data Pump, mas nada impede você de adaptar o script para utilizar a ferramenta tradicional de export da Oracle.

Variáveis de Ambiente

Antes de passar para os scripts de backup, segue abaixo algumas variáveis utilizadas não apenas pelos scripts em shell, como também os scripts em SQL. Vale a pena lembrar que quando os scripts são executados no modo não interativo (como quando chamados pelo cron) é comum que os arquivos .bashrc e .bash_profile ignorem suas configurações. Por via das dúvidas, vamos exportar todas as variáveis de ambiente do Oracle e outros que serão úteis em nossos scripts.

  • ORACLE_HOME : o diretório onde o SGDB Oracle foi instalado;
  • ORACLE_SID : o nome do banco de dados alvo das operações de backup;
  • SQLPATH : o diretório padrão do SQL*Plus para armazenamento de scripts SQL;
  • NLS_LANG : Diz respeito a localização e codificação de caracteres utilizados;
  • PATH : deve incluir o caminho para o diretório com os aplicativos Oracle;
  • dia : referência para saber a data em que a operação foi realizada. Gosto de utilizar o formato ano/mês/dia, pois é mais fácil de listar em órdem cronológica arquivos com este formato de data;
  • log : arquivo contendo o log de toda a operação;
  • arch_dest : local de destino local para os archives;
  • dump_dest : local de destino local para os backups lógicos;
  • remote_dest : nome ou ip do servidor remoto que receberá os backups
  • dados: nome dos pontos de montagem onde se encontram os datafiles;
  • oracle_mail : email para onde os logs e alertas serão enviados;

Chamadas no crontab

Segue aqui as chamadas utilizadas para acionar o script

00 06 1 * * /u01/app/oracle/admin/nome_base/adhoc/backup.sh nome_base rotina_mensal
00 22 * * * /u01/app/oracle/admin/nome_base/adhoc/backup.sh nome_base backup_noturno
00,30  * * * * /bin/bash /u01/app/oracle/admin/nome_base/adhoc/backup.sh nome_base copia_archives

Note que para realizar as rotinas de backup off line, é preciso do privilégio de SYSDBA para fechar e abrir a instância. Então se você utiliza um usuário separado para o backup, deverá colocar a linha abaixo no crontab do usuário oracle do SO e não usuário backup.

00 00 * * 0 /u01/app/oracle/admin/nome_base/adhoc/backup.sh nome_base backup_off_line

O Script

Bom, chega de enrolação, segue abaixo o script. Se você encontrar algum erro (eu tive que tirar algumas coisas muito específicas aqui do trabalho) ou alguma forma de melhorar ele, por favor deixe um comentário ou mande um e-mail.

#!/bin/bash
######################################################################################
#	Arquivo: backup.sh                                                           #
#	Uso: Script para backup "user managed" de bancos de dados Oracle             #
#	Criado por: Fábio Telles                                                     #
#	Última alteração: 14/08/2008                                                 #
#	Motivo: Publicação                                                           #
######################################################################################
#	TODO: Refazer todo script em PERL ;-)                                        #
######################################################################################
 
# Verificação de parâmetros
ORACLE_SID=$1
operation=$2
if [ -z $operation ]; then
  echo "***** Favor definir a operação a ser executada"
  echo "***** chamada $0 $* ilegal"
  echo "***** uso: $0  "
  exit 2
fi
if [ -z $ORACLE_SID ]; then
  echo "***** Favor definir o nome do banco de dados"
  echo "***** chamada $0 $* ilegal"
  echo "***** uso: $0  "
  exit 2
fi
 
# Definição de variáveis (variáveis referenciadas fora do script precisam ser exportadas)
export ORACLE_SID
export ORACLE_BASE=/opt/app/oracle
export ORACLE_HOME=${ORACLE_BASE}/product/10.2.0/db_1
export LD_LIBRARY_PATH=${ORACLE_HOME}/lib
export SQLPATH=/u01/app/oracle/admin/${ORACLE_SID}/adhoc
export NLS_LANG=Portuguese_Brazil.WE8ISO8859P1
export PATH=$PATH:${ORACLE_HOME}/bin
export dia=`date +%Y-%m-%d`
export log=/u01/app/oracle/admin/${ORACLE_SID}/backup/${operation}_${dia}.log
export arch_dest=/u02/app/oracle/admin/${ORACLE_SID}/arch
export dump_dest=/u03/app/oracle/admin/${ORACLE_SID}/exp
export remote_dest=seu_servidor_backup
dados=(/u04 /u05)
oracle_mail=email_do_dba@sua_empresa.com.br
 
# Uma função para cada rotina
copia_archives() {
  rsync -t ${arch_dest}/* ${remote_dest}:${arch_dest} 2>> $log
  if [ $? -ne 0 ]; then
    cat $log | mail -s "Erros na cópia de archives de `uname -n` p/ $remote_dest em ${dia}" $oracle_mail
  fi
}
 
verifica_discos() {
  # Verifica se alguma partição montada tem mais de 80% e 95% de ocupação
  for part in `df -hlP -t ext3 | cut -c0-9` ;do
    df $part > /dev/null 2> /dev/null
    if [ $? -eq 0 ]; then
      mount=`df $part | tail -1 | awk '{print $6}'`
      percent=`df $part | tail -1 | awk '{print $5}'`
      if [ ${percent%\%} -gt 80 -a ${percent%\%} -lt 95 ]; then
        echo "Atenção: `uname -n` c/ $part montada em $mount está c/ $percent ocupado." >> $log
        cat $log | mail -s "Alerta de disco em `uname -n`!" $oracle_mail
      elif [ ${percent%\%} -ge 95 ]; then
        echo "ATENÇÃO URGENTE: `uname -n` c/ $part montada em $mount está c/ $percent ocupado." >> $log
        cat $log | mail -s "ALERTA CRÍTICO de disco em `uname -n`!" $oracle_mail
     fi
    fi
  done
}
 
analize() {
  echo "Analyze: analizando ${ORACLE_SID} em "`date +%R:%S` >> $log
  sqlplus -S / <<-EOF_ANALIZE
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/analyze.log
    EXECUTE dbms_stats.gather_database_stats();
    SPOOL OFF
    EXIT;
EOF_ANALIZE
  egrep --color -n -B 2 '(ORA-|SP2-)' ${SQLPATH}/analyze.log  >> $log 2>> $log
}
 
backup_logico() {
  echo "Backup lógico: removendo exports de ${dump_dest} em "`date +%R:%S` >> $log
  rm -v ${dump_dest}/${ORACLE_SID}_full_* >> $log 2>> $log
  echo "Backup lógico: export iniciado em "`date +%R:%S` >> $log
  expdp / full=y directory=data_pump_dir_${ORACLE_SID} dumpfile=${ORACLE_SID}_full_${dia}.dmp \
    logfile=${ORACLE_SID}_full_exp_${dia}.log
  tail -n 1 ${dump_dest}/${ORACLE_SID}_full_exp_${dia}.log >> $log 2>> $log
  echo "Backup lógico: copiando dump para ${remote_dest}/${dump_dest}  em "`date +%R:%S` >> $log
  scp ${dump_dest}/${ORACLE_SID}_full_${dia}.dmp \
    ${remote_dest}:${dump_dest}/${ORACLE_SID}_full_${dia}.dmp >> $log 2>> $log
  scp ${dump_dest}/${ORACLE_SID}_full_exp_${dia}.log \
    ${remote_dest}:${dump_dest}/${ORACLE_SID}_full_exp_${dia}.log >> $log 2>> $log
  echo "" >> $log
}
 
verifica_data_file() {
  echo "Verificação física on-line iniciada em "`date +%R:%S` >> $log
  sqlplus -S / <<-EOF_VERIFICA_DATA_FILE
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/db_verify.txt
    SELECT '!dbv file=' || file_name || ' 2>> ${SQLPATH}/db_verify.log'
      FROM dba_data_files
      ORDER BY tablespace_name, file_name;
    SPOOL OFF
    @db_verify.txt
    EXIT
EOF_VERIFICA_DATA_FILE
  egrep --color -n -B 7 -A 9 'Total Pages Failing.*(Data).*[^0]$' \
    ${SQLPATH}/db_verify.log >> $log 2>> $log
  egrep --color -n -B 9 -A 7 'Total Pages Failing.*(Index).*[^0]$' \
    ${SQLPATH}/db_verify.log >> $log 2>> $log
  egrep --color -n -B 14 -A 2 'Total Pages Marked Corrupt.*[^0]$' \
    ${SQLPATH}/db_verify.log >> $log 2>> $log
  echo "" >> $log 2>> $log
}
 
backup_fisico_on_line() {
  echo "Backup físico on-line: copiando data files para ${remote_dest} em "`date +%R:%S`  >> $log
  sqlplus -S / <<-EOF_BACKUP_ON_LINE
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/backup_on_line.txt
    SELECT 'ALTER SYSTEM CHECKPOINT;' FROM dual;
    SELECT command
      FROM (
        SELECT
          '2' ordem,
          tablespace_name,
          'ALTER TABLESPACE ' || tablespace_name || ' BEGIN BACKUP;' command
          FROM dba_tablespaces
          WHERE contents != 'TEMPORARY'
        UNION
        SELECT
          '3' ordem,
          a.tablespace_name,
          '!scp ' || file_name || ' ${remote_dest}:' || file_name || ' 2>> $log'   command
          FROM
            dba_data_files  a,
            dba_tablespaces b
          WHERE
            a.tablespace_name = b.tablespace_name AND
            b.contents != 'TEMPORARY'
        UNION
        SELECT
          '4' ordem,
          tablespace_name,
          'ALTER TABLESPACE ' || tablespace_name || ' END BACKUP;' command
          FROM dba_tablespaces
          WHERE contents != 'TEMPORARY'
      )
      ORDER BY tablespace_name, ordem;
    SELECT 'ALTER SYSTEM ARCHIVE LOG CURRENT;' FROM dual;
    SELECT 'EXIT;' FROM dual;
    SPOOL OFF
    SET PAGES 50
    SPOOL ${SQLPATH}/backup_on_line.log
    @${SQLPATH}/backup_on_line.txt
    SPOOL OFF
EOF_BACKUP_ON_LINE
  egrep --color -n -B 2 '(ORA-|SP2-)' ${SQLPATH}/backup_on_line.txt >> $log 2>> $log
  egrep --color -n -B 2 '(ORA-|SP2-)' ${SQLPATH}/backup_on_line.log >> $log 2>> $log
  echo "" >> $log
}
 
compara_backup_fisico() {
  echo "Backup físico on-line: comparando origem e destino em "`date +%R:%S`  >> $log
  echo "Backup físico on-line: origem =" >> $log 2>> $log
  for p in ${dados[*]}; do
    du -hs ${p}/oradata/${ORACLE_SID} --exclude temp* >> $log 2>> $log
  done
  echo "Backup físico on-line: destino =" >> $log 2>> $log
  for p in ${dados[*]}; do
    ssh $remote_dest du -hs ${p}/oradata/${ORACLE_SID} >> $log 2>> $log
  done
  echo "" >> $log
}
 
backup_control_file() {
  echo "Backup físico on-line: copiando control files para ${ORACLE_HOME}/dbs em "`date +%R:%S`  >> $log
  sqlplus -S / <<-EOF_BACKUP_ON_LINE
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/backup_control_file.txt
    SELECT
      '!cp ' || par.value || '/' ||
      ins.instance_name || '_ora_' ||
      pro.spid || '_control_file.trc ${ORACLE_HOME}/dbs/control_file_back.sql 2>> $log'
      FROM
        v\$parameter par,
        v\$instance ins,
        v\$process pro,
        v\$session ses
      WHERE
        par.name='user_dump_dest' AND
        ses.sid = SYS_CONTEXT ('USERENV', 'SID') AND
        ses.paddr = pro.addr
     ;
    SPOOL OFF
    SPOOL ${SQLPATH}/backup_control_file.log
    ALTER SESSION SET TRACEFILE_IDENTIFIER = 'control_file';
    ALTER DATABASE BACKUP CONTROLFILE TO TRACE;
    ALTER SESSION SET TRACEFILE_IDENTIFIER = '';
    @${SQLPATH}/backup_control_file.txt
    ALTER DATABASE BACKUP CONTROLFILE TO '${ORACLE_HOME}/dbs/control_file_back.bin' REUSE;
    SPOOL OFF
EOF_BACKUP_ON_LINE
  egrep --color -n -B 2 '(ORA-|SP2-)' ${SQLPATH}/backup_control_file.txt >> $log 2>> $log
  egrep --color -n -B 2 '(ORA-|SP2-)' ${SQLPATH}/backup_control_file.log >> $log 2>> $log
  echo "" >> $log
}
 
backup_dbs() {
  echo "Backup físico on-line: copiando ${ORACLE_HOME}/dbs para \
    ${remote_dest}:${ORACLE_HOME}/dbs_${ORACLE_SID}.tar.bz2 em "`date +%R:%S`  >> $log
  cd $ORACLE_HOME
  tar -cjf ${SQLPATH}/dbs_${ORACLE_SID}.tar.bz2 dbs >> $log 2>> $log
  scp ${SQLPATH}/dbs_${ORACLE_SID}.tar.bz2 \
    ${remote_dest}:${SQLPATH}/dbs_${ORACLE_SID}.tar.bz2 >> $log 2>> $log
}
 
limpa_archives() {
  echo "Archives: removendo archives com mais de 8 dias de $arch_dest em "`date +%R:%S` >> $log
  find $arch_dest -daystart -mtime +8 -exec rm -fv \{\} \; >> $log 2>> $log
  echo "" >> $log
}
 
verifica_alert() {
  echo "Alert: erros encontrados =>" >> $log
  egrep --color -n -B 2 '(ORA-|SP2-)' \
    /u01/app/oracle/admin/${ORACLE_SID}/bdump/alert_${ORACLE_SID}.log >> $log 2>> $log
  echo "Alert: renomeando bdump/alert_${ORACLE_SID}.log para ${ORACLE_SID}_${dia}.log" >> $log
  if [ -f /u01/app/oracle/admin/${ORACLE_SID}/bdump/alert_${ORACLE_SID}.log ]; then
    mv /u01/app/oracle/admin/${ORACLE_SID}/bdump/alert_${ORACLE_SID}.log \
      /u01/app/oracle/admin/${ORACLE_SID}/bdump/${ORACLE_SID}_${dia}.log >> $log
  fi
}
 
verifica_tablespaces() {
  echo "Tablespaces com mais de 80% de espaço ocupado =>" >> $log
  sqlplus -S / <<-EOF_VERIFICA_TABLESPACES
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/verifica_tablespaces.log
    SELECT
      u.tablespace_name || ' com ' ||
        TO_CHAR( 100 * u.utilizado / m.maximo , '999.9') || '% ocupado' tablespace
      FROM
        (SELECT tablespace_name, SUM (bytes) utilizado
          FROM dba_segments
          GROUP BY tablespace_name) u,
        (SELECT
          tablespace_name,
          SUM (bytes) alocado,
          SUM (DECODE (autoextensible, 'NO', bytes, maxbytes)) maximo
          FROM dba_data_files
          GROUP BY tablespace_name) m,
        (SELECT
          tablespace_name, SUM (bytes) livre
          FROM dba_free_space
          GROUP BY tablespace_name) l
          WHERE
            l.tablespace_name = u.tablespace_name AND
            l.tablespace_name = m.tablespace_name AND
            100 * u.utilizado / m.maximo > 80
    UNION
    SELECT
      tablespace_name || ' com ' ||
        TO_CHAR( 100 * SUM (user_bytes) / SUM (DECODE (autoextensible, 'NO', bytes, maxbytes)) , '999.9')
        || '% ocupado' tablespace
      FROM dba_temp_files
      GROUP BY tablespace_name
      HAVING 100 * SUM(user_bytes) /  SUM (DECODE (autoextensible, 'NO', bytes, maxbytes)) > 80
    ;
    SPOOL OFF
    EXIT;
EOF_VERIFICA_TABLESPACES
  cat ${SQLPATH}/verifica_tablespaces.log >> $log 2>> $log
}
 
mostra_espaco_disco() {
  echo "Espaço em disco =>" >> $log
  df -hl -t ext3 >> $log 2>> $log
  echo "" >> $log
}
 
backup_spfile() {
  echo "Copiando spfile para ${ORACLE_HOME}/dbs/init${ORACLE_SID}_backup.ora em "`date +%R:%S` >> $log
  #Só funciona se o spfile existir
  if [ -f ${ORACLE_HOME}/dbs/spfile${ORACLE_SID}.ora ]; then
    #Só pode ser executado pelo usuário Oracle do SO. Verificação abaixo
    if [ $USER != 'oracle' ]; then
      echo "***** A operação ${operation} não é válida com o usuário $USER" >> $log
      echo "***** tente novamente com o usuário oracle do SO." >> $log
      exit 2
    fi
    echo " --Backup do spfile (precisa de privilégio sysdba ou sysoper)"
    sqlplus -S / as sysdba <<-EOF_BACKUP_SPFILE
      SET FEEDBACK OFF
      SET TRIMSPOOL ON
      SET TERMOUT OFF
      SET PAGES 0
      SET LINES 250
      SPOOL ${SQLPATH}/backup_spfile.log
      CREATE PFILE='${ORACLE_HOME}/dbs/init${ORACLE_SID}_backup.ora'
        FROM SPFILE='${ORACLE_HOME}/dbs/spfile${ORACLE_SID}.ora';
      SPOOL OFF
      EXIT
EOF_BACKUP_SPFILE
    egrep --color -n -B 2 '(ORA-|SP2-)' ${SQLPATH}/backup_spfile.log >> $log 2>> $log
  else
    echo "SPFILE ${ORACLE_HOME}/dbs/spfile${ORACLE_SID}.ora não encontrado!" >> $log
  fi
}
 
shut_down() {
  echo "Backup físico off line: desligando instância em "`date +%R:%S` >> $log
  #Só pode ser executado pelo usuário Oracle do SO. Verificação abaixo
  if [ $USER != 'oracle' ]; then
    echo "***** A operação ${operation} não é válida com o usuário $USER" >> $log
    echo "***** tente novamente com o usuário oracle do SO." >> $log
    exit 2
  fi
  sqlplus -S / as sysdba <<-EOF_SHUT_DOWN
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/shut_down.log
    SHUTDOWN IMMEDIATE
    SPOOL OFF
    EXIT
EOF_SHUT_DOWN
  grep --color -n -B 2 ORA- ${SQLPATH}/shut_down.log >> $log 2>> $log
  proc=`ps -ef | egrep pmon_$ORACLE_SID  | grep -v grep`
  if [ -n $proc ]; then
    echo "***** Problema ao desligar a instância $ORACLE_SID" >> $log
    echo "***** Tentando realizar um shutdown abort" >> $log
    sqlplus -S / as sysdba <<-EOF_SHUT_DOWN_ABORT
      SET FEEDBACK OFF
      SET TRIMSPOOL ON
      SET TERMOUT OFF
      SET PAGES 0
      SET LINES 250
      SPOOL ${SQLPATH}/shut_down_abort.log
      SHUTDOWN ABORT
      SPOOL OFF
      EXIT
EOF_SHUT_DOWN_ABORT
    grep --color -n -B 2 ORA- ${SQLPATH}/shut_down_abort.log >> $log 2>> $log
    proc=`ps -ef | egrep pmon_$ORACLE_SID  | grep -v grep`
    if [ -n $proc ]; then
      echo "***** Algum problema sério aconteceu no banco $ORACLE_SID" >> $log
      echo "***** O servidor pode estar num estado inconsistente" >> $log
      cat $log | mail -s "URGENTE!!! Erro no backup off line de `uname -n`/$dia" $oracle_mail
      exit 2
      grep --color -n -B 2 ORA- ${SQLPATH}/shut_down_abort.log >> $log 2>> $log
    fi
  fi
  echo "" >> $log 2>> $log
}
 
copia_datafiles() {
  echo "Backup físico of line: copiando arquivos de dados em "`date +%R:%S` >> $log
  for p in ${dados[*]}; do
    scp ${p}/oradata/${ORACLE_SID}/* ${remote_dest}:${p}/oradata/${ORACLE_SID} 2>> $log
  done
}
 
start_up() {
  echo "Backup físico: ligando instância em "`date +%R:%S` >> $log
  #Só pode ser executado pelo usuário Oracle do SO. Verificação abaixo
  if [ $USER != 'oracle' ]; then
    echo "***** A operação ${operation} não é válida com o usuário $USER" >> $log
    echo "***** tente novamente com o usuário oracle do SO." >> $log
    exit 2
  fi
  sqlplus -S / as sysdba <<-EOF_START_UP
    SET FEEDBACK OFF
    SET TRIMSPOOL ON
    SET TERMOUT OFF
    SET PAGES 0
    SET LINES 250
    SPOOL ${SQLPATH}/start_up.log
    STARTUP
    SPOOL OFF
    EXIT
EOF_START_UP
  grep --color -n -B 2 ORA- ${SQLPATH}/start_up.log >> $log 2>> $log
  if [ $? -eq 0 ]; then
    echo "***** Problema ao ligar a instância $ORACLE_SID" >> $log
    cat $log | mail -s "URGENTE!!! Erro no backup off line de `uname -n`/$dia" $oracle_mail
    exit 2
  else
    proc=`ps -ef | egrep pmon_$ORACLE_SID  | grep -v grep`
    if [ -z $proc ]; then
      echo "***** Problema ao ligar a instância $ORACLE_SID" >> $log
      cat $log | mail -s "URGENTE!!! Erro no backup off line de `uname -n`/$dia" $oracle_mail
      exit 2
    fi
  fi
  echo "" >> $log
}
 
define_mes_anterior() {
  mes=`date +%m`
  mes=$(expr $mes - 1)
  if [ $mes = 0 ]; then
    ano=`date +%Y`
    ano=$(expr $ano - 1)
    mes=${ano}-12
  else
    mes=`date +%Y`-$mes
  fi
}
 
move_logs() {
  echo "Logs: Movendo logs em adump para adump/old/${ORACLE_SID}_audit_${mes}.tar.bz2" >> $log
  cd /u01/app/oracle/admin/${ORACLE_SID}/adump
  touch ${ORACLE_SID}_audit_${mes}.tar $log 2>> $log
  find -name "ora_*.aud" -exec tar -rf ${ORACLE_SID}_audit_${mes}.tar \{\} \; 2>> $log
  if [ -s ${ORACLE_SID}_audit_${mes}.tar ]; then
    bzip2 ${ORACLE_SID}_audit_${mes}.tar  >> $log 2>> $log
    mv ${ORACLE_SID}_audit_${mes}.tar.bz2 old  >> $log 2>> $log
    rm ora_*.aud
  else
    rm ${ORACLE_SID}_audit_${mes}.tar  >> $log 2>> $log
  fi
 
  echo "Logs: Movendo logs em bdump para bdump/old/${ORACLE_SID}_bdump_log_${mes}.tar.bz2" >> $log
  cd /u01/app/oracle/admin/${ORACLE_SID}/bdump
  touch ${ORACLE_SID}_bdump_log_${mes}.tar $log 2>> $log
  find -name "${ORACLE_SID}_${mes}-*.log" -exec \
    tar -rf ${ORACLE_SID}_bdump_log_${mes}.tar \{\} \; 2>> $log
  if [ -s ${ORACLE_SID}_bdump_log_${mes}.tar ]; then
    bzip2 ${ORACLE_SID}_bdump_log_${mes}.tar  >> $log 2>> $log
    mv ${ORACLE_SID}_bdump_log_${mes}.tar.bz2 old  >> $log 2>> $log
    rm ${ORACLE_SID}_${mes}-*.log
  else
    rm ${ORACLE_SID}_bdump_log_${mes}.tar >> $log 2>> $log
  fi
 
  echo "Logs: Movendo trace em bdump para bdump/old/${ORACLE_SID}_bdump_trace_${mes}.tar.bz2"` >> $log
  cd /u01/app/oracle/admin/${ORACLE_SID}/bdump
  touch ${ORACLE_SID}_bdump_trace_${mes}.tar $log 2>> $log
  find -name "${ORACLE_SID}_*.trc" -exec tar -rf ${ORACLE_SID}_bdump_trace_${mes}.tar \{\} \; 2>> $log
  if [ -s ${ORACLE_SID}_bdump_trace_${mes}.tar ]; then
    bzip2 ${ORACLE_SID}_bdump_trace_${mes}.tar  >> $log 2>> $log
    mv ${ORACLE_SID}_bdump_trace_${mes}.tar.bz2 old  >> $log 2>> $log
    rm ${ORACLE_SID}_*.trc
  else
    rm ${ORACLE_SID}_bdump_trace_${mes}.tar >> $log 2>> $log
  fi
 
  echo "Logs: Movendo trace em cdump para cdump/old/${ORACLE_SID}_cdump_trace_${mes}.tar.bz2" >> $log
  cd /u01/app/oracle/admin/${ORACLE_SID}/cdump
  touch ${ORACLE_SID}_cdump_trace_${mes}.tar $log 2>> $log
  find -name "core_*" -exec tar -rf ${ORACLE_SID}_cdump_trace_${mes}.tar \{\} \; 2>> $log
  if [ -s ${ORACLE_SID}_cdump_trace_${mes}.tar ]; then
    bzip2 ${ORACLE_SID}_cdump_trace_${mes}.tar  >> $log 2>> $log
    mv ${ORACLE_SID}_cdump_trace_${mes}.tar.bz2 old  >> $log 2>> $log
    rm -R core_*
  else
    rm ${ORACLE_SID}_cdump_trace_${mes}.tar >> $log 2>> $log
  fi
 
  echo "Logs: Movendo trace em udump para udump/old/${ORACLE_SID}_udump_trace_${mes}.tar.bz2" >> $log
  cd /u01/app/oracle/admin/${ORACLE_SID}/udump
  touch ${ORACLE_SID}_udump_trace_${mes}.tar $log 2>> $log
  find -name "${ORACLE_SID}_*.trc" -exec tar -rf ${ORACLE_SID}_udump_trace_${mes}.tar \{\} \; 2>> $log
  if [ -s ${ORACLE_SID}_udump_trace_${mes}.tar ]; then
    bzip2 ${ORACLE_SID}_udump_trace_${mes}.tar  >> $log 2>> $log
    mv ${ORACLE_SID}_udump_trace_${mes}.tar.bz2 old  >> $log 2>> $log
    rm ${ORACLE_SID}_*.trc
  else
    rm ${ORACLE_SID}_udump_trace_${mes}.tar >> $log
  fi
 
  echo "Logs: Movendo logs de backup para backup/old/${ORACLE_SID}_backup_${mes}.tar.bz2" >> $log
  cd /u01/app/oracle/admin/${ORACLE_SID}/backup
  touch ${ORACLE_SID}_udump_trace_${mes}.tar $log 2>> $log
  find -name "*_${mes}.log" -exec tar -rf ${ORACLE_SID}_backup_log_${mes}.tar \{\} \; 2>> $log
  if [ -s ${ORACLE_SID}_backup_log_${mes}.tar ]; then
    bzip2 ${ORACLE_SID}_backup_log_${mes}.tar  >> $log 2>> $log
    mv ${ORACLE_SID}_backup_log_${mes}.tar.bz2 old  >> $log 2>> $log
    rm ${ORACLE_SID}_${mes}-*.log
  else
    rm ${ORACLE_SID}_backup_log_${mes}.tar >> $log 2>> $log
  fi
}
 
#Rotina principal
case $operation in
  'copia_archives')
    copia_archives
    verifica_discos
  ;;
  'backup_noturno')
    echo "" >> $log 2>> $log
    echo "Inicio da rotina de backup noturno em $ORACLE_SID em "`uname -n`" para o dia ${dia}" >> $log
    echo "================================================================="  >> $log
    echo "" >> $log
    analize
    backup_logico
    verifica_data_file
    backup_fisico_on_line
    compara_backup_fisico
    backup_control_file
    backup_dbs
    limpa_archives
    verifica_alert
    verifica_tablespaces
    mostra_espaco_disco
    echo "Fim do backup noturno. Este log está disponível em $log" >> $log
    cat $log | mail -s "Log do backup noturno de `uname -n`/${dia}" $oracle_mail
  ;;
  'backup_off_line')
    echo "" >> $log
    echo "Início do backup off line de $ORACLE_SID em "`uname -n`" para o dia ${dia}" >> $log
    echo "=============================================================================="  >> $log
    echo "" >> $log
    shut_down
    copia_datafiles
    start_up
    echo "Fim do backup off line. Este log está disponível em $log" >> $log
    cat $log | mail -s "Log do backup off line de `uname -n`/${dia}" $oracle_mail
  ;;
  'rotina_mensal')
    define_mes_anterior
    echo "" >> $log
    echo "Início da rotina mensal de $ORACLE_SID em "`uname -n`" para o mês ${mes} em ${dia}" >> $log
    echo "=============================================================================="  >> $log
    echo "" >> $log
    move_logs
    verifica_espaco_disco
    echo "Fim da rotina mensal. Este log está disponível em $log" >> $log
    cat $log | mail -s "Log da rotina mensal de `uname -n`/${dia}" $oracle_mail
  ;;
  *)
    echo "***** A operação ${operation} não é válida" >> $log
    echo "***** chamada $0 $* ilegal"
    echo "***** uso: $0  " >> $log
  ;;
esac

Referências

Manuais do Oracle Database 10g

Adivinha quantos manuais existem para o Oracle Database 10.2?

  1. 12
  2. 36
  3. 120
  4. 360

Se você entrar no site da documentação você contará com nada mais, nada menos que 360 manuais. Não, não estou brincando não. Entre lá e confira você mesmo.

Bom é verdade que tirando todas as documentações específicas para instalação em diferentes plataformas, SO, storage e suas respectivas notas de lançamento, ficam apenas 141 manuais… o que lhe parece bem mais razoável, não?

Se você tirar todos os pacotes opcionais ou comprados separadamente, conseguimos enxugar para 50 manuais apenas. Veja que estamos tirando da lista coisas importantes como Data Guard, RAC e HTML DB, XML DB, entre outros. Bom, agora vem a pergunta de 1 milhão de euros (dólar não tá com essa bola toda): o que eu devo ler?

  1. O manual completo de instalação do Oracle em sua plataforma predileta junto com os seus respectivos “release notes“. Os “release notes” devem ser sempre lidos a partir do site da Oracle, pois são atualizados com freqüência, ao contrario dos demais manuais.
  2. Se você está começando e não tem a menor intenção de se tornar um DBA, então leia o “2 Day DBA“. Se você pretende trabalhar como DBA, leia de cabo a rabo o “Administrator’s Guide“. Não toque em nada em produção enquanto não ler isso.
  3. Se você não está entendendo bem o “Administrator’s Guide” leia o “Concepts” antes. Você vai ter que ler isso mais cedo ou mais tarde. Dá uma visão geral muito boa.
  4. Todo desenvolvedor que usa Oracle deve ler o “Application Developer’s Guide – Fundamentals” e o “PL/SQL User’s Guide and Reference“. É claro que os DBAs também devem ler tudo, de cabo a rabo.
  5. Algumas ferramentas básicas que o DBA deve conhecer: “SQL*Plus® User’s Guide and Reference” e “Utilities“. Não viva sem elas.
  6. Conhecer profundamente a parte de segurança é investir na manutenção do emprego do DBA: “Backup and Recovery Basics“, “Backup and Recovery Advanced User’s Guide” e pelo menos o “Security Guide
  7. Agora os guias de referência que você não vai conseguir viver sem: “Reference“, SQL Reference“, “Backup and Recovery Reference“, “Error Messages” e “PL/SQL Packages and Types Reference“. Você não vai ler eles inteiros, mas saber como eles estão organizados e onde achar o que precisa rapidamente é fundamental.

E para quem acha que comprando um único livro de Oracle ou tirando uma certificação vai sobreviver no mercado… melhor desistir enquanto é tempo. Boa diversão! :-)