¿Cómo manejo esta condición de carrera en django?

Se supone que este código debe obtener o crear un objeto y actualizarlo si es necesario. El código está en uso de producción en un sitio web.

En algunos casos, cuando la base de datos está ocupada, lanzará la excepción “DoesNotExist: MyObj query query does not exist”.

# Model: class MyObj(models.Model): thing = models.ForeignKey(Thing) owner = models.ForeignKey(User) state = models.BooleanField() class Meta: unique_together = (('thing', 'owner'),) # Update or create myobj @transaction.commit_on_success def create_or_update_myobj(owner, thing, state) try: myobj, created = MyObj.objects.get_or_create(owner=user,thing=thing) except IntegrityError: myobj = MyObj.objects.get(owner=user,thing=thing) # Will sometimes throw "DoesNotExist: MyObj matching query does not exist" myobj.state = state myobj.save() 

Utilizo una base de datos innodb mysql en ubuntu.

¿Cómo trato de forma segura este problema?

Esto podría ser un desenlace del mismo problema que aquí:

¿Por qué este ciclo no muestra un recuento de objetos actualizado cada cinco segundos?

Básicamente get_or_create puede fallar : si echas un vistazo a su fuente, verás que es: get, if-problem: save + some_trickery, if-still-problem: get again, if-still-problem: rendición y aumento .

Esto significa que si hay dos subprocesos (o procesos) create_or_update_myobj ejecutando create_or_update_myobj , ambos tratando de obtener o crear el mismo objeto, entonces:

  • primer hilo intenta obtenerlo, pero aún no existe,
  • Entonces, el hilo intenta crearlo, pero antes de que se cree el objeto …
  • … el segundo hilo intenta conseguirlo, y esto obviamente falla
  • ahora, debido al AUTOCOMMIT predeterminado = OFF para la conexión de base de datos MySQLdb, y al nivel serializable REPEATABLE READ, ambos hilos han congelado sus vistas de la tabla MyObj.
  • posteriormente, el primer hilo crea su objeto y lo devuelve con gracia, pero …
  • … el segundo hilo no puede crear nada, ya que violaría unique restricción unique
  • lo que es gracioso, subsecuentemente get en el segundo hilo no ve el objeto creado en el primer hilo, debido a la vista congelada de la tabla MyObj

Por lo tanto, si quieres obtener de forma segura o get_or_create algo, prueba algo como esto:

  @transaction.commit_on_success def my_get_or_create(...): try: obj = MyObj.objects.create(...) except IntegrityError: transaction.commit() obj = MyObj.objects.get(...) return obj 

Editado el 27/05/2010

También hay una segunda solución al problema: usar el nivel de aislamiento READ COMMITTE, en lugar de REPEATABLE READ. Pero está menos probado (al menos en MySQL), por lo que podría haber más errores / problemas con él, pero al menos permite vincular vistas a las transacciones, sin comprometerse en el medio.

Editado el 22/01/2012

Aquí hay algunas buenas entradas de blog (no mías) sobre MySQL y Django, relacionadas con esta pregunta:

http://www.no-ack.org/2010/07/mysql-transactions-and-django.html

http://www.no-ack.org/2011/05/broken-transaction-management-in-mysql.html

Su manejo de excepciones está enmascarando el error. Debería pasar un valor de state en get_or_create() o establecer un valor predeterminado en el modelo y la base de datos.

Una forma (tonta) podría ser detectar el error y simplemente volver a intentarlo una o dos veces después de esperar una pequeña cantidad de tiempo. No soy un experto en DB, por lo que podría haber una solución de señalización.