Bloqueo optimista por ejemplo concreto(Java)
concurrency locking (2)
@TheConstructor: Gran explicación de lo que es y no es. Cuando dijiste "El bloqueo optimista no es una magia para fusionar cambios en conflicto", quería comentar. Solía administrar una aplicación DataFlex que permitía a los usuarios editar registros en un formulario. Cuando presionaban el botón "Guardar", la aplicación hacía lo que se llamaba una " re-lectura de múltiples usuarios " de los datos (extrayendo los valores actuales) y los comparaba con lo que el usuario había modificado. Si los campos modificados por el usuario no se hubieran modificado mientras tanto, solo esos campos se volverían a escribir en el registro (que estaba bloqueado solo durante la operación de re-lectura + escritura) y, por lo tanto, 2 usuarios podrían modificar de forma transparente diferentes campos en El mismo disco sin problemas. No requería sellos de versión, solo un conocimiento de qué campos fueron modificados.
Por supuesto, esta no es una solución perfecta, pero funcionó en ese caso. Fue optimista y permitió cambios no relacionados y le dio al usuario un error en los cambios en conflicto. Esto fue lo mejor que se pudo hacer, pre-SQL, pero aún hoy es un buen principio de diseño, quizás para más escenarios relacionados con objetos o la web.
He pasado la mañana leyendo todos los artículos principales que Google arremolina contra el bloqueo optimista , y por el resto de mi vida, todavía no lo entiendo.
Entiendo que el bloqueo optimista implica la adición de una columna para rastrear la "versión" del registro, y que esta columna puede ser una marca de tiempo, un contador o cualquier otra construcción de seguimiento de versión. Pero aún no entiendo cómo eso garantiza la integridad de ESCRITO (lo que significa que si varios procesos están actualizando la misma entidad al mismo tiempo, que luego, la entidad refleja correctamente el estado real en el que debe estar)
¿Puede alguien proporcionar un ejemplo concreto y fácil de entender de cómo se podría usar el bloqueo optimista en Java (contra, quizás, una base de datos MySQL)? Digamos que tenemos una entidad de Person
:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
}
Y las instancias de Person
se conservan en una tabla MySQL de people
:
CREATE TABLE people (
person_id PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code
age INT NOT NULL,
color_id FOREIGN KEY (colors) NOT NULL # Say we also have a colors table and people has a 1:1 relationship with it
);
Ahora digamos que hay 2 sistemas de software, o 1 sistema con 2 subprocesos, que intentan actualizar la misma entidad de Person
al mismo tiempo:
- Software / Thread # 1 está intentando persistir un cambio de apellido (de " John Smith " a " John Doe ")
- Software / Thread # 2 está intentando persistir un cambio en el color favorito (de ROJO a VERDE )
Mis preguntas:
- ¿Cómo se podría implementar el bloqueo optimista en las tablas de
people
y / ocolors
? (Buscando ejemplo específico de DDL) - ¿Cómo podría utilizar este bloqueo optimista en la capa de aplicación / Java? (Buscando ejemplo de código específico)
- ¿Puede alguien ejecutarme en un escenario en el que los cambios de DDL / código (de # 1 y # 2 arriba) entrarán en juego en mi escenario (o en cualquier otro escenario) y "bloquearían de manera optimista" las tablas de
people
/colors
correctamente? Básicamente, estoy buscando ver un bloqueo optimista en acción, con una explicación fácil de seguir de por qué funciona.
Normalmente, cuando observa el bloqueo optimista, también utiliza una biblioteca como Hibernate u otra implementación de JPA con soporte de @Version
.
El ejemplo podría leerse así:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
@Version
private Long version;
}
Si bien obviamente no tiene sentido agregar una anotación @Version
si no está utilizando un marco que lo admita.
El DDL podría entonces ser
CREATE TABLE people (
person_id PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code
age INT NOT NULL,
color_id FOREIGN KEY (colors) NOT NULL, # Say we also have a colors table and people has a 1:1 relationship with it
version BIGINT NOT NULL
);
¿Qué pasa con la versión?
- Cada vez que antes de almacenar la entidad, verifica si la versión almacenada en la base de datos sigue siendo la versión que usted conoce.
- Si es así, almacene sus datos con la versión incrementada en uno
Para realizar ambos pasos sin arriesgar otro proceso, cambiar los datos entre ambos pasos, normalmente se maneja a través de una declaración como
UPDATE Person SET lastName = ''married'', version=2 WHERE person_id = 42 AND version = 1;
Después de ejecutar la declaración, verifica si actualizó una fila o no. Si lo hizo, nadie más cambió los datos desde que los leyó, de lo contrario, alguien más los cambió. Si alguien más cambió los datos, normalmente recibirá una OptimisticLockException
de la biblioteca que está utilizando.
Esta excepción debería hacer que todos los cambios se revoquen y que el proceso de cambio del valor se reinicie, ya que la condición sobre la cual se actualizaría la entidad puede dejar de ser aplicable.
Así que no hay colisión:
- Proceso A lee Persona
- El proceso A escribe Persona incrementando así la versión
- Proceso B lee Persona
- Proceso B escribe Persona incrementando así la versión
Colisión:
- Proceso A lee Persona
- Proceso B lee Persona
- El proceso A escribe Persona incrementando así la versión
- El proceso B recibe una excepción al intentar guardar ya que la versión cambió desde que se leyó la persona
Si Color es otro objeto, debes poner una versión allí por el mismo esquema.
¿Qué no es el bloqueo optimista?
- El bloqueo optimista no es una magia para fusionar cambios conflictivos. El bloqueo optimista solo evitará que los procesos sobrescriban accidentalmente los cambios por otro proceso.
- El bloqueo optimista en realidad no es un bloqueo DB real. Simplemente funciona comparando el valor de la columna de la versión. No impide que otros procesos accedan a ningún dato, por lo tanto, espere que obtenga
OptimisticLockException
s
¿Qué tipo de columna usar como versión?
Si muchas aplicaciones diferentes acceden a sus datos, es mejor que utilice una columna que la base de datos actualiza automáticamente. por ejemplo para MySQL
version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
De esta manera, las aplicaciones que implementan el bloqueo optimista notarán cambios por aplicaciones tontas.
Si actualiza las entidades con más frecuencia que la resolución de TIMESTAMP
o la interpretación de Java de la misma, este enfoque puede fallar en detectar ciertos cambios. Además, si permite que Java genere el nuevo TIMESTAMP
, debe asegurarse de que todas las máquinas que ejecutan sus aplicaciones estén en perfecta sincronización.
Si todas sus aplicaciones pueden alterarse un número entero, largo, ... la versión suele ser una buena solución, ya que nunca sufrirá los relojes de configuración diferente ;-)
Hay otros escenarios. Podría, por ejemplo, utilizar un hash o incluso generar aleatoriamente una String
cada vez que se cambie una fila. Es importante que no repita valores mientras que cualquier proceso contiene datos para el procesamiento local o dentro de un caché, ya que ese proceso no podrá detectar cambios al mirar la columna de la versión.
Como último recurso, puede utilizar el valor de todos los campos como versión. Si bien este será el enfoque más costoso en la mayoría de los casos, es una forma de obtener resultados similares sin cambiar la estructura de la tabla. Si utiliza Hibernate, existe la @OptimisticLocking
@OptimisticLocking para imponer este comportamiento. Use @OptimisticLocking(type = OptimisticLockType.ALL)
en la clase de entidad para fallar si alguna fila cambió desde que leyó la entidad o @OptimisticLocking(type = OptimisticLockType.DIRTY)
para simplemente fallar cuando otro proceso cambió los campos que cambió, también .