2011-07-05 33 views
18

En mi aplicación Django a menudo tengo que hacer algo similar al get_or_create(). Por ejemplo,Django: ¿cómo hacer get_or_create() de una manera segura?

El usuario envía una etiqueta. Necesito ver si esa etiqueta ya está en la base de datos. De lo contrario, cree un nuevo registro para él. Si es , simplemente actualice el registro existente .

Pero si mira en el documento para get_or_create() parece que no es seguro para la rosca. El hilo A revisa y encuentra que el registro X no existe. Luego, el hilo B comprueba y encuentra que el registro X no existe. Ahora tanto el Subproceso A como el Subproceso B crearán un nuevo Registro X.

Esto debe ser una situación muy común. ¿Cómo lo manejo de una manera segura para los hilos?

+1

Uno de los dos subprocesos recibirá un error de registro duplicado y una excepción. No habrá datos duplicados. –

Respuesta

10

Esto debe ser una situación muy común. ¿Cómo lo manejo de una manera segura para los hilos?

Sí.

La solución "estándar" en SQL es simplemente intentar crear el registro. Si funciona, está bien. Sigue adelante.

Si un intento de crear un registro recibe una excepción "duplicada" del SGBDR, haga un SELECCIONAR y continúe.

Django, sin embargo, tiene una capa ORM, con su propio caché. Entonces, la lógica se invierte para hacer que el caso común funcione directa y rápidamente, y el caso poco común (el duplicado) plantea una rara excepción.

+0

He experimentado entradas duplicadas en una base de datos de postgres que deberían haber sido únicas cuando estaba usando 'get_or_create' en un método de vista que recibía solicitudes concurrentes, creo que esto es una preocupación válida. –

+1

@A Lee: Con restricciones de índice únicas correctamente definidas, un duplicado no debería ser posible. ¿Cómo pudo eludir la restricción del índice único? –

+0

Ah, eso hubiera solucionado el problema ahora que lo pienso con más claridad. El 'get_or_create' usó varios campos y lo moví a una ruta de ejecución diferente en lugar de dejarlo en la vista y agregar una restricción única en los múltiples campos del modelo. –

3

intento transaction.commit_on_success decorador para exigible donde está intentando get_or_create (**) kwargs

"Usar el decorador commit_on_success utilizar una sola transacción por todo el trabajo realizado en un function.If la función devuelve correctamente, entonces Django comprometerá todo el trabajo realizado dentro de la función en ese punto. Si la función genera una excepción, Django revertirá la transacción ".

aparte de ella, en las llamadas concurrentes a get_or_create, tanto los hilos de tratar de conseguir el objeto con el argumento que se le pasa (a excepción de Arg "por defecto", que es un diccionario utilizado durante la creación de la llamada en caso de get() no para recuperar cualquier objeto). en caso de falla, ambos hilos intentan crear el objeto dando como resultado múltiples objetos duplicados, a menos que se implemente un único/único conjunto a nivel de base de datos con los campos utilizados en la llamada a get().

es similar a este post How do I deal with this race condition in django?

+1

Esto no es realmente necesario, vea mis otras respuestas para mejores formas de manejar esto. –

27

Desde 2013 más o menos, get_or_create es atómica, por lo que se encarga de concurrencia bien:

Este método es atómica Suponiendo un uso correcto, la configuración de base de datos correcta , y corregir el comportamiento de la base de datos subyacente. Sin embargo, si la exclusividad no se aplica en el nivel de base de datos para los kwargs utilizados en una llamada get_or_create (vea unique or unique_together), este método es propenso a una condición de carrera que puede dar lugar a múltiples filas con los mismos parámetros insertado simultáneamente.

Si está utilizando MySQL, asegúrese de utilizar el nivel de aislamiento READ COMMITTED en lugar de REPETIBLE LEER (por defecto), de lo contrario puede ver casos en get_or_create lanzará una IntegrityError pero el objeto no aparecerá en una llamada get() posterior.

Desde: https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

Aquí hay un ejemplo de cómo podría hacerlo:

definir un modelo único, ya sea con = True:

class MyModel(models.Model): 
    slug = models.SlugField(max_length=255, unique=True) 
    name = models.CharField(max_length=255) 

MyModel.objects.get_or_create(slug=<user_slug_here>, defaults={"name": <user_name_here>}) 

... o utilizando unique_togheter :

class MyModel(models.Model): 
    prefix = models.CharField(max_length=3) 
    slug = models.SlugField(max_length=255) 
    name = models.CharField(max_length=255) 

    class Meta: 
     unique_together = ("prefix", "slug") 

MyModel.objects.get_or_create(prefix=<user_prefix_here>, slug=<user_slug_here>, defaults={"name": <user_name_here>}) 

Observe cómo los campos que no son únicos están en la definición predeterminada, NO entre los campos únicos en get_or_create. Esto asegurará que tus creaciones sean atómicas.

Así es como se implementó en Django: https://github.com/django/django/blob/fd60e6c8878986a102f0125d9cdf61c717605cf1/django/db/models/query.py#L466 - Intente crear un objeto, capture un eventual IntegrityError y devuelva la copia en ese caso. En otras palabras: manejar la atomicidad en la base de datos.

+2

Gracias a quienes votaron por esta respuesta, he agregado algunos ejemplos para que sea aún más fácil de entender. –

Cuestiones relacionadas