2016-07-31 1 views
2

Kürzlich habe ich gefunden, dass Django ORM Model.save() ein SQL ausführt, um 'ALL' Spalten standardmäßig zu aktualisieren, auch wenn nichts geändert wurde. Das betrifft mich wirklich, weil jede Änderung, die ich gemacht habe, eine Chance hat, durch einen anderen Model.save() Prozess auf den ursprünglichen Wert zurückgesetzt zu werden. Zum Beispiel habe ich ein Modell Order. Und es gibt zwei gleichzeitige Prozesse (P1, P2), die daran arbeiten. Zunächst einmal wählt P1 eine Zeile:Djangos Model.save() modifiziert unerwünschte Spalten

# P1 
order = Order.objects.get(pk=10000) 

Dann wählt P2 die gleiche Zeile und aktualisiert die status Spalt: (die folgenden Anweisungen können in einer Transaktion oder sogar ein SERIALIZABLE ungeöffneten werden, aber dies kann nicht . lösen das Problem)

# P2 
order = Order.objects.get(pk=10000) 
if order.status == UNPAID: 
    order.status = PAID # update the `status` column 
    order.save() 

Danach aktualisiert P1 eine andere trivial Spalte:

# P1 
order.xxx = xxx # update some other trivial column 
order.save() # This would set the `status` back to UNPAID !!! 

order.status würde zurück zueingestellt werden, und das ist nicht was ich will.

Ich weiß, ich kann save(update_fields=...), select_for_update(), filter(...).update(...), SERIALIZABLE-Transaktion oder explizite Sperren auf P1 verwenden, um dieses Problem zu verhindern. Aber die Sache ist: Es ist lächerlich, sie auf allen Model.save() Aussagen im gesamten Projekt zu verwenden. Außerdem, auch wenn ich das in meinem Projektcode mache, gibt es einen anderen Code, der das tut (Django Admin, ModelForm ...).

Sollte ich Model.save() umschreiben, um nur diese geänderten Felder zu aktualisieren?

(Es scheint wie ein großes Problem für mich. Oder habe ich etwas falsch gemacht?)

+0

Es passiert, weil 'P1' keine Ahnung hat, dass Sie' P2's 'status' Attribut geändert haben. Warum brauchst du 2 verschiedene Referenzen auf die gleiche 'Reihenfolge'? – DeepSpace

+0

@DeepSpace Ich würde denken, P1 und P2 sind verschiedene Prozesse. – thebjorn

+0

@DeepSpace Ja, das sind verschiedene Prozesse. Ein Mitarbeiter verwendet beispielsweise Django Admin, um diese Bestellung zu ändern ("P1"), und ein Kunde tätigt eine Zahlung ("P2"). – prajnamort

Antwort

0

Wie andere Leute in den Kommentaren gesagt, Sie klassische Art von race condition haben. Es gibt einige Möglichkeiten, dies in Django zu regeln. Sie haben bereits einige davon erwähnt, aber sie können meiner Erfahrung nach nicht als Sicherheit betrachtet werden. Ich schlage vor, Sie zu beobachten this video genannt Immutable Django und beginnen zu graben in diese Richtung.

Grundsätzlich können Sie eine Art von version Spalte in Ihrem Modell hinzufügen und in jedem Speichern erhöhen und mit vorhandenen vergleichen. Wenn die neue Version gleich oder kleiner ist, dann wurde bereits eine gespeichert - Sie versuchen, unwirksamen Eintrag zu speichern.

0

Sie benötigen nur eine Spalte für ein ähnliches Rennen Bedingung geschehen:

# Process 1 
account = Account.objects.get(pk=1) 
account.balance += 1000 

# Process 2 
account = Account.objects.get(pk=1) 
account.balance -= 10 
account.save() 

# Process 1 
account.save() 

Nun ist die Balance Abzug von Prozess 2 verloren. Sie müssenselect_for_update verwenden, um sicherzustellen, dass Ihre Schreibvorgänge konsistent sind. Selbst wenn Sie nur die "schmutzigen" Felder speichern und als Standard festlegen, wird dies immer noch passieren.