Sven Rautenberg: SELECT und UPDATE parallel ausführen?

Beitrag lesen

Moin!

Hallo!

ich habe ein Problem mit einem Select und Update Statement.

Unzwar habe ich eine DB in der laufen neue Einträge hinzukommen. Und es gibt Clients, die sich immer einzelne Einträge abholen. Aber immer nur einen. Nun darf aber kein Eintrag an zwei Clients gesendet werden.

Deswegen habe ich nun ein STATUS Feld eingeführt, was nach dem select durch eine UPDATE Query auf 1 gesetzt wird. Das Problem ist, dass die beiden Befehle nacheinander ausgeführt werden und es somit schon vorgekommen ist, dass zwei Clients die gleiche AW bekommen haben.

Gibt es eine Möglichkeit SELECT und Update als ein StateMent auszuführen?

Ändere deine Datenbankstrategie!

Was du brauchst, ist eine atomare Operation in der Datenbank. Das kann natürlich mit Transaktionen klappen (die verbinden mehrere einzelne Operationen zu einer quasi-atomaren Operation). Aber im Prinzip hast du "nur" folgendes Problem: Du mußt einen einzigen Datensatz der vorhandenen Tabelle so kennzeichnen, dass er in der Folge nur von einem Client bearbeitet werden kann.

Und diese Operation heißt "UPDATE". :) Ein einzelnes Update klappt immer, ohne dass es von anderen Updates oder Selects beeinflußt wird.

Also: Deine Kennzeichnung des Datensatzes für den Client muß diesen eindeutig kennzeichnen. Dazu benötigst du sehr wahrscheinlich noch die eine oder andere Spalte zusätzlich in deiner Datenbank.

Ich stelle mir die Sache so vor: Deine Datentabelle enthält (neben den Spalten für die Daten) noch mindestens eine Spalte für den Bearbeitungsstatus und eine Spalte für eine Client-ID, die den Client eindeutig identifiziert, der den jeweiligen Eintrag bearbeitet.

Beispiel:
ID   Status   CID  Daten...
1    neu      ---  ....
2    neu      ---  ....
3    neu      ---  ....

Diese Tabelle hat drei frische Einträge, die von Clients bearbeitet werden sollen. Dein bisheriger Ansatz dürfte gewesen sein, dass du zunächst mit SELECT nachschaust, ob Einträge zur Verfügung stehen, und den jeweiligen Eintrag dann mit UPDATE lockst, damit kein anderer mehr rankommt. Das führt bekanntermaßen zu Problemen. :)

Wenn du stattdessen folgendes UPDATE auf obige Tabelle machst, dann kriegst du exakt _einen_ Datensatz verändert und kannst ihn _hinterher_ mit SELECT auslesen:
UPDATE tabelle SET status="lock", CID="23" WHERE status="neu" LIMIT 1

LIMIT 1 sorgt dafür, dass exakt ein Datensatz verändert wird, und nicht alle. WHERE status="neu" schließt alle Zeilen aus, die nicht neu sind (wie du siehst, sind das zumindest schon mal nicht die Zeilen, die durch diese Operation auf "locked" gestellt wurden - doch dazu gleich mehr).

Nach dieser Operation hast du genau eine Zeile in der Datenbank, die "locked" ist und als CID die jeweilige Client-ID hat. Danach kannst du SELECTieren:
SELECT ... FROM tabelle WHERE status="lock" AND CID="23".

Diesen Eintrag verarbeitest du dann - und wenn du fertig bist, gibst du ihn mit
UPDATE tabelle SET status="frei", CID="---" WHERE id="..."
wieder frei. Beachte den dritten Statuszustand "frei" - nur damit kannst du brandneue Einträge von bereits bearbeiteten Einträgen unterscheiden - falls ein alter Eintrag bearbeitet werden soll, kannst du den Status ja wieder auf "locked" setzen.

Wenn die Gefahr besteht, dass dein Datenbankdesign durch Systemabstürze etc. durcheinanderkommt, d.h. wenn die Möglichkeit besteht, dass ein gelockter Eintrag nicht mit 100% Sicherheit am Ende der Bearbeitung auf "frei" gesetzt werden kann, dann mußt du, damit du einen Eintrag beim SELECT zweifelsfrei wiederkriegst, mit eindeutigen IDs arbeiten, die du beim UPDATE mit einfügst:

UPDATE tabelle SET status="lock", CID="23", UID="uniqueID" WHERE status="neu" LIMIT 1

Die Spalte UID muß natürlich hinzugefügt werden.

Da du dir die uniqueID vor dem Update ausdenkst (PHP bietet z.B. eine entsprechde Funktion) und merkst, kannst du damit das SELECT eindeutig machen:
SELECT ... FROM tabelle WHERE status="lock" AND CID="23" AND UID="uniqueID"

Du kannst feststellen, ob was im System schiefgegangen ist (d.h. gelockte Einträge nicht wieder freigegeben wurden), indem du für jede CID ein SELECT für gelockte Einträge ausführst. Das Ergebnis darf maximal aus _einer_ Zeile bestehen (nur ein Lock pro Client zum Bearbeiten pro Zeit) - andernfalls mußt du irgendwie aufräumen. Dieses Problem wirst du unter Umständen aber auch mit Transaktionen haben.

- Sven Rautenberg

--
Diese Signatur gilt nur am Freitag.