quinta-feira, 21 de janeiro de 2016

Corrupção física e lógica no Oracle



por Wagner Aparecido Miranda - W2R Consultoria e Sistemas 


Este artigo relata um fato vivido recentemente em Cliente.

Primeiro vamos aos conceitos:

O que é uma corrupção física?

Um bloco pode estar corrompido se o “CHECKSUM” estiver inválido. E quando isso pode ocorrer?

Quando o cabeçalho do bloco estiver inválido - ocorre início do bloco está corrompido com valores inválidos.

Quando o bloco estiver incompleto – ocorre quando as informações do cabeçalho do bloco não correspondem com o restante do bloco (conhecido também como cauda de bloco).

Quando CHECKSUM estiver inválido – o  (CHECKSUM) é calculado pelo processo (DBWR) ou pelo processo (direct loader) antes de gravar o bloco do disco e armazenar o cabeçalho do bloco.

O parâmetro  DB_BLOCK_CHECKSUM pode ser usado para identificar se o bloco foi alterado por algo externo ao banco de dados e também para depois que o bloco foi gravado pela última vez pelo banco de dados. Toda vez que o bloco é lido e se o parâmetro DB_BLOCK_CHECKSUM estiver diferente de FALSE, o banco de dados calcula o CHECKSUM e compara a informação armazenada no cabeçalho do bloco.

Blocos deslocados – ocorrec quando o banco de dados Oracle detecta que o conteúdo do bloco a ser lido pertence a um bloco diferente e o CHECKSUM é válido. A corrupção de blocos físicos, normalmente são relatados como erros ORA-1578 e a descrição detalhada da corrupção é gravada no arquivo do “alert.log”.

O que é uma corrupção lógica?

Quando o bloco contém a verificação do CHECKSUM válida e a estruturado início do bloco está corrompida (o conteúdo do bloco está corrompido).

Corrupções lógicas não são normalmente gravadas no alert.log. O utilitário DBVerify apresenta os blocos que estão logicamente danificados no bloco.

Quando o parâmetro DB_BLOCK_CHECKING está habilitado, ele grava o erro interno ORA-600 [kddummy_blkchk].

Se o parâmetro DB_BLOCK_CHECKING estiver habilitado e o bloco já estiver logicamente corrompido no disco, a próxima atualização do bloco irá marcar o bloco como “Soft Corrupt” e futuras leituras deste bloco irão produzir o erro ORA-1578. Nesse caso o utilitário DBVerify apresenta a corrupção com o erro "DBV-200: Block, dba, already marked corrupted".

Como identificar os segmentos corrompidos no banco de dados?

Para identificar tanto o dano físico como lógico pode usar:

1.   Utilitário RMAN (Recovery Manager) ou;

2.   Utilitário DB Verify – DBV

§  Utilizando o RMAN o paralelismo torna a validação mais rápida, porque utiliza vários canais.

§  Por “default” os backups via RMAN (não possuem a opção de verificação lógica) só existe a detecção de corrupções de bloco físico. 

Abaixo é apresentado o comando que verifica o banco de dados completo para ambas corrupções (lógica ou física) sem realmente executar  uma cópia de segurança

RMAN> configure device type disk parallelism 4;
RMAN> backup validate check logical database;

ou

RMAN> run {
allocate channel diskl1 type disk;
allocate channel disk2 type disk;
allocate channel disk3 type disk;
allocate channel disk4 type disk;
backup validate check logical database;
}


Como identificar corrupção física ou lógica utilizando o utilitário DBVerify?

O utilitário DBVERIFY identifica corrupções físicas e lógicas, porém esse utilitário não pode ser executado no banco de dados inteiro utilizando somente um único comando. Para executá-lo não é necessário conexão com o banco de dados.

Deve-se indicar cada “datafile”, como no exemplo abaixo:

$ dbv file=[caminho+nome_do_datafile]
      blocksize = [tamanho_do_bloco_do_banco_de_dados]


Qual é o melhor? RMAN ou DBV?

  O RMAN pode ser executado com a opção de paralelismo, tornando-o mais rápido que o DBV que não pode ser executado em paralelo.
  O DBV verifica a existência de blocos vazios. Então levará mais tempo para fazer a varredura de arquivo de dados inteiro.
  Na versão do Oracle 10g o RMAN não pode marcar blocos com extensões livres quando estejam configuradas em LMT (Locally Managed Tablespaces).
  Na versão do Oracle 11g o RMAN verifica as extensões usadas e livres.
     RMAN> VALIDATE DATAFILE 1 BLOCK 10 to 100;. 

