La documentación de Django 2.2 , que estoy usando, proporciona el siguiente ejemplo de uso para select_for_update
:
from django.db import transaction entries = Entry.objects.select_for_update().filter(author=request.user) with transaction.atomic(): for entry in entries: ...
Usando este enfoque, uno presumiblemente mutaría las instancias del modelo asignadas a la entry
y llamaría a save
en estas.
Hay casos en los que preferiría el enfoque alternativo a continuación, pero no estoy seguro de si funcionaría (o incluso tendría sentido) con select_for_update
.
with transaction.atomic(): Entry.objects.select_for_update().filter(author=request.user).update(foo="bar", wobble="wibble")
La documentación establece que el bloqueo se crea cuando se evalúa el conjunto de consultas , por lo que dudo que el método de update
funcione. Por lo que sé, la update
solo realiza una consulta UPDATE ... WHERE
, sin SELECT
antes. Sin embargo, agradecería que alguien con más experiencia en este aspecto del ORM de Django pudiera confirmarlo.
Una pregunta secundaria es si un bloqueo incluso agrega alguna protección contra las condiciones de carrera si uno realiza una sola consulta de UPDATE
contra las filas bloqueadas. (Entré en este tren de pensamiento porque estoy refactorizando el código que usa un candado al actualizar los valores de dos columnas de una sola fila).
Por lo que sé, la actualización solo realiza una consulta ACTUALIZAR ... DONDE, sin SELECCIONAR antes
Si eso es correcto. Puede confirmar esto mirando las consultas reales realizadas. Usando la aplicación de "encuestas" del tutorial canónico de Django como ejemplo:
with transaction.atomic(): qs = polls.models.Question.objects.select_for_update().all() qs.update(question_text='test') print(connection.queries) # {'sql': 'UPDATE "polls_question" SET "question_text" = \'test\'', 'time': '0.008'}
Entonces, como es de esperar, no hay SELECT
.
Sin embargo, garantizar que se adquiera el bloqueo sería tan simple como hacer cualquier cosa para que se evalúe el conjunto de consultas.
with transaction.atomic(): qs = polls.models.Question.objects.select_for_update().all() list(qs) # cause evaluation, locking the selected rows qs.update(question_text='test') print(connection.queries) #[... # {'sql': 'SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" FOR UPDATE', 'time': '0.003'}, # {'sql': 'UPDATE "polls_question" SET "question_text" = \'test\'', 'time': '0.001'} #]
Una pregunta secundaria es si un bloqueo incluso agrega alguna protección contra las condiciones de carrera si uno realiza una sola consulta de ACTUALIZACIÓN contra las filas bloqueadas.
En general, sí. Si es necesario en una situación particular depende de qué tipo de condición de carrera te preocupa. El bloqueo evitará las condiciones de carrera en las que otra transacción puede intentar actualizar la misma fila, por ejemplo.
Las condiciones de carrera también se pueden evitar sin bloqueos, dependiendo de la naturaleza de la actualización/condición de carrera. A veces una transacción es suficiente, a veces no lo es. También puede usar expresiones que se evalúan del lado del servidor en la base de datos para evitar condiciones de carrera (por ejemplo, usando las expresiones F()
de Django ).
También hay otras consideraciones, como su dialecto de base de datos, niveles de aislamiento y más.
Referencia adicional sobre pensamientos de condición de carrera: Anti-patrones de PostgreSQL: ciclos de lectura-modificación-escritura ( archivo )