2009-10-29 20 views
20

¿Cómo manejo la concurrencia en un modelo de Django? No deseo que los cambios en el registro sean sobrescritos por otro usuario que lea el mismo registro.Control de concurrencia en Django modelo

+0

duplicado Posible de http: // stackoverflow .com/questions/320096/django-how-can-i-protect-against-concurrent-modification-of-database-entries – Tony

Respuesta

22

La respuesta corta, esta realmente no es una pregunta de Django como se presentó.

El control de concurrencia a menudo se presenta como una pregunta técnica, pero en muchos aspectos es una cuestión de requisitos funcionales. ¿Cómo quiere/necesita su aplicación para trabajar? Hasta que sepamos eso, será difícil dar cualquier consejo específico de Django.

Pero, me siento como el senderismo, así que aquí va ...

Hay dos preguntas que tienden a preguntarme cuando se enfrentan a la necesidad de control de concurrencia:

  • ¿Qué tan probable es que que dos usuarios necesitarán modificar al mismo tiempo el mismo registro?
  • ¿Cuál es el impacto para el usuario si se pierden sus modificaciones a un registro?

Si la probabilidad de colisiones es relativamente alta, o si el impacto de perder una modificación es grave, entonces puede estar buscando algún tipo de bloqueo pesimista. En un esquema pesimista, cada usuario debe adquirir un bloqueo lógico antes de abrir el registro para su modificación.

El bloqueo pesimista tiene mucha complejidad. Debe sincronizar el acceso a los bloqueos, considerar la tolerancia a errores, la caducidad del bloqueo, los bloqueos de las las mayúsculas pueden ser anulados por los superusuarios, los usuarios pueden ver quién tiene el bloqueo, y así sucesivamente.

En Django, esto podría implementarse con un modelo de bloqueo separado o con algún tipo de clave foránea 'bloqueo de usuario' en el registro bloqueado. El uso de una tabla de bloqueo le da un poco más de flexibilidad en términos de almacenamiento cuando se adquirió la cerradura, usuario, notas, etc. Si necesita una tabla de bloqueo genérica que se puede usar para bloquear cualquier tipo de registro, eche un vistazo al django.contrib.contenttypes framework, pero rápidamente esto puede convertirse en el síndrome de abstracción del astronauta.

Si las colisiones son poco probables o se pierden, las modificaciones se vuelven a crear trivialmente, entonces se puede salir funcionalmente con técnicas de concurrencia optimistas. Esta técnica es simple y más fácil de implementar. Esencialmente, solo realiza un seguimiento de un número de versión o marca de tiempo de modificación y rechaza cualquier modificación que detecte como fuera de control.

Desde el punto de vista del diseño funcional, solo tiene que considerar cómo se presentan estos errores de modificación simultáneos a sus usuarios.

En términos de Django, control de concurrencia optimista se puede implementar reemplazando el método ahorrar en la clase del modelo ...

def save(self, *args, **kwargs): 
    if self.version != self.read_current_version(): 
     raise ConcurrentModificationError('Ooops!!!!') 
    super(MyModel, self).save(*args, **kwargs) 

Y, por supuesto, para cualquiera de estos mecanismos de concurrencia para ser robusto, que tiene que considerar transactional control. Ninguno de estos modelos es completamente viable si no puede garantizar las propiedades ACID de sus transacciones.

+0

Ok, ese último ejemplo que escribiste era lo que yo quería saber, así que tengo que escribir el mío guardar el método para el modelo. Vengo de otro marco cuando se trata de establecer una propiedad para "comparar valores" con un control, por lo que no tenía idea de cómo implementarlo en Django, y no encontré ningún ejemplo en el tutorial inicial o me lo perdí. Pensé que dado que Django automatiza varias tareas que en otros marcos son hechas por el programador, esta tarea podría ser automatizada como en el marco en el que me referí primero – Pablo

+0

Sí, mi opinión personal es que los marcos deberían evitar generalizar estos patrones de diseño debido a todas las funciones/implicaciones técnicas. YMMV ... –

+2

Como se menciona a continuación, el código en el último fragmento está roto. Todavía puede aparecer una modificación entre el control de versión y el método de guardar. – julkiewicz

10

No creo que funcione un 'número de versión o marca de tiempo'.

Cuando self.version == self.read_current_version() es , existe la posibilidad de que el número de versión haya sido modificado por otras sesiones justo antes de llamar al super().save().

+2

Sin bloquear la tabla en cuestión, esto es correcto. Sin embargo, hay decoradores para simplificar el bloqueo de mesas con los modelos Django, que deberían evitar la condición de carrera a la que se refiere. – Cerin

2

Estoy de acuerdo con la explicación introductoria de Joe Holloway.

quiero contribuir con un fragmento de código de trabajo en relación a la última parte de su respuesta ("En términos de Django, control de concurrencia optimista se puede implementar reemplazando el método ahorrar en la clase del modelo ...")

usted puede utilizar las siguientes clases como un antepasado para su propio modelo

Si está dentro de una transacción db (ej. mediante el uso de transaction.atomic en un ámbito exterior) las siguientes afirmaciones son pitón seguro y consistente

En la práctica a través de una sola toma, las declaraciones filter + update proporcionan una especie de test_an d_set en el registro: verifican la versión y adquieren un bloqueo de nivel db implícito en la fila. Por lo tanto, el siguiente "guardar" puede actualizar los campos del registro para asegurarse de que es la única sesión que opera en esa instancia del modelo. La final cometer (por ejemplo ejecutado automáticamente por __exit__ en transaction.atomic) libera el bloqueo de nivel de db implícita en la fila

class ConcurrentModel(models.Model): 
    _change = models.IntegerField(default=0) 

    class Meta: 
     abstract = True 

    def save(self, *args, **kwargs): 
     cls = self.__class__ 
     if self.pk: 
      rows = cls.objects.filter(
       pk=self.pk, _change=self._change).update(
       _change=self._change + 1) 
      if not rows: 
       raise ConcurrentModificationError(cls.__name__, self.pk) 
      self._change += 1 
     super(ConcurrentModel, self).save(*args, **kwargs) 

Se toma de https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default