DBV> start=10 end=100

O RMAN mantém as informações de corrupção no arquivo de control file (nas visões: v$ database_block_corruption e  v$ backup_corruption). O  DBV não grava as informações de corrupção. As informações podem ser vistas utilizando a consulta abaixo.

    Select * from v$database_block_corruption;

 O RMAN não grava detalhes de corrupção como o que exatamente está corrompido em um bloco relatado como uma lógica bloco corrompido.O DBV relata os detalhes de corrupção na tela ou em um arquivo de log.
O DBV pode digitalizar blocos com um SCN mais elevado do que um determinado SCN.
O DBV não precisa de uma conexão com o banco de dados.

select * from dba_extents
 where file_id = &DATA_FILE_ID
   and &CORRUPTED_BLOCK_ID 
    between block_id AND block_id + blocks - 1;

ou

SELECT e.owner, e.segment_type, e.segment_name, e.partition_name, c.file#
     , greatest(e.block_id, c.block#) corr_start_block#
     , least(e.block_id+e.blocks-1, c.block#+c.blocks-1) corr_end_block#
     , least(e.block_id+e.blocks-1, c.block#+c.blocks-1)
       - greatest(e.block_id, c.block#) + 1 blocks_corrupted
     , null description
  FROM dba_extents e, v$database_block_corruption c
 WHERE e.file_id = c.file#
   AND e.block_id <= c.block# + c.blocks - 1
   AND e.block_id + e.blocks - 1 >= c.block#
UNION
SELECT s.owner, s.segment_type, s.segment_name, s.partition_name, c.file#
     , header_block corr_start_block#
     , header_block corr_end_block#
     , 1 blocks_corrupted
     , 'Segment Header' description
  FROM dba_segments s, v$database_block_corruption c
 WHERE s.header_file = c.file#
   AND s.header_block between c.block# and c.block# + c.blocks - 1
UNION
SELECT null owner, null segment_type, null segment_name, null partition_name, c.file#
     , greatest(f.block_id, c.block#) corr_start_block#
     , least(f.block_id+f.blocks-1, c.block#+c.blocks-1) corr_end_block#
     , least(f.block_id+f.blocks-1, c.block#+c.blocks-1)
       - greatest(f.block_id, c.block#) + 1 blocks_corrupted
     , 'Free Block' description
  FROM dba_free_space f, v$database_block_corruption c
 WHERE f.file_id = c.file#
   AND f.block_id <= c.block# + c.blocks - 1
   AND f.block_id + f.blocks - 1 >= c.block#
order by file#, corr_start_block#;

terça-feira, 15 de dezembro de 2015

Como utilizar o Transaction Isolation Level - SQL Server

por Wagner Aparecido Miranda - W2R Consultoria e Sistemas 

Os Níveis de Isolamento (ou Transaction Isolation Level, do Inglês) não são apenas transações criadas para validar se um processamento gerou erro ou não. Vejo muita gente usando somente o BEGIN TRAN e COMMIT/ROLLBACK pensando que estão usando transações de forma correta, afinal, se der algum problema durante o processamento ele pode voltar com um ROLLBACK e tudo estará tudo como antes.
Bom, olhar somente por essa ótica pode estar errado, justamente pelo fato do Isolation Level ter causas/efeitos mais profundos do que somente garantir que a transação que está rodando no momento dê erro ou não.
Vale lembrar que o SQL Server é um SGBD que implementa o conceito de ACID(Atomicidade, Consistência, Isolamento e Durabilidade) e os níveis de isolamento atuam diretamente neste conceito. Outra coisa que é bom lembrar é que os níveis de isolamento impactam diretamente na quantidade de LOCKs e WAITs gerados no SQL.
Por padrão, o SQL Server tem o Isolation Level configurado para READ COMMITED. Isso pode ser confirmado rodando esta linha de código abaixo, e encontrando o resultado de ISOLATION LEVEL.
DBCC USEROPTIONS
Para exemplificar estas explicações, abra uma conexão com o SQL Server e execute o código abaixo. Ele irá criar um database e duas tabelas com alguns dados, onde serão explicados os tipos de isolamentos e de quebra um exemplo de dead lock.
CREATE DROP DATABASE dbNogare
GO

USE dbNogare
GO

CREATE TABLE tbIsolationLevel
(
    Id INT IDENTITY,
    Col1 varchar(10),
    Col2 varchar(10),
    Col3 varchar(10)
)
GO

INSERT INTO tbIsolationLevel(Col1,Col2,Col3)
VALUES ('AAAAA','BBBBB','CCCCC')
GO 5

CREATE TABLE tbDeadlock ( id INT)
GO

INSERT INTO tbDeadlock(ID) values (1),(2)
GO

Quando alteramos o nivel de isolamento, ele é aplicado à conexão que o alterou. Se você abrir uma nova conexão com o SQL Server e verificar qual é o nivel de isolamento desta transação, você verá que voltou para o padrão. Read Committed.
READ COMMITTED nos garante uma leitura somente do que já está “comitado” no banco, isso é, garante que o dado que está sendo lido foi realmente escrito no banco e não é uma leitura suja ou fantasma, causado por alguma outra transação. Pelo fato dele só ler as informações realmente escritas no banco, se uma outra transação estiver trabalhando com a tabela que você quer ler, o SQL irá esperar a transação liberar a tabela para então fazer a leitura. Isso gera LOCK (pela outra transação) e WAIT (pela sua).
Para entender na prática esse tipo de isolamento, abra duas conexões diferentes com a tabela tbNogare e execute o primeiro bloco de código em uma delas, e o segundo bloco na segunda conexão. Após iniciar a execução do primeiro, vá até a segunda conexão e execute em simultâneo, deixando ambas rodando. Você irá ver o SQL Server terminar de executar a primeira transação para então iniciar a segunda.
/*********  RODAR NA CONEXÃO 1 *********/
BEGIN TRAN
    UPDATE tbIsolationLevel set Col1 = 'DDDDD' where id = 1
    WAITFOR DELAY '00:00:10'
ROLLBACK TRAN

/*********  RODAR NA CONEXÃO 2 *********/
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM tbIsolationLevel

READ UNCOMMITTED evita que o SQL Server dê um LOCK na tabela de leitura por causa de uma transação, isso faz com que também não gere um WAIT em outra transação. Porém, este processo permite uma leitura suja dos dados, entregando a informação “errada” em alguns cenários. Quando se utiliza este nível de isolamento, é possível ler os dados de uma tabela mesmo ela sendo utilizada dentro de uma transação longa que executa vários processos (INSERTSUPDATES DELETES). Estes passos intermediários que são executados antes do COMMIT no final, podem ser lidos e retornados. Quando isso acontece, chamamos de leitura suja (Dirty Read, do inglês).
Veja os códigos abaixo, e execute cada bloco simultaneamente em uma conexão diferente.
/*********  RODAR NA CONEXÃO 1 *********/
BEGIN TRAN
    UPDATE tbIsolationLevel set Col1 = 'DDDDD' where id = 1
    WAITFOR DELAY '00:00:10'
ROLLBACK TRAN
No código abaixo existem duas consultas. Ambas podem ler dados sujos, a primeira alterando o isolation level e a segundo consumindo dados com o hint WITH (NOLOCK). No final, o resultado de ambas serão os mesmos.
/*********  RODAR NA CONEXÃO 2 *********/
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM tbIsolationLevel

-- Usando o hint NOLOCK
SELECT * FROM tbIsolationLevel with (nolock)
Já o REPEATABLE READ garante que a transação que leia uma tabela mais do que uma vez dentro desta mesma transação, possa fazer isso sem ler dados diferentes dos registros já encontrados da primeira vez. Nenhuma alteração (UPDATE) dos dados já lidos anteriormente serão modificados na leitura (são alterados na tabela), mas novos registros (INSERT) são retornados nesta segunda leitura.
Veja nos blocos de código abaixo este processo.
/*********  RODAR NA CONEXÃO 1 *********/
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
    SELECT * FROM tbIsolationLevel
    WAITFOR DELAY '00:00:10'
    SELECT * FROM tbIsolationLevel
ROLLBACK TRAN
Veja o SQL Server gerando um WAIT para executar o UPDATE, mas não o gera quando roda o INSERT, ele simplesmente insere os dados.
/*********  RODAR NA CONEXÃO 2 *********/
UPDATE tbIsolationLevel set Col1 = 'DDDDD' where id = 1

INSERT INTO tbIsolationLevel(Col1,Col2,Col3)
VALUES ('DDDDD','EEEEE','FFFFF')
Uma variação mais completa do REPEATABLE READ é o SERIALIZABLE que bloqueia qualquer modificação de dados nas colunas que são consultadas, independente da modificação ser um UPDATE ou um INSERT. Este nível de isolamento causa um LOCKna transação original e um WAIT na segunda transação tanto para o UPDATE quanto para INSERT.
Veja este processo nos códigos abaixo:
/*********  RODAR NA CONEXÃO 1 *********/
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
    SELECT * FROM tbIsolationLevel
    WAITFOR DELAY '00:00:10'
    SELECT * FROM tbIsolationLevel
ROLLBACK TRAN

/*********  RODAR NA CONEXÃO 2 *********/
UPDATE tbIsolationLevel set Col1 = 'DDDDD' where id = 1

INSERT INTO tbIsolationLevel(Col1,Col2,Col3)
VALUES ('DDDDD','EEEEE','FFFFF')

Uma alternativa para evitar LOCK e WAIT nas tabelas, e também garantir que os dados modificados sejam escritos e não tenha leitura suja é o SNAPSHOT. Ele permite tal ação porque ele copia os dados alterados para a tempdb, possibilitando ler os dados originais durante a transação, mesmo que eles sejam alterados. Quando uma transação tem um snapshot, ela coloca uma flag em todos os registros, para garantir que não foram alterados. Caso isso ocorra, ela altera a flag.
Veja na imagem abaixo a explicação deste processo.
Com o snapshot habilitado no banco, e setado na transação, ele lê os dados originais da tabela mesmo sofrendo modificações por outra transação.
Se os dados lidos não sofrerem nenhuma alteração, nada é escrito na tempDB.


Porém, se algum dado for modificado, o SQL Server continua lendo a tabela, mas a versão original do dado que foi modificado é copiado para a tempDB e na hora da leitura o SQL se encarrega de ler a tempDB e recuperar o dado original.

Esta funcionalidade, por padrão, vem desabilitada do banco de dados, e para utilizar, é necessário habilitar fazendo uma alteração no database. No código abaixo, é possível ver essa alteração.
USE MASTER
GO
ALTER DATABASE dbNogare
SET ALLOW_SNAPSHOT_ISOLATION ON
GO
USE dbNogare
GO
Depois de habilitar o Snapshot Isolation Level no banco de dados, é hora de ver os blocos de códigos que possibilitam este nivel de isolamento.
/*********  RODAR NA CONEXÃO 1 *********/
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
    SELECT * FROM tbIsolationLevel
    WAITFOR DELAY '00:00:10'
    SELECT * FROM tbIsolationLevel
    UPDATE tbIsolationLevel set Col1 = 'XXXXX' where id = 2
    WAITFOR DELAY '00:00:10'
COMMIT TRAN

/*********  RODAR NA CONEXÃO 1 *********/
UPDATE tbIsolationLevel set Col1 = 'NNNNNN' where id = 1
SELECT * FROM tbIsolationLevel

Por fim, o DEAD LOCK também causa um pouco de confusão com sua execução. A causa de um DEAK LOCK é quando uma transação espera uma liberação para seguir em frente, mas a transação que está segurando o processo (causando LOCK) está esperando a primeira transação terminar de processar alguma coisa. A grosso modo, éesperando o terminar e o B esperando o terminar. Eles ficariam um esperando o outro até alguém “desistir”. Para isso, o SQL Server escolhe uma vítima de acordo com o início do processo e o grau de severidade que aquele processo pode ter. Normalmente a transação que começou por ultimo é a vítima, se desligando do processo (o SQL faz isso automaticamente) e dá um rollback nas alterações. Isso faz com que a transação que ganhou o processo termine suas atividades.

Para ver isso através de código, execute os blocos abaixo.
/*********  RODAR NA CONEXÃO 1 *********/
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRAN
    UPDATE tbDeadlock
    SET id = 12
    WHERE id = 2
    WAITFOR DELAY '00:00:10'
    UPDATE tbDeadlock
    SET id = 11
    WHERE id = 1
ROLLBACK TRAN

/*********  RODAR NA CONEXÃO 2 *********/
BEGIN TRAN
    UPDATE tbDeadlock
    SET id = 11
    WHERE id = 1
    WAITFOR DELAY '00:00:10'
    UPDATE tbDeadlock
    SET id = 12
    WHERE id = 2
ROLLBACK TRAN
Quando ocorre o dead lock, a mensagem de erro é assim:

Msg 1205, Level 13, State 45, Line 2 Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.