Transacciones Autónomas, COMMITs, ROLLBACKs y gestión de errores.

Una transacción es una ‘unidad de negocio atómica’, esto es: un conjunto de operaciones que debe funcionar como un todo. De esta forma, una transacción se completará o se revertirá como un unidad: o todas sus operaciones se completan o todas sus operaciones se revierten.

La gestión de transacciones en los SGBDR’s depende mayormente de qué SGBDR se trate, y cada uno las trata e implementa a su manera particular.

Pero hay veces (pocas, eso sí) que una transacción no es suficiente. Mientras que SQL Server maneja el concepto de ‘transacciones anidadas’ (“nested transactions“), Oracle utiliza las llamadas ‘transacciones autónomas’ (“autonomous transactions“).

El concepto que está detrás de estas transacciones consiste en que un proceso con una transacción activa puede llamar a un subprograma (procedimiento, función, paquete…) que comience una transacción independiente de la que la llamó, y por tanto no sujeta a los posibles ‘COMMITs’ o ‘ROLLBACKs’ de ésta. A su vez, la transacción autónoma (bien con COMMIT, bien con ROLLBACK) no influirá en el estado de la transacción desde la que fue invocada.

Esto es así en la teoría. Pero en el mundo real puede darse algún caso ‘especial’.

Vamos a verlo con más profundidad utilizando nuestra tabla PRUEBA01:

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA01 ORDER BY 1
  2  /

      ID_N C_TXT
---------- -------------------------
         1 UNO
         2 DOS
         3 TRES
         4 CUATRO
         5 CINCO
         6 SEIS
        11 ONCE
        21 VEINTIUNO
        22 VEINTIDOS
        23 VEINTITRES
        24 VEINTICUATRO
        25 VEINTICINCO
        30 TREINTA
        31 TREINTAYUNO

14 filas seleccionadas.

Vamos a crear una tabla con la misma estructura:

carlosal@db01.xxxxxx> CREATE TABLE PRUEBA20 AS
  2  SELECT * FROM PRUEBA01
  3  WHERE 1=0
  4  /

Tabla creada.

Ahora utilizaremos dos procedimientos almacenados que insertan filas en cada una de estas tablas (PRUEBA01 y PRUEBA20), con la particularidad de que además uno llamará al otro QUE INICIARÁ UNA TRANSACCIÓN AUTÓNOMA:

carlosal@db01.xxxxxx> CREATE OR REPLACE
  2  PROCEDURE PROC20 (p_ID_N NUMBER, p_C_TXT VARCHAR2)
  3  AS
  4  PRAGMA AUTONOMOUS_TRANSACTION;
  5  BEGIN
  6     INSERT INTO PRUEBA20 (ID_N, C_TXT)
  7                 VALUES (p_ID_N, p_C_TXT);
  8     COMMIT;
  9  END;
 10  /

Procedimiento creado.

carlosal@db01.xxxxxx> CREATE OR REPLACE
  2  PROCEDURE PROC01 (p_ID_N NUMBER, p_C_TXT VARCHAR2)
  3  AS
  4  BEGIN
  5     INSERT INTO PRUEBA01 (ID_N, C_TXT)
  6                 VALUES (p_ID_N, p_C_TXT);
  7
  8     PROC20 (p_ID_N, p_C_TXT);
  9
 10  END;
 11  /

Procedimiento creado.

Y ahora podemos verificar la autonomía de las transacciones:


carlosal@db01.xxxxxx> EXEC PROC01 (32,'TREINTAYDOS');

Procedimiento PL/SQL terminado correctamente.

carlosal@db01.xxxxxx> ROLLBACK;

Rollback terminado.

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA01
  2  ORDER BY 1
  3  /

      ID_N C_TXT
---------- -------------------------
         1 UNO
         2 DOS
         3 TRES
         4 CUATRO
         5 CINCO
         6 SEIS
        11 ONCE
        21 VEINTIUNO
        22 VEINTIDOS
        23 VEINTITRES
        24 VEINTICUATRO
        25 VEINTICINCO
        30 TREINTA
        31 TREINTAYUNO

14 filas seleccionadas.

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA20
  2  ORDER BY 1
  3  /

      ID_N C_TXT
---------- -------------------------
        32 TREINTAYDOS

carlosal@db01.xxxxxx>

Efectivamente, ‘TREINTAYDOS’ está en PRUEBA20, pero no en PRUEBA01.

Al revés también funciona:

carlosal@db01.xxxxxx> CREATE OR REPLACE
  2  PROCEDURE PROC20 (p_ID_N NUMBER, p_C_TXT VARCHAR2)
  3  AS
  4  PRAGMA AUTONOMOUS_TRANSACTION;
  5  BEGIN
  6     INSERT INTO PRUEBA20 (ID_N, C_TXT)
  7                 VALUES (p_ID_N, p_C_TXT);
  8     --COMMIT;
  9     ROLLBACK;
 10  END;
 11  /

Procedimiento creado.

carlosal@db01.xxxxxx> EXEC PROC01 (33,'TREINTAYTRES');

