Serialización de las transacciones y error en JDBC-Java con ORACLE

Visitas: 577  
Tiempo total: 11 días con 12:24:47 hrs  

Consiste en prevenir todos los problemas concurrentes que se pueden dar en las transacciones en una base de datos, estos conceptos son una base fundamental en las aplicaciones empresariales, dado que en estas la probabilidad que dos transacciones se ejecuten al mismo tiempo es alta, haciendo que una operación se realice y almacene datos incorrectos provocando pérdidas para la empresa y haciendo un completo relajo en la información.

Este problema debe ser solucionado para que una transacción cumpla con las característica ACID (Atomicity, Consistency, Isolation and Durability). En este caso Isolation o aislamiento significa que la transacción sea independiente y que no afecte a otras.

Problemas de concurrencia

Lecturas sucias

Es una transacción Tr-2 que lee la información que no ha sido confirmada por Tr-1 y que después es abortada, en este caso Tr-2 manipula información falsa que después es almacenada.

lectura sucia

Lecturas no repetibles

Consiste en que la lectura de un registro por Tr-1 en T1, en T2 Tr-2 manipula el registro y lo actualiza, haciendo que Tr-1 en T3 lea un registro con un valor distinto en T1.

lectura no repetida

Lecturas fantasmas

Al igual que una lectura no repetible, en este caso el resultado es un conjunto de filas en T3 distinto que en T1.

lectura fantasma

Niveles de aislamiento

Lecturas no comprometidas

Este es el nivel 1, y es la solución en la cual pueden ocurrir lecturas sucias, lecturas no repetibles y lecturas fantasma.

Lecturas comprometidas

Nivel 2, pueden ocurrir lecturas no repetibles y lecturas fantasma.

Lecturas repetibles

Nivel 3, en este únicamente pueden ocurrir lecturas las lecturas fantasma.

Serialización

Nivel 4, en el cual ninguno de los problemas puede ocurrir.

Error con bloqueo compartido con sentencias SELECT en JDBC-Java para ORACLE

Actualmente me encuentro trabajando en una práctica de bases de datos, por lo cual debo dar solución a las transacciones concurrentes que se pueden dar en una aplicación para bancos, dado que las soluciones de serialización se realizan a través de bloqueos.

Class.forName(STR_DRIVER);
Connection conn=DriverManager.getConnection(STR_URL, STR_USERNAME, STR_PASSWORD);
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);

/* transacciones */

conn.commit();
/* */
conn.rollback();

Puedes consultar la documentación para entender las cadenas indicadas por STR_, y dejo para futuras implementaciones el código anterior, puedes ver también como se establece el auto commit a falso dado que es verdadero por defecto, con el comando setTransactionIsolation() los posibles parámetros serán los siguientes, estableciendo la respectiva solución a utilizar:

TRANSACTION_NONE: Nivel 0
TRANSACTION_READ_UNCOMMITED: Nivel 1
TRANSACTION_READ_COMMITED: Nivel 2
TRANSACTION_REPEATABLE_READ: Nivel 3
TRANSACTION_SERIALIZABLE: Nivel 4, serializacion

Actualmente me encuentro haciendo pruebas, en donde Tr-1 en T1 consulta la información para después actualizarla, Tr-2 en T2 hace lo mismo con el mismo registro y en T3 Tr-1 actualiza el registro. En T4 cuando Tr-2 intenta actualizar la información, se obtiene el siguiente error:

Error: java.sql.SQLException: ORA-08177: can’t serialize access for this transaction

Lo cual significa que la información que se está actualizando no es la misma que en T2. Con esto, se evita la actualización de información falsa en la base de datos pero con el bloqueo compartido que realiza surge el problema de realizar consultas innecesarias por que terminaran en un error al final de la transacción.

Una solución alternativa es reemplazar la consulta SELECT común por:

SELECT campos FROM tabla WHERE condición FOR UPDATE;
También:
SELECT campos FROM tabla WHERE condición FOR UPDATE OF columna;
Inclusive agregando para ambas (Esperar 5 segundos):
WAIT 5

