terça-feira, 1 de maio de 2012

Como resolver problemas com transações Two-Phase-Commit 2/2


Na segunda parte deste artigo apresentaremos como resolver transações distribuídas que não possuam entradas correspondentes na visão DBA_2PC_PENDING e como “limpar” transações distribuídas que estão com status de PREPARED.

SOLUÇÕES
TRANSAÇÕES DISTRIBUÍDAS SEM ENTRADAS CORRESPONDENTES NA VISÃO DBA_2PC_PENDING
Neste caso as visões DBA_2PC_PENDING estão vazias, mas os usuários estão recebendo erros relacionados a transações distribuídas, por exemplo, os erros ORA-2054, ORA-1591.
Normalmente este caso não deve aparecer porem caso seja reproduzível pode ser um “BUG”, deve-se após resolver o problema, verificar no Oracle Support Metalink a atualização de “patches” para a correção da causa do problema. Abaixo são apresentadas algumas soluções de contorno:

  1. Limpar os objetos referenciados por essa transação e importá-las;
  2. Utilizar o parâmetro “_corrupted_rollback_segments” para descartar o segmento de rollback que esteja com problema;
  3. Inserir dados fictícios na visão DBA_2PC_PENDING para a execução de “commit ou rollback” (FORCE).

Para a solução 3 deve-se inserir dados fictícios no dicionário para que a transação possa ser confirmada ou revertida manualmente.

Observe que o banco de dados não será capaz de processar esta transação distribuída e o processo (RECO) deve ser desativado antes de usar este método (deve-se desabilitar o modo de arquivamento automático “archive log mode”).

Importante: Realizar um BACKUP do seu banco de dados antes de usar este método.
Transações que ficaram presas são a causa do erro ORA-01591, por isso a necessidade de limpar essas entradas, deve-se utilizar a package DBMS_TRANSACTION.

execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('id_da_transacao');

O exemplo a seguir descreve como diagnosticar e resolver este caso. Suponha que  os usuários estão recebendo a mensagem abaixo:

ORA-1591: lock held by in-doubt distributed transaction 1.92.66874.
E a consulta abaixo não retorne linhas.
SQL> select local_tran_id, state
       from dba_2pc_pending
      where local_tran_id='1.92.66874';

no rows selected

Além disso, ao consultar o segmento de “undo”  é apresentada a transação “1.92.66874” que está em modo PREPARED.

SQL> SELECT KTUXEUSN, KTUXESLT, KTUXESQN, /* Transaction ID */
            KTUXESTA Status, KTUXECFL Flags
       FROM x$ktuxe
      WHERE ktuxesta!='INACTIVE'
        AND ktuxeusn= 1;  /* <== Substituir este valor pelo
                                 número do segmento de rollback
                                 de sua transação */

SQL> KTUXEUSN   KTUXESLT   KTUXESQN   STATUS           FLAGS
     ---------- ---------- ---------- ---------------- --------------------
     1          92         66874      PREPARED         SCO|COL|REV|DEAD

E se realiza uma tentativa de realizar um “commit force” ou “rollback force” desta transação.
SQL> commit force '1.92.66874';
ORA-02058: no prepared transaction found with ID 1.92.66874
O erro ORA-02058 são gerados porque as visões DBA_2PC_PENDING e DBA_2PC_NEIGHBORS estão vazias. Para usar o “commit force” ou “rollback force” deve-se inserir um registro fictício na visão DBA_2PC_PENDING, conforme apresentado abaixo:

SQL> alter system disable distributed recovery;

