Serialización de las transacciones y error en JDBC-Java con ORACLE
Tiempo total: 12 días con 2:28:0 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.
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.
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.
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]