django - sistemas - que son las condiciones de carrera
¿Cómo manejo esta condición de carrera en django? (3)
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 get_or_create 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 subproceso crea su objeto y lo devuelve con gracia, pero ...
- ... el segundo hilo no puede crear nada, ya que violaría
unique
restricciónunique
- 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.