Gibt es eine Möglichkeit, sich vor gleichzeitigen Änderungen desselben Datenbankeintrags durch zwei oder mehr Benutzer zu schützen?
Es wäre akzeptabel, dem Benutzer, der den zweiten Commit / Save-Vorgang ausführt, eine Fehlermeldung anzuzeigen, aber die Daten sollten nicht stillschweigend überschrieben werden.
Ich denke, das Sperren des Eintrags ist keine Option, da ein Benutzer möglicherweise die Schaltfläche "Zurück" verwendet oder einfach seinen Browser schließt und die Sperre für immer verlässt.
Antworten:
So mache ich in Django optimistisches Sperren:
Der oben aufgeführte Code kann als Methode in Custom Manager implementiert werden .
Ich mache folgende Annahmen:
Diese Annahmen reichen aus, um sicherzustellen, dass niemand zuvor den Eintrag aktualisiert hat. Wenn mehrere Zeilen auf diese Weise aktualisiert werden, sollten Sie Transaktionen verwenden.
WARNUNG Django Doc :
quelle
filter
, beide eine identische Liste mit unverändert erhaltene
und dann beide gleichzeitig aufrufenupdate
? Ich sehe kein Semaphor, das Filter und Update gleichzeitig blockiert. EDIT: Oh, ich verstehe jetzt Lazy Filter. Aber wie gültig ist die Annahme, dass update () atomar ist?Diese Frage ist etwas alt und meine Antwort etwas spät, aber nach dem, was ich verstehe, wurde dies in Django 1.4 behoben, indem:
select_for_update(nowait=True)
Siehe die Dokumente
Dies funktioniert natürlich nur, wenn das Back-End die Funktion "Für Update auswählen" unterstützt, was beispielsweise bei SQLite nicht der Fall ist. Leider:
nowait=True
wird von MySql nicht unterstützt, da muss man: verwendennowait=False
, das nur blockiert, bis die Sperre aufgehoben wird.quelle
Tatsächlich helfen Ihnen Transaktionen hier nicht viel ... es sei denn, Sie möchten, dass Transaktionen über mehrere HTTP-Anforderungen ausgeführt werden (was Sie höchstwahrscheinlich nicht möchten).
Was wir normalerweise in diesen Fällen verwenden, ist "Optimistic Locking". Das Django ORM unterstützt das meines Wissens nicht. Es gab jedoch einige Diskussionen über das Hinzufügen dieser Funktion.
Sie sind also alleine. Grundsätzlich sollten Sie Ihrem Modell ein "Versions" -Feld hinzufügen und es als verstecktes Feld an den Benutzer übergeben. Der normale Zyklus für ein Update ist:
Um eine optimistische Sperrung zu implementieren, überprüfen Sie beim Speichern der Daten, ob die vom Benutzer zurückgegebene Version mit der in der Datenbank übereinstimmt, aktualisieren Sie die Datenbank und erhöhen Sie die Version. Wenn dies nicht der Fall ist, bedeutet dies, dass seit dem Laden der Daten eine Änderung vorgenommen wurde.
Sie können dies mit einem einzelnen SQL-Aufruf tun, mit:
UPDATE ... WHERE version = 'version_from_user';
Dieser Aufruf aktualisiert die Datenbank nur, wenn die Version noch identisch ist.
quelle
Django 1.11 bietet drei praktische Optionen , um diese Situation abhängig von Ihren Anforderungen an die Geschäftslogik zu bewältigen:
Something.objects.select_for_update()
wird blockiert, bis das Modell frei wirdSomething.objects.select_for_update(nowait=True)
und abfangen,DatabaseError
ob das Modell derzeit für die Aktualisierung gesperrt istSomething.objects.select_for_update(skip_locked=True)
gibt die aktuell gesperrten Objekte nicht zurückIn meiner Anwendung, die sowohl interaktive als auch Batch-Workflows für verschiedene Modelle enthält, habe ich diese drei Optionen gefunden, um die meisten meiner gleichzeitigen Verarbeitungsszenarien zu lösen.
Das "Warten"
select_for_update
ist sehr praktisch in sequentiellen Batch-Prozessen - ich möchte, dass sie alle ausgeführt werden, aber lassen Sie sie sich Zeit. Dasnowait
wird verwendet, wenn ein Benutzer ein Objekt ändern möchte, das derzeit für die Aktualisierung gesperrt ist. Ich werde ihm nur mitteilen, dass es derzeit geändert wird.Das
skip_locked
ist für eine andere Art von Update nützlich, wenn Benutzer eine erneute Prüfung eines Objekts auslösen können - und ich weiß nicht , wer es auslöst, solange es ausgelöst wird , soskip_locked
ermöglicht es mir , die duplizierten Auslöser leise zu überspringen.quelle
Weitere Informationen finden Sie unter https://github.com/RobCombs/django-locking . Das Sperren erfolgt auf eine Weise, die keine ewigen Sperren hinterlässt, indem Javascript beim Verlassen der Seite entsperrt wird und Zeitüberschreitungen gesperrt werden (z. B. falls der Browser des Benutzers abstürzt). Die Dokumentation ist ziemlich vollständig.
quelle
Sie sollten wahrscheinlich zumindest die Django-Transaktions-Middleware verwenden, auch unabhängig von diesem Problem.
Was Ihr eigentliches Problem betrifft, dass mehrere Benutzer dieselben Daten bearbeiten ... ja, verwenden Sie die Sperrung. ODER:
Überprüfen Sie, gegen welche Version ein Benutzer aktualisiert (tun Sie dies sicher, damit Benutzer das System nicht einfach hacken können, um zu sagen, dass sie die neueste Kopie aktualisiert haben!), Und aktualisieren Sie nur, wenn diese Version aktuell ist. Andernfalls senden Sie dem Benutzer eine neue Seite mit der Originalversion, die er bearbeitet hat, der eingereichten Version und den neuen Versionen, die von anderen geschrieben wurden, zurück. Bitten Sie sie, die Änderungen in einer vollständig aktuellen Version zusammenzuführen. Sie können versuchen, diese mithilfe eines Toolset wie diff + patch automatisch zusammenzuführen, aber die manuelle Zusammenführungsmethode muss ohnehin für Fehlerfälle funktionieren. Beginnen Sie also damit. Außerdem müssen Sie den Versionsverlauf beibehalten und Administratoren erlauben, Änderungen rückgängig zu machen, falls jemand die Zusammenführung unbeabsichtigt oder absichtlich durcheinander bringt. Aber das solltest du wahrscheinlich trotzdem haben.
Es gibt sehr wahrscheinlich eine Django-App / Bibliothek, die das meiste für Sie erledigt.
quelle
Eine andere Sache zu suchen ist das Wort "atomar". Eine atomare Operation bedeutet, dass Ihre Datenbankänderung entweder erfolgreich durchgeführt wird oder offensichtlich fehlschlägt. Eine schnelle Suche zeigt diese Frage nach atomaren Operationen in Django.
quelle
Die Idee oben
updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\ .update(updated_field=new_value, version=e.version+1) if not updated: raise ConcurrentModificationException()
sieht gut aus und sollte auch ohne serialisierbare Transaktionen gut funktionieren.
Das Problem besteht darin, wie das taube Verhalten von .save () erweitert werden kann, damit keine manuelle Installation durchgeführt werden muss, um die Methode .update () aufzurufen.
Ich habe mir die Idee des Custom Managers angesehen.
Mein Plan ist es, die von Model.save_base () aufgerufene Manager _update-Methode zu überschreiben, um das Update durchzuführen.
Dies ist der aktuelle Code in Django 1.3
def _update(self, values, **kwargs): return self.get_query_set()._update(values, **kwargs)
Was IMHO getan werden muss, ist so etwas wie:
def _update(self, values, **kwargs): #TODO Get version field value v = self.get_version_field_value(values[0]) return self.get_query_set().filter(Q(version=v))._update(values, **kwargs)
Ähnliches muss beim Löschen passieren. Das Löschen ist jedoch etwas schwieriger, da Django über django.db.models.deletion.Collector in diesem Bereich ziemlich viel Voodoo implementiert.
Es ist seltsam, dass Modren-Tools wie Django keine Anleitung für Optimictic Concurency Control haben.
Ich werde diesen Beitrag aktualisieren, wenn ich das Rätsel löse. Hoffentlich wird die Lösung auf eine nette pythonische Art und Weise sein, die nicht jede Menge Codierung, seltsame Ansichten, das Überspringen wesentlicher Teile von Django usw. beinhaltet.
quelle
Um sicher zu gehen, muss die Datenbank Transaktionen unterstützen .
Wenn es sich bei den Feldern um "Freiform" handelt, z. B. Text usw., und Sie mehreren Benutzern erlauben müssen, dieselben Felder zu bearbeiten (Sie können nicht die Berechtigung eines einzelnen Benutzers für die Daten haben), können Sie die Originaldaten in einem speichern Variable. Überprüfen Sie beim Festschreiben des Benutzers, ob sich die Eingabedaten gegenüber den Originaldaten geändert haben (wenn nicht, müssen Sie die Datenbank nicht durch Umschreiben alter Daten belästigen), wenn die Originaldaten im Vergleich zu den aktuellen Daten in der Datenbank identisch sind Sie können speichern, wenn es sich geändert hat, können Sie dem Benutzer den Unterschied zeigen und ihn fragen, was zu tun ist.
Wenn es sich bei den Feldern um Zahlen handelt, z. B. Kontostand, Anzahl der Artikel in einem Geschäft usw., können Sie dies automatischer verarbeiten, wenn Sie die Differenz zwischen dem ursprünglichen Wert (gespeichert, als der Benutzer mit dem Ausfüllen des Formulars begann) und dem neuen Wert berechnen, den Sie können Starten Sie eine Transaktion, lesen Sie den aktuellen Wert, addieren Sie die Differenz und beenden Sie die Transaktion. Wenn Sie keine negativen Werte haben können, sollten Sie die Transaktion abbrechen, wenn das Ergebnis negativ ist, und dies dem Benutzer mitteilen.
Ich kenne Django nicht, also kann ich dir die cod3s nicht geben ..;)
quelle
Von hier:
So verhindern Sie das Überschreiben eines Objekts, das von einer anderen Person geändert wurde
Ich gehe davon aus, dass der Zeitstempel als verstecktes Feld in der Form gespeichert wird, in der Sie die Details speichern möchten.
def save(self): if(self.id): foo = Foo.objects.get(pk=self.id) if(foo.timestamp > self.timestamp): raise Exception, "trying to save outdated Foo" super(Foo, self).save()
quelle