Wie vermeide ich Wettkampfbedingungen in einer Webanwendung?

31

Stellen Sie sich eine E-Commerce-Site vor, auf der Alice und Bob die Produktlisten bearbeiten. Alice verbessert die Beschreibungen, während Bob die Preise aktualisiert. Gleichzeitig bearbeiten sie das Acme Wonder Widget. Bob beendet zuerst und speichert das Produkt mit dem neuen Preis. Alice braucht etwas länger, um die Beschreibung zu aktualisieren, und wenn sie fertig ist, speichert sie das Produkt mit ihrer neuen Beschreibung. Leider überschreibt sie auch den Preis mit dem alten Preis, der nicht vorgesehen war.

Nach meiner Erfahrung treten diese Probleme in Webanwendungen häufig auf. Einige Softwareprodukte (z. B. Wiki-Software) sind dagegen geschützt. In der Regel schlägt der zweite Speichervorgang mit "Die Seite wurde während der Bearbeitung aktualisiert" fehl. Die meisten Websites verfügen jedoch nicht über diesen Schutz.

Es ist erwähnenswert, dass die Controller-Methoden für sich genommen thread-sicher sind. Normalerweise verwenden sie Datenbanktransaktionen, was sie in dem Sinne sicher macht, dass Alice und Bob, wenn sie im selben Moment versuchen zu speichern, keine Beschädigung verursachen. Die Rennbedingungen sind darauf zurückzuführen, dass Alice oder Bob veraltete Daten in ihrem Browser haben.

Wie können wir solche Rennbedingungen verhindern? Insbesondere würde ich gerne wissen:

  • Welche Techniken können verwendet werden? zB Verfolgung des Zeitpunkts der letzten Änderung. Was sind die Vor- und Nachteile von jedem.
  • Was ist eine hilfreiche Benutzererfahrung?
  • In welchen Frameworks ist dieser Schutz eingebaut?
paj28
quelle
Sie haben bereits die Antwort gegeben: indem Sie das Änderungsdatum von Objekten nachverfolgen und es mit dem Alter der Daten vergleichen, die andere Änderungen zu aktualisieren versuchen. Möchten Sie etwas anderes wissen, z. B. wie man es effizient macht?
Kilian Foth
@KilianFoth - Ich habe einige Informationen hinzugefügt, die ich besonders gerne wissen
möchte
1
Ihre Frage ist in keiner Weise speziell für Webanwendungen. Desktopanwendungen können genau dasselbe Problem haben. Die typischen Lösungsstrategien werden hier beschrieben: stackoverflow.com/questions/129329/…
Doc Brown
2
Zu Ihrer
Information
Einige Diskussionen zu Django hier
paj28

Antworten:

17

Sie müssen "Ihre Schreibvorgänge lesen", dh, bevor Sie eine Änderung aufzeichnen, müssen Sie den Datensatz erneut lesen und prüfen, ob seit dem letzten Lesen Änderungen daran vorgenommen wurden. Sie können dies Feld für Feld (feinkörnig) oder basierend auf einem Zeitstempel (grobkörnig) durchführen. Während Sie diese Prüfung durchführen, benötigen Sie eine exklusive Sperre für den Datensatz. Wenn keine Änderungen vorgenommen wurden, können Sie Ihre Änderungen notieren und die Sperre aufheben. Wenn sich der Datensatz in der Zwischenzeit geändert hat, brechen Sie die Transaktion ab, heben die Sperre auf und benachrichtigen den Benutzer.

