python database django locking race-condition

python - Condiciones de carrera en django



database locking (6)

A partir de Django 1.1, puedes usar las expresiones F () de ORM para resolver este problema específico.

from django.db.models import F user = request.user user.points = F(''points'') + calculate_points(user) user.save()

Para más detalles ver la documentación:

https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F

Aquí hay un ejemplo simple de una vista django con una posible condición de carrera:

# myapp/views.py from django.contrib.auth.models import User from my_libs import calculate_points def add_points(request): user = request.user user.points += calculate_points(user) user.save()

La condición de carrera debe ser bastante obvia: un usuario puede realizar esta solicitud dos veces, y la aplicación podría ejecutar user = request.user simultáneamente, haciendo que una de las solicitudes anule a la otra.

Supongamos que la función calculate_points es relativamente complicada y hace cálculos basados ​​en todo tipo de cosas raras que no se pueden colocar en una sola update y sería difícil ponerlos en un procedimiento almacenado.

Así que aquí está mi pregunta: ¿Qué tipo de mecanismos de bloqueo están disponibles para django, para hacer frente a situaciones similares a esta?


Ahora, debes usar:

Model.objects.select_for_update().get(foo=bar)


Django select_for_update compatible con select_for_update , en versiones anteriores puede ejecutar consultas SQL sin procesar, por ejemplo, select ... for update que dependiendo de la base de datos subyacente bloqueará la fila de las actualizaciones, puede hacer lo que quiera con esa fila hasta el final de la transacción. p.ej

from django.db import transaction @transaction.commit_manually() def add_points(request): user = User.objects.select_for_update().get(id=request.user.id) # you can go back at this point if something is not right if user.points > 1000: # too many points return user.points += calculate_points(user) user.save() transaction.commit()


El bloqueo de la base de datos es el camino a seguir aquí. Hay planes para agregar compatibilidad con "seleccionar para actualizar" a Django ( here ), pero por ahora lo más simple sería usar SQL sin formato para ACTUALIZAR el objeto del usuario antes de comenzar a calcular la puntuación.

El bloqueo pesimista ahora es compatible con el ORM de Django 1.4 cuando el DB subyacente (como Postgres) lo admite. Vea las notas de la versión de Django 1.4a1 .


Esto puede simplificar demasiado tu situación, pero ¿qué pasa con el reemplazo de un enlace de JavaScript? En otras palabras, cuando el usuario hace clic en el enlace o botón, envuelve la solicitud en una función de JavaScript que inmediatamente deshabilita / "pone gris" el enlace y reemplaza el texto con la información "Cargando ..." o "Enviar solicitud ..." o algo similar. ¿Eso funcionaría para ti?


Tienes muchas maneras de encauzar este tipo de cosas.

Un enfoque estándar es Actualizar primero . Usted hace una actualización que aprovechará un bloqueo exclusivo en la fila; entonces haz tu trabajo; y finalmente cometer el cambio. Para que esto funcione, debe omitir el almacenamiento en caché del ORM.

Otro enfoque estándar es tener un servidor de aplicaciones separado de un único subproceso que aísla las transacciones web del cálculo complejo.

  • Su aplicación web puede crear una cola de solicitudes de puntuación, engendrar un proceso separado y luego escribir las solicitudes de puntuación en esta cola. El spawn se puede colocar en urls.py de Django para que suceda en el inicio de la aplicación web. O puede colocarse en un manage.py administración de manage.py separado. O puede hacerse "según sea necesario" cuando se intenta la primera solicitud de puntuación.

  • También puede crear un servidor web WSGI separado utilizando Werkzeug que acepte solicitudes de WS a través de urllib2. Si tiene un número de puerto único para este servidor, las solicitudes están en cola por TCP / IP. Si su manejador WSGI tiene un hilo, entonces, ha logrado un enhebrado simple serializado. Esto es ligeramente más escalable, ya que el motor de puntuación es una solicitud de WS y se puede ejecutar en cualquier lugar.

Sin embargo, otro enfoque es tener algún otro recurso que deba adquirirse y mantenerse para hacer el cálculo.

  • Un objeto Singleton en la base de datos. Una sola fila en una tabla única se puede actualizar con una ID de sesión para tomar el control; actualizar con ID de sesión de None para liberar el control. La actualización esencial debe incluir un filtro WHERE SESSION_ID IS NONE para asegurarse de que la actualización falla cuando otra persona mantiene el bloqueo. Esto es interesante porque es inherentemente libre de raza, es una actualización única, no una secuencia de SELECCIÓN DE ACTUALIZACIÓN.

  • Un semáforo de jardín se puede usar fuera de la base de datos. Las colas (generalmente) son más fáciles de trabajar que un semáforo de bajo nivel.