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 @