sábado, 1 de septiembre de 2007

MySQL: Niveles de aislamiento (Isolations levels)

En esta ocasión voy a abordar la temática de las bases de datos. Para ello me voy a referir a un DBMS tan extendido en uso como criticado por sus carencias con respecto a servidores de bases de datos comerciales: MySQL. Concretamente me voy a referir a una característica que a mí me ha encantado y es que MySQL nos permite especificar el nivel de aislamiento que deseamos para las transacciones.
Doy por hecho que la persona que está leyendo este artículo conoce, cuanto menos, lo que son las transacciones dentro de los SGBDR. Las especificaciones ANSI establecen que una transacción debe de cumplir con la norma ACID:

  • Atomic (Atomicidad): Establece que las operaciones que se realizan dentro de una transacción son efectuadas de manera atómica. Esto significa que la transacción bien se cometa o bien se anule siempre debe de ser al completo.

  • Consistent (Consistencia): Esta es una característica que más bien depende del programador del DBMS. Define que la base de datos debe de quedar de manera consistente tras la finalización (con éxito o no) de la transacción.

  • Isolation (Aislamiento): Determina que las distintas transacciones que se estén ejecutando lo hagan de forma aislada. Esto es, que los resultados de una transacción no son visibles por el resto de transacciones en curso hasta que dicha transacción finalize.

  • Durable (Durable): Esta regla impone que los cambios realizados por una transacción cometida deben de ser durables en el tiempo, deben de permanecer.
Pues bien, repasado este concepto ya podemos mostrar los problemas asociados a los distintos tipos de transacciones:

  • Dirty reads (Lecturas sucias): Es el problema más importante de todos. Supone que las transacciones en curso puedan leer el resultado de otras transacciones aún no confirmadas. Por ejemplo, vamos a suponer que tenemos dos transacciones activas (A y B). Inicialmente la transacción A lee un valor X de una tabla que, por ejemplo, es 0. Durante dicha transacción el valor de X se cambia a 10, pero aún la transacción no se ha cometido, por lo que en la tabla X = 0. Ahora la transacción B accede al valor X y obtiene ¡¡X = 10!! un valor que está usando A y que aún no se ha cometido. Supongamos que ahora se anula la transacción A. El resultado sería X = 0 en la tabla y X = 10 en la transacción B por lo que hemos llegado a un estado muy grave de inconsistencia.

  • Non-Repeatable reads (Lecturas no repetibles): Ocurre cuando una transacción activa vuelve a leer un dato cuyo valor difiere con respecto al de la anterior lectura. Lo vemos más claro con un ejemplo. Supongamos que una transacción activa, A, lee un valor X = 0. En este momento otra transacción B modifica el valor de X, por ejemplo X = 10, y se comete dicha transacción. Si ahora duracte la transacción A se vuelve a leer el valor X obtendríamos 10 en lugar del 0 que se esperaba. Aunque a primera vista este problema no parezca muy importante en realidad sí que lo es, sobre todo cuando X es una clave primaria o ajena. En este caso se puede originar una gran pérdida de consistencia en nuestra base de datos.

  • Phantom reads (Lecturas fantasma): Este supone el menor problema que se nos puede plantear con respecto a las transacciones. Sucede cuando una transacción en un momento lanza una consulta de selección con una condición y recibe en ese momento N filas y posteriormente vuelve a lanzar la misma consulta junto con la misma condición y recibe M filas con M > N. Esto es debido a que durante el intervalo que va de la primera a la segunda lectura se insertaron nuevas filas que cumplen la condición impuesta en la consulta.

Debido a estos problemas el ANSI establece diferentes niveles de aislamiento (isolations levels) para solventarlos. Hay que tener en cuenta que a mayor nivel de aislamiento mayores son los sacrificios que se hacen con respecto a la concurrencia y al rendimiento. Vamos a enumerar los niveles de aislamiento desde el menor hasta el mayor:

  • Read uncommitted (Lectura sin confirmación): En la práctica casi no se suele utilizar este nivel de aislamiento ya que es propenso a sufrir todos los problemas anteriormente descritos. En este nivel una transacción puede ver los resultados de transacciones aún no cometidas. Podemos apreciar que en este nivel no existe aislamiento alguno entre transacciones.

  • Read committed (Lectura confirmada): Es el predeterminado para la mayoría de gestores de bases de datos relacionales. Supone que dentro de una transacción únicamente se pueden ver los cambios de las transacciones ya cometidas. Soluciona el problema de las lecturas sucias, pero no el de las lecturas no repetibles ni tampoco el de las lecturas fantasmas.

  • Repeatable read (Lectura repetible): Define que cualquier tupla leída durante el transcurso de una transacción es bloqueada. De esta forma se soluciona, además de las lecturas sucias, el problema de las lecturas no repetibles. Aunque en dicho nivel se siguen dando las lecturas fantasmas.

  • Serializable (Lecturas en serie): Soluciona todos los problemas descritos. Para ello ordena las transacciones con el objetivo de que no entren en conflicto. Este nivel de aislamiento es bastante problemático ya que es, con diferencia, el que más sacrifica en rendimiento y concurrencia.

Hasta aquí hemos llegado con la teoría, ahora vamos a ver como MySQL nos permite trabajar con cualquiera de estos niveles de aislamiento (eso sí, siempre que utilicemos el motor de tabla InnoDB). Si deseamos conocer el nivel de aislamiento con el que actualmente está trabajando nuestro servidor no tenemos más que lanzar la sentencia: SHOW VARIABLES LIKE 'tx_isolation'. De esta manera la salida que obtendremos será similar a la siguiente:

En mi caso con MySQL 5 aparece que el nivel de aislamiento es el de REPEATABLE-READ. Para cambiarlo directamente desde la línea de comando de MySQL lo podemos hacer con la sentencia:

SET {GLOBAL | SESSION} TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITED | REPEATABLE READ | SERIALIZABLE}

A través de ella podemos especificar el nivel de aislamiento tanto para la sesión actual (SESSION) como para el servidor (GLOBAL). También es posible establecer el nivel de aislamiento al arranzar el servidor con el argumento --transation-isolation o modificando el fichero de configuración del servidor my.cfg.
Para terminar sólo quiero especificar que esta cualidad de mysql es válida a partir de la versión 4.0.5 y al igual que en el artículo anterior estoy totalmente abierto a sugerencias, críticas y demás. Un saludo.

1 comentario:

Anónimo dijo...

Muy buen articulo, muy claro para entender las diferencias entre las diferentes opciones de isolations levels