Procedimiento PL/SQL terminado correctamente.

carlosal@db01.xxxxxx> COMMIT
  2  /

Confirmación terminada.

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA01
  2  ORDER BY 1
  3  /

      ID_N C_TXT
---------- -------------------------
         1 UNO
         2 DOS
         3 TRES
         4 CUATRO
         5 CINCO
         6 SEIS
        11 ONCE
        21 VEINTIUNO
        22 VEINTIDOS
        23 VEINTITRES
        24 VEINTICUATRO
        25 VEINTICINCO
        30 TREINTA
        31 TREINTAYUNO
        33 TREINTAYTRES

15 filas seleccionadas.

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA20
  2  ORDER BY 1
  3  /

      ID_N C_TXT
---------- -------------------------
        32 TREINTAYDOS

carlosal@db01.xxxxxx>

Ahora ‘TREINTAYTRES’ está en PRUEBA01, pero no en PRUEBA20.

Pero vamos a hacer la cosa un poco más interesante:

carlosal@db01.xxxxxx> ALTER TABLE PRUEBA20
  2  ADD CONSTRAINT PRUEBA20_PK PRIMARY KEY(ID_N)
  3  /

Tabla modificada.

carlosal@db01.xxxxxx> EXEC PROC01 (32,'TREINTAYDOS');
BEGIN PROC01 (32,'TREINTAYDOS'); END;

*
ERROR en línea 1:
ORA-00001: unique constraint (CARLOSAL.PRUEBA20_PK) violated
ORA-06512: at "CARLOSAL.PROC20", line 5
ORA-06512: at "CARLOSAL.PROC01", line 7
ORA-06512: at line 1

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA01
  2   ORDER BY 1
  3  /

      ID_N C_TXT
---------- -------------------------
         1 UNO
         2 DOS
         3 TRES
         4 CUATRO
         5 CINCO
         6 SEIS
        11 ONCE
        21 VEINTIUNO
        22 VEINTIDOS
        23 VEINTITRES
        24 VEINTICUATRO
        25 VEINTICINCO
        30 TREINTA
        31 TREINTAYUNO
        33 TREINTAYTRES

15 filas seleccionadas.

carlosal@db01.xxxxxx>

Hemos visto que la inserción en PRUEBA20 ha fallado por la restricción de la ‘PRIMARY KEY’ que hemos creado, pero ‘TREINTAYDOS’ TAMPOCO ESTÁ EN PRUEBA01.

¿Qué ha ocurrido entonces?¿La transacción autónoma de PROC20 ha hecho un ROLLBACK de la transacción de PROC01 desde donde fue invocada? ¿Eso no va en contra de lo que son las transacciones autónomas?

No. El problema no es de que la transacción autónoma de PROC20 haga un ROLLBACK de la de PROC01, sino que la NO GESTIÓN de los errores (excepciones) en PROC20 hacen que las excepciones no tratadas en un procedimiento llamado se propaguen al procedimiento llamante, que al no tener a su vez tratamiento de errores provoque el ‘ROLLBACK’ automático en éste (PROC01). El problema, pues, no es de la autonomía de las transacciones, sino de la (no) gestión de excepciones.

Un simple ‘WHEN OTHERS THEN NULL’ basta para demostrarlo.

carlosal@db01.xxxxxx> CREATE OR REPLACE
  2  PROCEDURE PROC01 (p_ID_N NUMBER, p_C_TXT VARCHAR2)
  3  AS
  4  BEGIN
  5     INSERT INTO PRUEBA01 (ID_N, C_TXT)
  6                 VALUES (p_ID_N, p_C_TXT);
  7
  8     PROC20 (p_ID_N, p_C_TXT);
  9  EXCEPTION
 10     WHEN OTHERS THEN NULL;
 11  END;
 12  /

Procedimiento creado.

carlosal@db01.xxxxxx>
carlosal@db01.xxxxxx> EXEC PROC01 (32,'TREINTAYDOS');

Procedimiento PL/SQL terminado correctamente.

carlosal@db01.xxxxxx> SELECT * FROM PRUEBA01
  2  order by 1
  3  /

      ID_N C_TXT
---------- -------------------------
         1 UNO
         2 DOS
         3 TRES
         4 CUATRO
         5 CINCO
         6 SEIS
        11 ONCE
        21 VEINTIUNO
        22 VEINTIDOS
        23 VEINTITRES
        24 VEINTICUATRO
        25 VEINTICINCO
        30 TREINTA
        31 TREINTAYUNO
        32 TREINTAYDOS
        33 TREINTAYTRES

16 filas seleccionadas.

Y esta es otra razón más para siempre, siempre, hacer una gestión de errores en los procesos.

Otra cosa: ‘WHEN OTHERS THEN NULL’ se ha utlizado sólo como ejemplo ilustrativo. Una correcta gestión de errores nunca debería tener este tipo de cláusulas.

Saludos.

Carlos.

Una respuesta a Transacciones Autónomas, COMMITs, ROLLBACKs y gestión de errores.

  1. Jorge Bazan dice:

    Excelente post!!!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: