database web-applications concurrency race-condition

database - ¿Cómo trato los cambios concurrentes en una aplicación web?



web-applications concurrency (3)

Si no tiene transacciones en mysql, puede usar el comando de actualización para asegurarse de que los datos no estén corruptos.

UPDATE tableA SET status=2 WHERE status = 1

Si el estado es uno, solo un proceso obtendrá el resultado de que se actualizó un registro. En el código siguiente, devuelve -1 si NO se ejecutó la actualización (si no había filas para actualizar).

PreparedStatement query; query = connection.prepareStatement(s); int rows = -1; try { rows = query.executeUpdate(); query.close(); } catch (Exception e) { e.printStackTrace(); } return rows;

Aquí hay dos flujos de trabajo potenciales que me gustaría realizar en una aplicación web.

Variación 1

  • el usuario envía la solicitud
  • servidor lee datos
  • el servidor modifica los datos
  • servidor guarda datos modificados

Variación 2:

  • el usuario envía la solicitud
  • servidor lee datos
  • el servidor envía datos al usuario
  • el usuario envía la solicitud con modificaciones
  • servidor guarda datos modificados

En cada uno de estos casos, me pregunto: ¿cuáles son los enfoques estándar para garantizar que el acceso concurrente a este servicio produzca resultados correctos? (es decir, nadie edita, los valores corresponden a algunos pedidos de ediciones, etc.)

La situación es hipotética, pero aquí hay algunos detalles sobre dónde probablemente tendría que tratar esto en la práctica:

  • aplicación web, pero idioma no especificado
  • potencialmente, usando un marco web
  • data store es una base de datos relacional de SQL
  • la lógica involucrada es demasiado compleja para expresarse bien en una consulta, por ejemplo, valor = valor + 1

Siento que preferiría no intentar reinventar la rueda aquí. Sin duda, estos son problemas bien conocidos con soluciones bien conocidas. Por favor avise.

Gracias.


Las cosas son simples en la capa de aplicación: cada solicitud es servida por un hilo (o proceso) diferente, de modo que a menos que tenga estado en sus clases de procesamiento (servicios), todo es seguro.

Las cosas se complican más cuando llega a la base de datos, es decir, donde se encuentra el estado. Allí necesita transacciones para asegurarse de que todo esté bien.

Las transacciones tienen un conjunto de propiedades, ACID , que "garantiza que las transacciones de la base de datos se procesen de manera confiable".


Que yo sepa, no hay una solución general al problema.

La raíz del problema es que el usuario puede recuperar datos y mirarlos en la pantalla durante un tiempo prolongado antes de realizar una actualización y guardarlos.

Conozco tres enfoques básicos:

  1. Cuando el usuario lea la base de datos, bloquee el registro y no lo suelte hasta que el usuario guarde las actualizaciones. En la práctica, esto es muy poco práctico. ¿Qué pasa si el usuario abre una pantalla y luego va a almorzar sin guardar? ¿O se va a casa por el día? ¿O está tan frustrado tratando de actualizar este estúpido registro que se retira y nunca vuelve?

  2. Exprese sus actualizaciones como deltas en lugar de destinos. Para tomar el ejemplo clásico, supongamos que tiene un sistema que registra stock en el inventario. Cada vez que hay una venta, debe restar 1 (o más) del recuento de inventario.

Por lo tanto, supongamos que la cantidad actual disponible es 10. El usuario A crea una venta. Cantidad actual = 10. El usuario B crea una venta. También obtiene la cantidad actual = 10. El usuario A ingresa que se venden dos unidades. Nueva cantidad = 10 - 2 = 8. Guardar. El usuario B ingresa una unidad vendida. Nueva cantidad = 10 (el valor que cargó) - 1 = 9. Guardar. Claramente, algo salió mal.

Solución: en lugar de escribir "update inventory set quantity = 9 where itemid = 12345", escriba "update inventory set quantity = quantity-1 where itemid = 12345". Luego deje que la base de datos ponga en cola las actualizaciones. Esto es muy diferente de la estrategia n. ° 1, ya que la base de datos solo debe bloquear el registro el tiempo suficiente para leerlo, realizar la actualización y escribirlo. No tiene que esperar mientras alguien mira la pantalla.

Por supuesto, esto solo es utilizable para cambios que se pueden expresar como un delta. Si está, por ejemplo, actualizando el número de teléfono del cliente, no va a funcionar. (Como, el número anterior es 555-1234. El usuario A dice que lo cambie a 555-1235. Eso es un cambio de +1. El usuario B dice que lo cambie a 555-1243. Eso es un cambio de +9. Entonces, el cambio total es +10, el nuevo número del cliente es 555-1244. :-)) Pero en casos como ese, "el último usuario que haga clic en la tecla Enter gana" es probablemente lo mejor que puede hacer de todos modos.

  1. En la actualización, verifique que los campos relevantes en la base de datos coincidan con su valor "de". Por ejemplo, supongamos que trabajas para un bufete de abogados que negocia contratos para tus clientes. Usted tiene una pantalla donde un usuario puede ingresar notas sobre negociaciones. El usuario A abre un registro de contrato. El usuario B trae el mismo registro de contrato. El usuario A ingresa que acaba de hablar con la otra parte por teléfono y que están de acuerdo con los términos propuestos. El usuario B, que también ha estado tratando de llamar a la otra parte, informa que no está respondiendo a las llamadas telefónicas y sospecha que están bloqueando el acceso. El usuario A hace clic en guardar. ¿Queremos que los comentarios del usuario B sobrescriban al usuario A? Probablemente no. En su lugar, mostramos un mensaje que indica que las notas han cambiado desde que él leyó el registro, y le permite ver el nuevo valor antes de decidir si continuar con guardar, abortar o ingresar algo diferente.

[Nota: el foro vuelve a numerar automáticamente mis listas numeradas. No estoy seguro de cómo anular esto.]