Tengo un código que debería funcionar bajo una solicitud simultánea y una carga pesada.
Escribí un ejemplo para dar una mejor comprensión de lo que estoy tratando de hacer:
def add_tag(): with transaction.atomic(): image = Image.objects.get(pk=2) tag = Tag.objects.get(pk=6) image.tags.add(tag) # concurrent insert return 'done' class Command(BaseCommand): def handle(self, *args, **options): with ProcessPoolExecutor(max_workers=3) as executor: futures = [] for _ in range(3): futures.append(executor.submit(add_tag)) for future in as_completed(futures): print(future.result())Y aquí están mis modelos:
class Image(models.Model): title = models.CharField(max_length=255) tags = models.ManyToManyField('ov_tags.Tag') class Tag(models.Model): title = models.CharField(max_length=255)Estoy tratando de insertar en la tabla de relaciones ManyToMany en paralelo. Obviamente, esto provoca un error, debido al nivel de aislamiento de LECTURA COMPROMETIDA:
django.db.utils.IntegrityError: duplicate key value violates unique constraintAbsolutamente bien, pero ¿cómo eliminar este error por completo?
Para proteger mi imagen, traté de usar select_for_update en la selección de imagen.
image = Image.objects.select_for_update().get(pk=2)¡Y funciona! Lo ejecuto varias veces. Ya no hay errores y el artículo se insertó correctamente. Pero no sé por qué?
¿Está select_for_update bloqueando la tabla relacional de todos modos? ¿O está sucediendo en un lado de la aplicación? ¿Hay una manera correcta de lograr tal comportamiento?
¿Puedo usar la selección vacía para bloquear para insertar?
SELECT "image_tags"."tag_id" FROM "image_tags" WHERE ("image_tags"."tag_id" IN (6) AND "image_tags"."image_id" = 2) FOR UPDATEA nivel de base de datos, solo está bloqueando la instancia de Image específica a la que está agregando etiquetas. Tiene razón en que esto no impide las inserciones en la tabla relacional. Si otra pieza de código ignora el bloqueo y simplemente inserta una nueva fila en la tabla de relaciones, aún puede tener problemas.
Funciona para este fragmento de código porque cada transacción tiene un "buen comportamiento". Cada transacción primero adquiere un bloqueo en la imagen específica, antes de agregar nuevas entradas a la tabla relacional. Esto significa que cada proceso en el grupo de ejecutores esperará a que el proceso actual termine su transacción antes de intentar agregar nuevas filas en la tabla relacional.
Esto también funcionaría si bloqueara la Tag en lugar de la Image , pero no funciona si algún código bloquea la Tag , mientras que otro código bloquea la Image . En ese momento, un proceso puede adquirir el bloqueo en Image , pero el otro proceso no espera porque aún puede adquirir el bloqueo en Tag , y ambos procesos intentan insertar la misma fila en la tabla relacional al mismo tiempo. .
Eso es lo que quiero decir con "buen comportamiento": cada parte de su aplicación debe comportarse de una manera específica (adquirir el mismo bloqueo). Si solo una parte de su aplicación ignora este requisito, puede encontrarse con condiciones de carrera. Solo si todas las partes de su aplicación se comportan bien, podrá evitar las condiciones de carrera de esta manera.
Esto es exactamente lo que está sucediendo, la llamada select_for_update está bloqueando la tabla de imágenes en el nivel de la base de datos, de modo que ninguna otra transacción podrá modificar las filas seleccionadas hasta el final del bloque transaction.atomic .
Ver para referencia https://docs.djangoproject.com/en/1.11/ref/models/querysets/#select-for-update