Datensatz zum bearbeiten sperren

Hi @ all,

zwei kurze Fragen!
Ich schreibe ein Webfrontend, das über PHP auf eine PostgresQL-DB zugreift. Es sollen Datensätze zum bearbeiten im Browser geladen und anschließend in der DB gespeichert werden, wobei es natürlich kein lost update geben darf, d.h. dieser Datensatz darf nur von einem User zur Zeit bearbeitet werden. Dabei handelt es sich offensichtlich um 2 Prozesse, sodaß ich nicht ohne weiteres mit Transaktionen arbeiten kann (leider).
Ich würde die Datensatzsperre daher manuell über irgendwelche “lock”-Felder in den Tabellen realiesieren, die die ID des Bearbeitenden und einen Zeitstempel beinhalten, sodaß beim Anfordern der Daten zunächst geprüft wird, ob eine Bearbeitung gerade erlaubt ist. Andernfalls gibt’s direkt ne entsprechende Meldung.
Frage 1: ist dieses Vorgehen gut, oder gibt’s da viel bessere Lösungen?
Und Frage 2 (anderes Thema!): muß ich mich im Fall von PostgresQL ganz allgemein darum kümmern, was passiert, wenn mehrere Benutzer exakt im gleichen Moment bspw. einen INSERT-Befehle auf derselben Tabelle ausführen? Habe irgendwo gelesen, daß es dafür automatische Sperren gibt…?!
Vielen Dank im Voraus!

HTTP ist stateless - also NICHT verbindungsorientiert - du weisst also nicht ob der User “noch connected” ist …

du musst also dafuer Sorge tragen, dass du nach dem Sperren auch erkennst, ob die Sperrung hinfällig ist - Ende der Session -
im leider haeufig vorkommenden dummen Fall, wenn der User seinen browser schliesst, waere es dumm die Sperrung erst nach einem Timeout (wie lang soll der sein?) frei zu geben.

INSERTs sind atomar …

Transaktionen sind teuer …

Alles im allen empfehle ich in solchen Fällen pragmatisch zu denken - warum willst du sperren - was soll bei einem Insert denn schreckliches passieren ?

Was ich dir noch sonst empfehlen koennte weiss ich nicht, da du dein Problem nicht sehr detailliert geschildert hast.

Vielen Dank soweit, das mit der Freigabe erst nach Timeout gefällt mir auch überhaupt nicht, habe aber keine bessere Idee.
Das Problem ist einfach geschildert:
Es werden Werte und Ergebnisse von Laborexperimenten über ein Webformular in der DB gespeichert. Da diese Werte per Hand eingegeben werden, können leicht Fehler passieren. Die Anzeige bereits eingebener Experimentdaten ist unproblematisch, aber wenn etwas falsch ist, soll der Benutzer im “Ansichtsmodus” des Experiments auf einen Button “bearbeiten” klicken können, dadurch in einen “Bearbeitungsmodus” wechseln, seine Änderungen dann eingeben und per Klick auf “Änderungen speichern” speichern. Ich wüsste halt nicht, wie man zuverlässig das Ende der Beabeitung bestimmen soll, da der Benutzer sich ja unvorhergesehen verhalten kann, wie du schon sagtest. Daher als Notlösung der Timout…
Und zu dem Insert war ich einfach nicht sicher, ob Postgres ohne explizite Anweisungen mit ner Menge von wirklich gleichzeitigen Befehlen klar kommt. Wenn bspw. ein SERIAL-Id-Feld in der Tabelle ist, ist es ja theoretisch denkbar, daß 2 Inserts sich gleichzeitig die letzte Id holen (was dann bei jedem Insert dieselbe wäre), dann ausgeführt werden und somit auch denselben Wert als nächste SERIAL-ID haben o.ä.

