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.