Phil
quelle
Das klingt nach dem praktischsten Ansatz. Kennen Sie Frameworks, die dies implementieren? Ich denke, das größte Problem bei diesem Schema ist, dass eine einfache Meldung "Konflikt bearbeiten" die Benutzer frustriert, aber der Versuch, die Änderungssätze (manuell oder automatisch) zusammenzuführen, ist schwierig.
paj28
Leider kenne ich keine Frameworks, die dies standardmäßig unterstützen. Ich denke nicht, dass eine Bearbeitungs-Confilict-Fehlermeldung als frustrierend empfunden wird, solange sie nicht zu oft auftritt. Letztendlich hängt es von der Benutzerlast des Systems ab, ob Sie nur den Zeitstempel überprüfen oder eine komplexere Zusammenführungsfunktion implementieren.
Phil
Ich habe ein PC-verteiltes Datenbankprodukt unterhalten, das den differenzierten Ansatz (gegenüber der lokalen Datenbankkopie) verwendet: Wenn ein Benutzer den Preis und der andere die Beschreibung änderte - kein Problem! Genau wie im wirklichen Leben. Wenn zwei Benutzer den Preis geändert haben, erhält der zweite Benutzer eine Entschuldigung und versucht seine Änderung erneut. Kein Problem! Dies erfordert keine Sperren, außer in dem Moment, in dem Daten in die Datenbank geschrieben werden. Es spielt keine Rolle, ob ein Benutzer zum Mittagessen geht, während sein Wechselgeld auf dem Bildschirm angezeigt wird, und es später übermittelt. Bei Änderungen an entfernten Datenbanken wurden die Zeitstempel der Datensätze verwendet.
1
Dataflex hatte eine Funktion namens "reread ()", die das tut, was Sie beschreiben. In den späteren Versionen war es in einer Mehrbenutzerumgebung sicher. Und in der Tat war dies die einzige Möglichkeit, solche verschachtelten Updates zum Laufen zu bringen.
können Sie ein Beispiel geben , wie dies mit SQL Server zu tun? \
l --''''''--------- ‚‘ ‚‘ ‚‘ ‚‘ ‚‘ ‚‘
10

Ich habe zwei Hauptwege gesehen:

  1. Fügen Sie den Zeitstempel der letzten Aktualisierung der Seite, die Sie gerade bearbeiten, in eine ausgeblendete Eingabe ein. Wenn ein Commit ausgeführt wird, wird der Zeitstempel mit dem aktuellen verglichen und wenn er nicht übereinstimmt, wurde er von einer anderen Person aktualisiert und gibt einen Fehler zurück.

    • Pro: Mehrere Benutzer können verschiedene Teile der Seite bearbeiten. Die Fehlerseite kann zu einer Diff-Seite führen, auf der der zweite Benutzer seine Änderungen auf der neuen Seite zusammenführen kann.

    • con: Manchmal gehen große Teile des Aufwands bei großen gleichzeitigen Bearbeitungen verloren.

  2. Wenn ein Benutzer anfängt, die Seitensperre für einen angemessenen Zeitraum zu bearbeiten, erhält ein anderer Benutzer beim Versuch, sie zu bearbeiten, eine Fehlerseite und muss warten, bis entweder die Sperre abläuft oder der erste Benutzer die Sperre bestätigt hat.

    • pro: edit-aufwände werden nicht verschwendet.

    • con: Ein skrupelloser Benutzer kann eine Seite auf unbestimmte Zeit sperren. Eine Seite mit einer abgelaufenen Sperre kann möglicherweise weiterhin ein Commit ausführen, sofern nicht anders verfahren wird (mithilfe von Technik 1).

Ratschenfreak
quelle
7

Verwenden Sie Optimistic Concurrency Control .

Fügen Sie der betreffenden Tabelle eine versionNumber- oder versionTimestamp-Spalte hinzu (Ganzzahl ist am sichersten).

Benutzer 1 liest Datensatz:

{id:1, status:unimportant, version:5}

Benutzer 2 liest Datensatz:

{id:1, status:unimportant, version:5}

Benutzer 1 speichert den Datensatz, dies erhöht die Version:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

Benutzer 2 versucht, den gelesenen Datensatz zu speichern:

save {id:1, status:unimportant, version:5}
ERROR

