Concurrencia optimista en bases de datos
Para solventar esta traba a la hora de actualizar una base de datos se pueden esgrimir dos recursos o modelos: la concurrencia pesimista y la concurrencia optimista. Los objetos que manipulan datos en los distintos lenguajes de programación están preparados para manejar ambas técnicas, por lo que no nos va a resultar nada complicado aplicar una u otra. Nosotros apostamos desde siempre por la concurrencia optimista, sobre todo en determinados entornos, pero explicaremos brevemente en que consisten los dos procedimientos.
La concurrencia pesimista implica bloquear filas, o registros, en el origen de datos para impedir que otros usuarios modifiquen la información, de tal forma que el usuario actual resulte afectado. En un modelo pesimista, cuando un usuario realiza una acción que hace que se aplique un bloqueo, otros usuarios no pueden realizar acciones que entrarían en conflicto con ese bloqueo hasta que el propietario del mismo lo libere. Este modelo se utiliza principalmente en aquellos entornos en los que hay mucha contención de datos, de manera que el costo de protegerlos mediante bloqueos es menor que el costo de deshacer transacciones si se producen conflictos de concurrencia.
La concurrencia pesimista puede resultar útil en ambientes donde los tiempos de bloqueo son cortos, por ejemplo la actualización automática de registros, según determinadas reglas, mediante software. Sin embargo, es una técnica que consume recursos del servidor, necesita de una conexión persistente con el gestor de datos y no resulta nada escalable cuando los usuarios, interactuando con los datos, hacen que los registros queden bloqueados durante períodos de tiempo relativamente largos (mientras se modifican las líneas de una factura, por ejemplo).
Por el contrario, utilizando el sistema de concurrencia optimista no se bloquean filas cuando se lee, sino que se realizan copias locales desconectadas de los datos y, una vez se hayan editado, se vuelca la nueva información sobre la base de datos original. En este caso se produce el problema al que aludíamos al principio, ya que en el tiempo que nosotros hemos estado modificando los datos, otro usuario ha podido acceder al registro y haber realizado sus propias modificaciones. Es, entonces, la aplicación informática la que debe determinar si la información se han modificado o no desde que se leyó. Esta técnica, aunque parezca más farragosa de implementar, mejora el rendimiento del sistema y la velocidad de acceso de todos usuarios, ya que el origen de datos no se encuentra nunca bloqueado y puede servir información continuamente y bajo demanda.
En el momento en que un equipo intente modificar determinados registros y detecte que han sido ya modificados con respecto a la información original que tenía desde que se leyó, en el modelo de concurrencia optimista se considera que hay una infracción. La pericia del programador consistirá en detectar dichas infracciones y saber actuar a tal efecto, dependiendo del proceso que se esté realizando y de las circunstancias puntuales. Esto es, una infracción puede resolverse sobrescribiendo los nuevos datos o manteniendo las modificaciones sin realizar. En función de las características del proyecto y de las necesidades del cliente habrá que hacer una u otra cosa.
Vamos a ver un ejemplo práctico haciendo uso de una pequeña tabla de tres campos y un único registro. La tabla original podría ser como la siguiente:
IdCliente | Apellido | Nombre |
101 | Martínez | Cris |
Imaginemos que un Usuario_1
lee la fila anterior de la base de datos a las 12:00 AM. Veamos una representación gráfica de los tres valores que nos ocupan: el valor original (valor cuando se leyó el registro), el valor actual (el que modifica Usuario_1
en local) y el valor en la base de datos (el valor que se encuentra registrado en la tabla).
Nombre de columna | Valor original | Valor actual | Valor en la base de datos |
IdCliente | 101 | 101 | 101 |
Apellido | Martínez | Martínez | Martínez |
Nombre | Cris | Cris | Cris |
En este caso los tres valores coinciden, porque Usuario_1
todavía no ha hecho ninguna modificación, simplemente ha extraído la información.
A las 12:01 AM, un nuevo Usuario_2
recurre al servidor para leer la misma fila, y a las 12:03 AM edita el campo Nombre
(cambia «Cris» por «Cristina») y actualiza la base de datos. Nuestra representación visual del baile de cadenas de texto sería ahora la que sigue:
Nombre de columna | Valor original | Valor actual | Valor en la base de datos |
IdCliente | 101 | 101 | 101 |
Apellido | Martínez | Martínez | Martínez |
Nombre | Cris | Cristina | Cris |
La actualización se realiza correctamente porque los valores contenidos en la base da datos en el momento de renovar la fila coinciden con los valores originales de tenía Usuario_2
(«Cris», en la base de datos, es igual que el dato «Cris» del momento de la lectura); no existe ninguna infracción para Usuario_2
.
Vamos a imaginar ahora que Usuario_1
termina de realizar sus modificaciones a las 12:05 AM (cambia «Cris», su lectura, por «María Cristina»). Veamos la representación:
Nombre de columna | Valor original | Valor actual | Valor en la base de datos |
IdCliente | 101 | 101 | 101 |
Apellido | Martínez | Martínez | Martínez |
Nombre | Cris | María Cristina | Cristina |
Al intentar actualizar se va a encontrar con una infracción de la concurrencia optimista, ya que el valor actual de la base de datos («Cristina») no coincide con el valor que él esperaba para ese campo («Cris») porque Usuario_2
lo había modificado previamente. Es ahora el momento en el que hay que tomar la decisión de sobrescribir los cambios realizados o, por el contrario, cancelar la actualización. Como antes decíamos, la elección de una u otra acción dependerá de la situación en concreto y de las querencias del cliente.
Existen varias técnicas para determinar una infracción de concurrencia optimista a la hora de actualizar una base de datos. Una de ellas consiste en incluir una columna de marca de tiempo en la tabla. Las bases de datos suelen ofrecer funcionalidad de marca de tiempo que puede utilizarse para identificar la fecha y la hora en que se actualizó el registro por última vez. Mediante esta técnica, decimos, se incluye una columna de marca de tiempo en la definición de la tabla y, siempre que se actualiza el registro, se actualiza la marca de tiempo, de manera que queden reflejadas la fecha y la hora actuales. Al hacer una prueba para ver si hay infracciones de la concurrencia optimista, la columna de marca de tiempo se devuelve con cualquier consulta del contenido de la tabla. Cuando se intenta realizar una actualización, se compara el valor de marca de tiempo de la base de datos con el valor de marca de tiempo original contenido en la fila modificada. Si coinciden, se realiza la actualización y se edita la columna de marca de tiempo con la hora actual, con el objeto de reflejar la actualización. Si no coinciden, se ha producido una infracción de la concurrencia optimista.
Otra técnica para probar si hay alguna infracción relacionada con la concurrencia optimista consiste en comprobar que todos los valores de columna originales de una fila siguen coincidiendo con los existentes en la base de datos.
Veamos la siguiente consulta SQL contra una base de datos:
SELECT Col1, Col2, Col3 FROM Tabla1
UPDATE Table1 SET Col1 = @NuevoValorCol1,
SET Col2 = @NuevoValorCol2,
SET Col3 = @NuevoValorCol3
WHERE Col1 = @ViejoValorCol1 AND
Col2 = @ViejoValorCol2 AND
Col3 = @ViejoValorCol3
Lo que se hace es seleccionar (SELECT
) los campos, o columnas, que se van a modificar para, después, actualizar (UPDATE
), con los nuevos valores (@NuevoValorColX
), todos aquellos donde (WHERE
) los valores originales (@ViejoValorColX
) coincidan con los valores actuales en base de datos (ColX
).
La teoría es sencilla, pero la implementación puede llegar a complicarse bastante, no por la técnica en sí, sino por la obligación de tomar una decisión en cada uno de los momentos. Será ya la intuición del desarrollador la que indique qué solución tomar en cada caso de infracción. Lo que sí debe quedar claro es que, excepto en determinadas y muy puntuales ocasiones, la concurrencia optimista siempre debe preferirse por encima de la pesimista. Es mejor resolver mal una infracción (cosa que puede corregirse a posteriori) que mantener una red de datos a un 40% de su capacidad total de gestión por acciones de bloqueo tras bloqueo, lentitud, falta de escalabilidad y desidia del usuario que es incapaz de trabajar en esas condiciones.
Tengo una duda con esto de la concurrencia optimista. Volviendo a tu caso con User_1, y User_2 en esta secuencia:
1.-User_1 lee el registro
2.-User_2 lee el registro
3.-User_2 escribe en el registro
4.-User_1 escribe, pero como los valores «antiguos» al hacer la comparación no son los originales, existe una infracción que el programador solventa.
Hasta aquí todo bien. Sin embargo, User_2 ha leido un registro que tenía la información correcta, ya que User_1 aun no había actualizado, supongamos, por una conexión más lenta.
Si hubiésemos bloqueado la tabla, User_1 habría podido hacer su operación sobre la base de datos, y posteriormente, User_2 haber leido un registro con el valor correcto y realizar la suya.
@Moule, si hubiésemos bloqueado la tabla estaríamos hablando de concurrencia pesimista, por lo que cualquiera que acceda a la misma podría realizar sus modificaciones sin ningún problema. La única diferencia es que, mientras dura el bloqueo de un usuario cualquiera, ningún otro puesto de la red podrá acceder a esta tabla. Además, los datos pueden corromperse al no hacer comprobaciones ni validaciones sobre la concurrencia.
Articulo muy bueno, a favoritos 🙂
https://www.teknoplof.com/2010/08/06/concurrencia-optimista-en-bases-de-datos/comment-page-1/#comment-15935