Desenvolvimento

25 mar, 2013

Trabalhando com transações

Publicidade

A ideia de se trabalhar com transações foi uma grande invenção na história dos bancos de dados. Elas facilitam enormemente o controle de operações, além de garantirem a integridade dos dados.

Resumindo: frequentemente a dupla COMMIT/ROLLBACK salva a pátria!

O DB2, assim como muitos outros SGBDs, adota o AUTOCOMMIT como padrão. Ou seja, se o seu código não especifica um COMMIT, o DB2 entende que seu código contém uma transação implícita e conclui a transação quando o código chegar ao fim.

Ok, então se o código não tiver um COMMIT, a transação vai ser concluída com sucesso. Mas se você escrever o COMMIT no lugar errado, pode ter dores de cabeça.

Vejamos o exemplo de um procedimento armazenado que transfere dados entre as tabelas TABELA1 e TABELA2 (script completo – em sua forma original – é apresentado no final desta página). Observe que você deve salvar essas declarações em dois arquivos separados e depois rodar cada um deles. Primeiro crie as tabelas (arquivo criatabelas.ddl) e depois crie a rotina (arquivo meuscript.db2).

$ db2 -tvf criatabelas.ddl

$ db2  -td@ -vf  meuscript.db2

Em seguida, execute a procedure e veja o resultado na tabela.

$ db2  "CALL meuesquema.procedimento1 ('2013-01-22 00:00:00', '2013-03-30 00:00:00')"

  Return Status = 0

$ db2 "select * from meuesquema.tabela2"
CAMPO1                                 	CAMPO2                                         	CAMPO3                    
-------------------------------- -------------------------------------------------- --------------------------

  0 record(s)

Pois é, meu amigo. Quando viu o “Return Status = 0” como resultado da chamada da rotina, você acreditou que ela tinha rodado corretamente, certo? Se isso te consola, saiba que você não é o primeiro e nem o último em cair nessa cilada.

O que acontece é que o código da rotina contém dois problemas. Teoricamente, toda rotina do DB2 que altera dados (INSERT, UPDATE, DELETE) deve incluir a cláusula MODIFIES SQL DATA. Assim, as primeiras linhas da rotina (linhas 9 a 18 da listagem) devem ser substituídas pelas linhas a seguir:

CREATE PROCEDURE meuesquema.procedimento1 (
    startDate   TIMESTAMP,
    endDate 	TIMESTAMP)
LANGUAGE SQL
MODIFIES SQL DATA
DYNAMIC RESULT SETS 1
BEGIN

O segundo erro é a colocação do COMMIT, que deveria ser o último comando da rotina. Portanto, as linhas finais da rotina (61 a 72) também devem ser alteradas. Uma forma de fazer isso é substituindo aquelas linhas pelas que apresento a seguir:

IF intError > 0 THEN
        ROLLBACK;
        RETURN intError ;
    END IF;

    COMMIT;

END
@

Para testar o novo código, altere o arquivo meuscrip.db2, apague a rotina existente, recrie a rotina com o novo script e execute-a novamente. Aí sim você verá sua transação sendo executada corretamente!

$ db2  -td@ -vf  meuscript.db2
DB20000I  The SQL command completed successfully.
	$ db2  "CALL meuesquema.procedimento1 ('2013-01-22 00:00:00', '2013-03-30 00:00:00')"

  Return Status = 0

$ db2 "select * from meuesquema.tabela2"

CAMPO1                      	CAMPO2                  CAMPO3                    
------------------------- ----------------------- --------------------------
WAGNER                     	wcrivelini                1900-01-01-00.00.00.00000

 1 record(s) selected.

São dicas simples, mas evitam que você tenha problemas sérios com suas rotinas.

Por hoje é só.

-- script DDL (criatabelas.ddl)
-- criacao de tabelas
CREATE TABLE meuesquema.tabela1 (nome VARCHAR(50), userid VARCHAR(50), start_timestamp TIMESTAMP , end_timestamp TIMESTAMP );

INSERT INTO meuesquema.tabela1 VALUES ('wagner', 'wcrivelini', '2013-03-01 0:00:00', null);

CREATE TABLE meuesquema.tabela2 (campo1 VARCHAR(50), campo2 VARCHAR(50), campo3 TIMESTAMP );

-- script da rotina (meuscript.db2)
--================================================================
--procedimento armazenado contendo erro na definição da transação
--================================================================

CREATE PROCEDURE meuesquema.procedimento1 (
    startDate   TIMESTAMP,
    endDate 	TIMESTAMP)
LANGUAGE SQL
DYNAMIC RESULT SETS 1
BEGIN

--===============================================
--error handling
--===============================================

    DECLARE SQLSTATE	CHAR(5) DEFAULT '00000';
    DECLARE l_error     	CHAR(5) DEFAULT '00000';
    DECLARE intError    	INT;

    DECLARE CONTINUE HANDLER FOR NOT FOUND
    SET l_error = '00000';

    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING

    BEGIN
        SET l_error = SQLSTATE;

        IF SUBSTR(l_error, 1, 1) >= '5' AND SUBSTR(l_error, 1, 1) <= '9' THEN
            RESIGNAL;
        END IF;
    END;

--===============================================
-- business logic
--===============================================
    INSERT INTO meuesquema.tabela2 (campo1, campo2, campo3)
    SELECT 
        UPPER(T.NOME) AS CAMPO1,
        T.USERID AS CAMPO2,
        COALESCE(T.END_TIMESTAMP, '00:00:00') AS CAMPO3
    FROM  meuesquema.tabela1 T
    WHERE T.START_TIMESTAMP BETWEEN startDate  AND endDate;

    SET l_error = SQLSTATE ;

    IF LEFT(l_error, 2 ) > '02' THEN
        SET intError = intError + 1 ;
    END IF;

--===============================================
-- finishing transaction
=================================================
    IF intError = 0 THEN
        COMMIT;
        RETURN 0;
    ELSE
        ROLLBACK;
        RETURN intError ;
    END IF;

END
@