Rolf B: php, SQL, Multiuser - SESSION oder nicht ?

Beitrag lesen

Hallo PHP-Neuling,

wenn zwei User den gleichen Datensatz updaten, gibt es immer Probleme. Egal ob Session oder nicht.

Ganz abstrakt hast Du bei einem Edit immer die Abfolge Lesen, Bearbeiten, Update, und zwischen Lesen und Update vergeht Zeit. Während dieser Zeit kann ein weiterer User einen Edit beginnen.

D.h. wenn die Daten auf dem Stand Nr. 17 sind, kann es immer passieren, dass zwei User den Stand Nr. 17 lesen. Beide speichern irgendwann, und einer von beiden speichert als letzter. Dieser Stand bleibt erhalten, der andere wird überschrieben.

Das ist ein Grundproblem bei non-conversational Systemen. "Conversational" liegt dann vor, wenn man einen Fat Client hat, der DB-Sperren während der Edit-Dauer aufrecht erhalten kann. Ein non-conversational System funktioniert mit einem Client, der immer nur kurze Requests an den Server schickt. Zwischen zwei Requests weiß der Server nichts vom Client. Normale Web-Anwendungen sind immer non-conversational (nicht normal sind Systeme mit Websockets, die einen dauerhaft laufenden Prozess am Server haben, das lasse ich außen vor).

Um in non-conversational Systemem mit Read/Edit/Update umgehen zu können, gibt es mehrere Ansätze. Wenn's dazu was in unserem Wiki gibt, dann bin ich gerade zu dumm, es zu finden, und aus dem Handgelenk schreibe ich dazu auch keinen Artikel.

Die sichere Methode ist das pessimistische Sperren. Dafür hat man im Datensatz einen Bereich, wo man vermerkt, dass der Satz gesperrt ist. Im einfachsten Fall ein 0/1 Schalter, im ausführlichen Fall die User-ID des Sperrenden und den Timestamp der Sperrung. Wer einen Satz bearbeiten will, muss das Sperrkennzeichen setzen, was innerhalb einer Transaktion und mit korrekten Isolation Level (SELECT ... FOR UPDATE in InnoDB oder ein LOCK TABLE in MyISAM) machbar ist.

Problem: So eine Sperre kann hängenbleiben, wenn der Sperrende sie nicht explizit freigibt. Und man muss den Edit ausdrücklich auslösen, denn es wäre falsch, jeden Satz beim Lesen sofort zu sperren. Meistens wird man ja keinen Update beabsichtigen, sondern die Daten nur ansehen wollen.

Eine flexiblere Lösung ist das optimistische Sperren. Auch da hast Du Sperrkennzeichen in der Tabelle, aber man geht anders vor. Optimistisches Sperren ist nicht immer sinnvoll, aber in Systemen, wo hauptsächlich gelesen wird, die gängige Methode.

Schritt 1: Datensatz lesen (SELECT daten, lastupdate FROM table WHERE key=value)

Schritt 2: Form ausgeben, dabei lastupdate in Session speichern oder verschlüsseln und als hidden input im Form ablegen), User editieren lassen, POST empfangen, altes lastupdate aus Session oder Post-Daten holen

Schritt 3: Datensatz schreiben:

UPDATE table 
   SET daten=neuedaten 
       lastupdate=CURRENT_TIMESTAMP
 WHERE key=value 
   AND lastupdate=altes_lastupdate

Wenn jemand 'reingegrätscht ist, wird kein Satz gefunden weil lastupdate nicht mehr den gleichen Wert wie beim Lesen hat. An der Stelle beginnen die Probleme des optimistischen Sperrens: wie geht man damit um.

Einfachste Lösung: Update abweisen und dem User ein Ätsch melden. Bitte nochmal.

Komplexere Lösung: Das Form mit den geposteten Daten wieder ausgeben, die zwischenzeitlich vorgenommene Änderung darunterschreiben, den User zum Konsolidieren auffordern und diesen Input akzeptieren. Setzt natürlich voraus, dass die User das verantwortungsvoll tun. Und man kann beliebig viel Aufwand investieren, um das Mischen der Änderungen zu automatisieren.

Es steht Dir frei, beliebige weitere Lösungen des Problems zu konstruieren. Wichtig ist nur, dass man das Problem erkennt und den User damit nicht allein lässt.

Good lock![1]
Rolf

--
sumpsi - posui - obstruxi

  1. this line was intentionally made punny ↩︎