Frage

Hier ist ein einfaches Beispiel für eine Django -Ansicht mit einer potenziellen Rennbedingung:

# 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()

Die Rennbedingung sollte ziemlich offensichtlich sein: Ein Benutzer kann diese Anfrage zweimal stellen, und die Anwendung kann möglicherweise ausgeführt werden user = request.user Gleichzeitig verursacht eine der Anfragen, den anderen zu überschreiben.

Angenommen, die Funktion calculate_points ist relativ kompliziert und basiert Berechnungen auf der Grundlage aller Arten von seltsamen Dingen, die nicht in eine einzelne platziert werden können update und wäre schwer zu speichern.

Hier ist meine Frage: Welche Art von Sperrmechanismen stehen Django zur Verfügung, um sich mit ähnlichen Situationen zu befassen?

War es hilfreich?

Lösung

Django 1.4+ unterstützt SELECT_FOR_UPDATE, In früheren Versionen können Sie RAW -SQL -Abfragen z. select ... for update Was abhängig von der zugrunde liegenden DB die Zeile von allen Updates sperrt, können Sie mit dieser Zeile bis zum Ende der Transaktion alles tun, was Sie wollen. z.B

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()

Andere Tipps

Ab Django 1.1 können Sie die F () -Scharakterien des ORM verwenden, um dieses spezifische Problem zu lösen.

from django.db.models import F

user = request.user
user.points  = F('points') + calculate_points(user)
user.save()

Weitere Informationen finden Sie in der Dokumentation:

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

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

Datenbankverriegelung ist der richtige Weg hierher. Es ist geplant, Django ("SELECT for Update" -Verstützung hinzuzufügen (hier), aber im Moment wäre es, RAW SQL zu verwenden, um das Benutzerobjekt zu aktualisieren, bevor Sie mit der Berechnung der Punktzahl beginnen.


Die pessimistische Verriegelung wird jetzt von Django 1.4 von ORM unterstützt, wenn der zugrunde liegende DB (wie Postgres) es unterstützt. Siehe das Django 1.4A1 Versionsnotizen.

Sie haben viele Möglichkeiten, solche Dinge einzeln zu thread.

Ein Standardansatz ist Zuerst aktualisieren. Sie führen ein Update durch, das eine exklusive Sperre in der Reihe beschlagnahmt. Dann mach deine Arbeit; und schließlich die Veränderung begehen. Damit dies funktioniert, müssen Sie das Ausschneiden des Orms umgehen.

Ein weiterer Standardansatz besteht darin, einen separaten, Single-Thread-Anwendungsserver zu haben, der die Web-Transaktionen von der komplexen Berechnung isoliert.

  • Ihre Webanwendung kann eine Warteschlange von Bewertungsanforderungen erstellen, einen separaten Vorgang hervorrufen und dann die Bewertungsanforderungen in diese Warteschlange schreiben. Der Spawn kann in Django gestellt werden urls.py So passiert es beim Web-App-Startup. Oder es kann in getrennte gestellt werden manage.py Admin -Skript. Oder es kann nach Bedarf "erfolgen", wenn die erste Bewertungsanfrage versucht wird.

  • Sie können auch einen separaten Webserver mit WSGI-Geschmack erstellen, der mit Werkzug über Urllib2 WS-Anforderungen akzeptiert. Wenn Sie eine einzelne Portnummer für diesen Server haben, werden die Anforderungen von TCP/IP in die Warteschlange gestellt. Wenn Ihr WSGI-Handler einen Thread hat, haben Sie serialisierte Einzel-Threading erreicht. Dies ist etwas skalierbarer, da die Bewertungsmotor eine WS -Anfrage ist und überall ausgeführt werden kann.

Ein weiterer Ansatz besteht darin, eine andere Ressource zu haben, die erworben und für die Berechnung gehalten werden muss.

  • Ein Singleton -Objekt in der Datenbank. Eine einzelne Zeile in einer eindeutigen Tabelle kann mit einer Sitzungs -ID aktualisiert werden, um die Kontrolle zu beschlagnahmen. Aktualisieren Sie mit Sitzungs -ID von None Kontrolle freigeben. Das wesentliche Update muss a enthalten WHERE SESSION_ID IS NONE Filter, um sicherzustellen, dass das Update fehlschlägt, wenn das Schloss von jemand anderem gehalten wird. Dies ist interessant, weil es von Natur aus rassenfrei ist-es ist ein einziges Update-keine Select-Update-Sequenz.

  • Außerhalb der Datenbank kann ein Semaphor der Gartenvariante verwendet werden. Warteschlangen (im Allgemeinen) sind leichter zu arbeiten als mit einem Semaphor auf niedrigem Niveau.

Dies mag Ihre Situation zu vereinfachen, aber was ist mit nur einem JavaScript -Link -Ersatz? Mit anderen Worten, wenn der Benutzer auf den Link oder die Schaltfläche klickt, wickeln Sie die Anforderung in einer JavaScript -Funktion ein, die den Link sofort deaktiviert / "Graut ähnlich. Würde das für dich funktionieren?

Jetzt müssen Sie verwenden:

Model.objects.select_for_update().get(foo=bar)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top