Das brauchst du alles garnicht erfahren …

  • der User klickt bearbeiten → bei diesem post/get lieferst du ihm ein eingabe-formular mit den Daten des gewählten Datensatzes. An dieser stelle musst du ueberhaupt nicht wissen was der user will - er koennte ja auch einfach nur mal “nachschauen” wollen. Du startest also ueberhaupt keine transaktion.
  • der user Ändert im Formular herum und drueck speichern. Hier wirst du ein Update absetzen. Um sicher zu gehen, dass der User keine Änderungen von einem anderen User ueberschreibt, könntest du ein Feld in dem Datensatz für eine Datenversion, Änderungszeitpunkt oder besser eine art lfd. Nummer / Sequenz vorsehen, die du immer erhöhst/ aktualisierst, wenn ein User speichern drueckt. Diese nummer musst du natuerlich beim Anzeigen vorher auch dem Browser / Formular in einem unsichtbaren Feld uebergeben und von dort beim Speichern wieder zurueck schicken. Dann kannst du diese Nummer in deinem UPDATE Statement in die WHERE Bedingung aufnehmen. Wenn die ein anderer User diesen Satz zwischenzeitlich geöffnet und erfolgreich gespeichert hat, wird dieses jetzige UPDATE ins leere laufen. Du kannst dem Client ja ne Fehlermeldung liefern, wenn die anzahl der betroffenen Records == 0 ist.

Und zu dem Insert war ich einfach nicht sicher, ob Postgres ohne explizite Anweisungen mit ner Menge von wirklich gleichzeitigen Befehlen klar kommt. Wenn bspw. ein SERIAL-Id-Feld in der Tabelle ist, ist es ja theoretisch denkbar, daß 2 Inserts sich gleichzeitig die letzte Id holen (was dann bei jedem Insert dieselbe wäre), dann ausgeführt werden und somit auch denselben Wert als nächste SERIAL-ID haben o.ä.

Postgres ist EIN Prozess … wirklich GLEICHZEITIG kennt der auch nicht. Selbst wenn da mehrere Threads die Aufgaben des PG uebernehmen wird das mit sicherheit alles schön synchronisiert werden. Auf jeden Fall ist es ein gutes Zeichen, dass Du dir darueber Gedanken machst. Solltest du immer. Hier kann ich Dich allerdings beruhigen … Die Änderung der Sequenz innerhalb eines INSERT sollte “Threadsafe” sein

Ok, einleuchtend und wohl die bessere Lösung. Einziger Nachteil ist wirklich, dass ne Menge von Eingaben evtl. ins Leere läuft und der jeweilige Benutzer erst beim Speicherversuch merkt, daß alle Arbeit umsonst war.

Alles klar, thx, Frage beantwortet!

Du holst einen Datensatz und zeigst das zum Korrigieren an. Wenn der Benutzer diesen speichern will, prüfst du (via select for update), ob sich die Daten zwischenzeitlich geändert haben. Falls nein, machst Du das Update, falls ja, fährst Du das ‘select for update’ via Rollback zurück und zeigst dem Benutzer die nun aktuellen Daten, vielleicht mit einem Hinweis, daß diese von jemand anderen zwischenzeitlich geändert wurden. Er kann das dann akzeptieren oder erneut ändern.

Und zu dem Insert war ich einfach nicht sicher, ob Postgres ohne explizite Anweisungen mit ner Menge von wirklich gleichzeitigen Befehlen klar kommt. Wenn bspw. ein SERIAL-Id-Feld in der Tabelle ist, ist es ja theoretisch denkbar, daß 2 Inserts sich gleichzeitig die letzte Id holen (was dann bei jedem Insert dieselbe wäre), dann ausgeführt werden und somit auch denselben Wert als nächste SERIAL-ID haben o.ä.

Nein. Der Datentyp ‘serial’ ist extra dafür geschaffen, mit exakt solchen Problemen sicher umzugehen.

Andreas

So mach’ ich’s! Nochmals dankeschön euch beiden.