SQL> insert into pending_trans$ ( LOCAL_TRAN_ID, GLOBAL_TRAN_FMT, GLOBAL_ORACLE_ID,
STATE, STATUS, SESSION_VECTOR, RECO_VECTOR,TYPE#, FAIL_TIME, RECO_TIME)
values( '1.92.66874',    /* Altere o valor de sua transação local (LOCAL_TRAN_ID)  */
306206,                  /*                                                      */
'XXXXXXX.12345.1.2.3',   /* Esses valores podem ser utilizados sem qualquer      */
'prepared','P',          /* modificação. Muitos desses valores sáo constantes    */
hextoraw( '00000001' ),  /*                                                      */
hextoraw( '00000000' ),  /*                                                      */
0, sysdate, sysdate );

SQL> insert into pending_sessions$
values( '1.92.66874',   /* <==Altere para sua transação local (LOCAL_TRAN_ID)*/
1, hextoraw('05004F003A1500000104'), 'C', 0, 30258592, '', 146);
SQL> commit;

SQL> commit force '1.92.66874';

Se a instrução “commit force” gerar um erro, anote a mensagem de erro e execute os comandos abaixo:

SQL> delete from pending_trans$ where local_tran_id='1.92.66874';
SQL> delete from pending_sessions$ where local_tran_id='1.92.66874';
SQL> commit;
SQL> alter system enable distributed recovery;

Caso contrário executar o processo de entrada fictícia do dicionário, usando:

SQL> alter system enable distributed recovery;
SQL> connect / as sysdba
SQL> COMMIT;

Use a consulta abaixo para recuperar o valor para do parâmetro “_smu_debug_mod”.

SQL> col Parameter for a20
SQL> col "Session Value" for a20
SQL> col "Instance Value" for a20

SQL> SELECT a.ksppinm "Parameter",b.ksppstvl "Session Value",
            c.ksppstvl "Instance Value"
       FROM x$ksppi a, x$ksppcv b, x$ksppsv c
      WHERE a.indx = b.indx
        AND a.indx = c.indx
        AND a.ksppinm = '_smu_debug_mode';

Configure o parâmetro “_smu_debug_mode” para 4.

SQL> alter system  set "_smu_debug_mode" = 4; 
           /* Configurar com esse valor se estiver usando
              o segmento de UNDO em modo automático */

A partir da versão 9.2x o comando “alter session” pode ser utilizado dinamicamente.

SQL> commit;  /* Isto é para evitar o erro ORA-01453 durante a
                 limpeza utilizando a package “purge_lost_db_entry” */

SQL> exec dbms_transaction.purge_lost_db_entry( '1.92.66874' );

SQL> commit;

SQL> alter system set "_smu_debug_mode" = ;

SQL> commit;

COMO LIMPAR UMA TRANSAÇÃO DISTRIBUÍDA COM “STATUS” PREPARED, QUANDO UM COMMIT OU ROLLBACK FORCE, TRAVAR.

Se a mensagem abaixo for apresentada.
ORA-01591: lock held by in-doubt distributed transaction 44.88.85589

Deve-se verificar se o registro existe na visão DBA_2PC_PENDING e qual o segmento de “rollback” está em uso, execuntando o comando abaixo.

SQL> SELECT LOCAL_TRAN_ID,STATE FROM DBA_2PC_PENDING;
LOCAL_TRAN_ID   STATE
--------------  --------
44.88.85589     prepared


SQL> SELECT KTUXEUSN, KTUXESLT, KTUXESQN, /* Transaction ID */
            KTUXESTA Status, KTUXECFL Flags
       FROM x$ktuxe
      WHERE ktuxesta!='INACTIVE'
        AND ktuxeusn= 44; /* <== Alterar o valor para o número da transação
                                 no seu segmento de UNDO que é exibido na
                                 primeira parte do ID da transação */

KTUXEUSN   KTUXESLT   KTUXESQN   STATUS           FLAGS
---------- ---------- ---------- ---------------- ------------------------
44         88         85589      PREPARED         SCO|COL|REV|DEAD


SQL> Commit force 44.88.85589;

ou

SQL> rollback force 44.88.85589;

Se ao executar o “COMMIT” ou “ROLLBACK FORCE” e o comando travar, o banco de dados irá assinalar o evento de espera (wait system events)  "free global transaction table entry”.

Para limpar esta transação, deve-se utilizar a package abaixo:
SQL> EXECUTE DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('44.88.85589');
BEGIN DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('44.88.85589'); END;

*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.DBMS_TRANSACTION", line 94
ORA-06512: at line 1

Caso essa mensagem de erro apareça pode-se executar umas das soluções:


1.   Gerar entradas de dados nas visões das DBA_2PC_PENDING e DBA_2PC_NEIGHBORS sem transações correspondentes;
2.   Gerar transações distribuídas sem entradas correspondentes nas visões DBA_2PC_PENDING e DBA_2PC_NEIGHBORS

Para a solução 1, executar os comandos abaixo:

SQL> delete from sys.pending_trans$ where local_tran_id = '44.88.85589';
SQL> delete from sys.pending_sessions$ where local_tran_id = '44.88.85589';
SQL> delete from sys.pending_sub_sessions$
      where local_tran_id ='44.88.85589';
SQL> commit;


Para a solução 2, executar os passos abaixo:

Inserir registros fictícios, como descritos na seção (ver: Transações distribuídas sem entradas correspondentes na visão DBA_2PC_PENDING). Para transações distribuídas sem registros correspondentes na visão DBA_2PC_PENDING, executar.

SQL> Commit force '44.88.85589';

Para limpar transações pendentes, usar:

SQL> exec dbms_transaction.purge_lost_db_entry('44.88.85589');


Para escrever esse artigo usei como referência a nota da Oracle (Note:126069.1 -  Manually Resolving In-Doubt Transactions: Different Scenario's).


Rubens Oliveira
DBA Oracle Consultor
olivert.dba@consultant.com