Ruhezustand / JPA kann dies automatisch mit der @VersionAnnotation tun

Sie müssen den Status des gelesenen Datensatzes im Allgemeinen in einer Sitzung beibehalten (dies ist sicherer als in einer verborgenen Formularvariablen).

Neil McGuigan
quelle
Danke ... besonders hilfreich, um über @Version Bescheid zu wissen. Eine Frage: Warum ist es sicher, den Status in der Sitzung zu speichern? In diesem Fall würde ich befürchten, dass die Verwendung des Zurück-Buttons die Dinge verwirren könnte.
paj28
Die Sitzung ist sicherer als ein verstecktes Formularelement, da der Benutzer den Versionswert nicht ändern kann. Wenn das kein Problem ist, ignorieren Sie den Teil über die Sitzung
Neil McGuigan,
Diese Technik wird als optimistisch offline Sperre , und es ist in SQLAlchemy als auch
paj28
@ paj28 - dieser Link für SQLAlchemyverweist nicht auf optimistische Offline-Sperren, und ich kann ihn in den Dokumenten nicht finden. Hatten Sie einen hilfreicheren Link oder haben Sie nur allgemein auf SQLAlchemy hingewiesen?
Dwanderson
@dwanderson Ich meinte den Versionszähler Teil dieses Links.
paj28
1

Einige ORM-Systeme (Object Relational Mapping) erkennen, welche Felder eines Objekts seit dem Laden aus der Datenbank geändert wurden, und erstellen die SQL-Aktualisierungsanweisung, um nur diese Werte festzulegen. ActiveRecord für Ruby on Rails ist ein solches ORM.

Der Nettoeffekt ist, dass Felder, die der Benutzer nicht geändert hat, nicht im UPDATE-Befehl enthalten sind, der an die Datenbank gesendet wird. Personen, die verschiedene Felder gleichzeitig aktualisieren, überschreiben die Änderungen nicht gegenseitig.

Suchen Sie je nach verwendeter Programmiersprache nach den verfügbaren ORMs und prüfen Sie, ob diese nur Spalten in der Datenbank aktualisieren, die in Ihrer Anwendung als "dirty" gekennzeichnet sind.

Greg Burghardt
quelle
Hallo Greg. Leider hilft das bei solchen Rennbedingungen nicht. Wenn Sie mein ursprüngliches Beispiel betrachten, wird die Preisspalte beim Speichern von Alice im ORM als verschmutzt angezeigt und aktualisiert - auch wenn die Aktualisierung nicht gewünscht ist.
paj28
1
@ paj28 Der Schlüsselpunkt in Gregs Antwort lautet " Felder, die der Benutzer nicht geändert hat ". Alice hat den Preis nicht geändert, daher hat der ORM nicht versucht, den "Preis" -Wert in der Datenbank zu speichern.
Ross Patterson
@ RossPatterson - Woher kennt der ORM den Unterschied zwischen Feldern, die der Benutzer geändert hat, und veralteten Daten aus dem Browser? Zumindest ohne eine zusätzliche Nachverfolgung. Wenn Sie Gregs Antwort bearbeiten möchten, um eine solche Nachverfolgung einzuschließen, oder eine andere Antwort übermitteln möchten, ist dies hilfreich.
paj28
@ paj28 Ein Teil des Systems muss wissen, was der Benutzer getan hat, und nur die Änderungen speichern, die der Benutzer vorgenommen hat. Wenn der Benutzer den Preis geändert, dann zurück geändert und dann übermittelt hat, sollte dies nicht als "etwas, das der Benutzer geändert hat" gelten, da dies nicht der Fall war. Wenn Sie ein System haben, das diese Ebene der Parallelitätskontrolle erfordert, müssen Sie es auf diese Weise erstellen. Wenn nicht, nicht
@nocomprende - Ein Teil, sicher - aber nicht der ORM, wie diese Antwort sagt
paj28