Pero el resultado de JAVA – JDBC sigue siendo el mismo pero esta vez al momento de realizar la consulta Tr-2 en T2:

Error: java.sql.SQLException: ORA-08177: can’t serialize access for this transaction

Funciona correctamente al eliminar la instrucción:

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

Al realizar esto, la transacción Tr-2 espera hasta que Tr-1 realice el COMMIT, al observar esto probé con la siguiente solución:

String sql1 = “LOCK TABLE tabla IN ROW EXCLUSIVE MODE”;
String sql2 = “SELECT columna FROM tabla WHERE id = 4” ;
String sql3 = “UPDATE tabla SET columna = ? WHERE id = 4”;

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);

PreparedStatement pstmt1 = conn.prepareStatement(sql1);
PreparedStatement pstmt2 = conn.prepareStatement(sql2);
PreparedStatement pstmt3 = conn.prepareStatement(sql3);

En resumen, puedes observar como realizo un bloqueo de filas exclusivo en la primera sentencia, la selección en el segundo QUERY y la actualización en la última. El resultado sigue siendo el mismo:

Error: java.sql.SQLException: ORA-08177: can’t serialize access for this transaction

Con el nivel de serialización mi objetivo es evitar los problemas que surgen de la concurrencia correctamente, pero aun después de ejecutar el QUERY para bloquear las filas exclusivamente, la conexión utiliza un bloqueo compartido de nuevo. La sentencia:

LOCK TABLE tabla IN ROW EXCLUSIVE MODE;

La he utilizado usando el CMD de Windows y junto a Oracle SQL Developer y funciona correctamente, esperando en cada sesión a que la otra realice el COMMIT para después realizar la inserción.

Solución

Al momento de realizar esta publicación, me encontraba bajo los conceptos que definen un paradigma, pero después de escuchar algo de buena música, a continuación la solución.

Al tener acceso a la base de datos como administrador, puedes establecer los parámetros:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
COMMIT;

Con esto establecemos el nivel de serialización directamente en la base de datos, y dado esto podemos eliminar las siguientes dos instrucciones en el software:

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);

La primera instrucción la acabamos de realizar en la base de datos, la segunda porque Oracle no utiliza COMMIT por defecto en sus transacciones. Una vez esto y recordando que la prueba directamente en el CMD de Windows y en Oracle SQL Developer funcionaron correctamente, la aplicación va a funcionar sin necesidad de entrar a indagar en cómo fue diseñada la base de datos desde nuestra aplicación.

Finalizando, esta solución funciona al realizar la consulta como:

SELECT campos FROM tabla WHERE condición FOR UPDATE OF columna;

Porque la instrucción SELECT en el nivel de serialización estable un bloqueo compartido, y con FOR UPDATE establece un bloqueo exclusivo, el cual es nuestra solución.

Referencias

[http://www.tic.udc.es/~fbellas/teaching/is-2004-2005/Tema2Apartado2.1.pdf]
[http://es.wikipedia.org/wiki/ACID]
[http://es.wikipedia.org/wiki/Aislamiento_%28ACID%29]
[http://stackoverflow.com/questions/8570440/lock-table-in-oracle-database-using-jdbc-driver]
[http://docs.oracle.com/cd/E17952_01/refman-5.1-en/innodb-locking-reads.html]
[http://www.techonthenet.com/oracle/transactions/lock_table.php]
[http://youtu.be/7lQ4e9qLOTI?t=47m21s]
[http://www.ampliatusoportunidades.com/2012/04/25/que-pasa-cuando-falta-el-pensamiento-sistemico/]
[http://docs.oracle.com/cd/B10500_01/server.920/a96524/c21cnsis.htm#2570]
[http://docs.oracle.com/cd/B10500_01/server.920/a96524/c21cnsis.htm]


Para recibir boletines de información, por favor escribe tu correo electrónico:

Por favor ingrese un correo electrónico valido.
Registrado correctamente!