PHP-Neuling: php, SQL, Multiuser - SESSION oder nicht ?

Hallo zusammen :)

Ist mir fast peinlich jetzt. Aber ich bin einfach unsicher. Und so richtig finde ich nichts detailliertes

Wie verhält sich das im Backend des Servers .... :

Ich habe eine website in PHP mit MySQL DB erstellt. Die Seite ist in Tabellenform und ruft nur Daten aus der DB ab. Diese Daten können über einen Login von usern gelesen, geändert und erweitert werden. Mehrere user können gleichzeitig in der Seite arbeiten, Speichern, löschen usw.

Jetzt zur Anfängerfrage ...

Wenn ich ohne SESSION Variablen arbeite, kann es da theoretisch passieren das Informationen verloren oder überschrieben/gelöscht werden, wenn zufällig 2 Nutzer gleichzeitig am selben Datensatz arbeiten ?

Oder wenn EIN Nutzer im selben Browser evtl. 2 Tabs zum bearbeiten nimmt ?

Da fehlt mir leider noch das Verständnis für. Allerdings bereitet mir das Sorgen :/

Viele Grüße, und bleibt gesund !

akzeptierte Antworten

  1. Hi there,

    Jetzt zur Anfängerfrage ...

    Wenn ich ohne SESSION Variablen arbeite, kann es da theoretisch passieren das Informationen verloren oder überschrieben/gelöscht werden, wenn zufällig 2 Nutzer gleichzeitig am selben Datensatz arbeiten ?

    Mit der Session hat das überhaupt nichts zu tun. Das ist eine ganz andere Ebene. Entscheidend für Dich ist in dem Fall das Verhalten der Datenbank, die sich um PHP-Sessions etc nicht kümmert. Die verhindert ein solches Szenario normalerweise. Was aber passieren kann, daß ein Anwender einen Datensatz abspeichert und ein anderer User über diesen Satz wieder "darüberspeichert", dann ist das, was der erste Anwender eingegeben hat, natürlich weg.

    Oder wenn EIN Nutzer im selben Browser evtl. 2 Tabs zum bearbeiten nimmt ?

    Gleicher Fall - was zuletzt gespeichert wird, das gilt...

  2. 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 ↩︎

    1. Hallo Rolf,

      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.

      Im Wiki nicht, aber im Archiv.

      Ich habe den Beitrag deshalb noch im Gedächtnis und wiedergefunden, weil der gute Tom damals für das Locking, was eigentlich gar keins ist, den eigenen Ausdruck "Academic Locking" erfunden hat. Und weil es den vorher noch nicht gab, wurde er gemobbt dafür. Das wäre so, als wäre es im Forum für die Energie des Verstehens verboten, eigene Gedanken zu entwickeln.

      Ich habe mir daher damals geschworen, hier erst selber zu posten, wenn ich nicht mehr im Beruf stehe.

      Soviel zur "Energie des Verstehens". Da muss man auch Fragen stellen dürfen, die man eigentlich "dem Hoster stellen soll". Davon profitieren nämlich dann Viele!

      Liebe Grüße an Alle
      DB-Willi

  3. Tach!

    Wenn ich ohne SESSION Variablen arbeite, kann es da theoretisch passieren das Informationen verloren oder überschrieben/gelöscht werden, wenn zufällig 2 Nutzer gleichzeitig am selben Datensatz arbeiten ?

    Zum Verständnis muss man den Vorgang etwas genauer betrachten.

    Vorgang 1:

    • PHP-Script startet
    • Verbindungsaufbau zum DBMS
    • Daten lesen
    • PHP-Script endet, Verbindungsabbau zum DBMS

    Ob der Verbindungsabbau implizit am Scriptende oder explizit mit Funktionsaufruf passiert, spielt hier keine Rolle.

    Vorgang 2:

    • PHP-Script startet
    • Verbindungsaufbau zum DBMS
    • Datensatz schreiben
    • PHP-Script endet, Verbindungsabbau zum DBMS

    Ohne eine Verbindung hat das DBMS keinerlei Ambitionen zugunsten irgendeines Users irgendwelche anderen Datenzugriffe zu verhindern. Zwischen den beiden Vorgängen können also beliebige Änderungen durch andere Nutzungen vorgenommen werden.

    Während eine Verbindung besteht, könnte man mit Transaktionen und Datensatz- oder Tabellensperrungen andere Zugriffe verhindern. Das hilft dir aber in deinem Szenario nicht. Man verwendet das nur, um komplexe Vorgänge ganz oder gar nicht ohne Störungen durch andere Vorgänge durchzuführen. Selbst wenn man die Verbindung offenhalten kann (persistente Verbindung kennt PHP auch in bestimmten Konstellationen), macht man aber sowas generell nicht, wenn der Anwender viel Zeit braucht, um die Daten zu ändern. Damit blockiert man nur die anderen Zugriffe.

    Damit keine Daten durcheinander überschrieben werden, braucht man eine andere Vorgehensweise. Bevor man schreibt, muss man prüfen, ob die Daten auf demselben Stand sind, wie zum Zeitpunkt des Lesens. Dazu braucht es pro Datensatz einen Versionszähler, oder einen Timestamp, wenn der fein genug auflöst.

    UPDATE felddaten, SET version=version+1 WHERE id=$id AND version=$gelesene_version;

    Das wäre eine Vorgehensweise. Wenn die Anzahl der betroffenen Datensätze gleich 0 ist, war zwischenzeitlich jemand am Versionszähler, und man würde fremde Änderungen überschreiben.

    Wie du nun damit umgehst, wenn solch eine Konkurrenzsitiuation festgestellt wird, ist eine Sache deiner Geschäftslogik und was deine Anwender brauchen.

    Das ganze Thema nennt sich Concurrency Control. Damit wirst du noch mehr und genauere Informationen finden.

    dedlfix.

  4. Hi,

    den konkurrierenden Betrieb kannst Du im Design der Tabellen (Datenmodell) und (mandatorisches UND) der DM-Queries (Data Manipulation) vorsehen.

    Gib den Tabellen (oder einer zentralen, die in allen Queries beteiligt ist) eine Spalte mit einem Conflict Counter mit. Dieser wird vor jeder Veränderung mit ausgelesen und beim Zurückschreiben der Daten incrementiert. Daten dürfen nur zurückgeschrieben werden, wenn der ausgelesene Counter mit dem in der DB übereinstimmt. Sonst ist affected rows == 0, oder besser, ein Trigger löst eine definierte Exception aus, die dann für eine Meldung an den User genutzt werden kann.

    Ersatzweise wird für den Zähler auch oft der Zeitpunkt der letzten Veränderung benutzt. Das ist aber noch relativ unsicher, da ein Zeitstempel nicht direkt abhängig vom Update ist und durch seine grobe Granularität (i.d.R. 1 Sekunde) trotzdem mehrere Threads denselben ausgelesen haben können.

    good success
    DB-Willi

    1. Hallo DB-Willi,

      Ersatzweise wird für den Zähler auch oft der Zeitpunkt der letzten Veränderung benutzt ... grobe Granularität

      Ja. Sorry. Ich benutze hauptsächlich MS SQL Server, und da ist der TIMESTAMP Typ ein Synonym für ROWVERSION - das gibt's in MySQL meines Wissens nicht.

      Was ich auch übersehen habe, ist, dass MYSQL einen Parameter für CURRENT_TIMESTAMP braucht, um die Anzahl der Dezimalstellen festzulegen. CURRENT_TIMESTAMP(6) liefert ihn auf die Mikrosekunde (wenn das OS einen Mikrosekundentimer besitzt). Aber kollisionsfest ist das nicht.

      Ein Zähler ist bestimmt sicherer, der sollte dann aber BIGINT sein, oder die +1 Operation muss mit MOD gegen Überlauf gesichert werden.

      Was ich in meinem eigenen Text noch vergaß: wenn man mehr als eine Table updaten muss, dann MUSS das ganze in einer Transaction laufen, um bei einer Kollision alles zurücksetzen zu können.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hallo Rolf,

        Ein Zähler ist bestimmt sicherer, der sollte dann aber BIGINT sein, oder die +1 Operation muss mit MOD gegen Überlauf gesichert werden.

        Da verwechselst Du jetzt aber ID und Conflict Counter?

        Im einfachsten Fall hat jede Tabelle ihre eigene Spalte für einen Conflict-Counter und somit jede Row einen eigenen Wert dafür. Dass eine Row mehr Updates erfährt, als es maximal Rows gibt, habe ich noch nicht erlebt.

        Was ich in meinem eigenen Text noch vergaß: wenn man mehr als eine Table updaten muss, dann MUSS das ganze in einer Transaction laufen, um bei einer Kollision alles zurücksetzen zu können.

        Das kommt darauf an, ob DM-Statements auf verbundene Tabellen komplett serialisiert (für den Prozess exklusiv gesperrt) werden vom DBMS, oder nur die DM-Statements auf jede Tabelle separat setialisiert (ExLock) werden.

        Transaktionen benötigt man eigentlich nur, wenn mehrere voneinander unabhängige Datenmanipulationen zu einem logischen Vorgang zusammengefasst werden sollen, der vollständig ungestört abgearbeitet werden muss, damit die Daten konsistent bleiben.

        Commit
        DB-Willi

        1. Hallo DB-Willi,

          verwexeln

          Nein, ich denke nicht. Wenn der Conflict Counter ein Bigint ist (64-bit), dann ist man wohl auf der sicheren Seite. Bei einem normalen Int (32-bit) sollte man mit 2 Milliarden auf der sicheren Seite sein, aber 640k RAM genügten ja auch für alle Zeiten. Lieber den MOD reinprogrammieren, und wenn das System dann irrtümlich etwas länger online ist, keine Überraschung erleben. Ich bin da eher defensiv eingestellt.

          Transaktionen ...

          Den "Das kommt darauf an" Abschnitt kapiere ich nicht. "DM Statements auf verbundene Tabellen komplett serialisiert" - was soll das sein? Sprichst Du von foreign keys? Table A enthält Fremdschlüssel aus Table B - warum sollte das DBMS Table A sperren wenn ich in B ein Nichtschlüsselattribut ändere (oder umgekehrt)?

          Transaktionen benötigt man eigentlich nur, wenn mehrere voneinander unabhängige Datenmanipulationen zu einem logischen Vorgang zusammengefasst werden sollen, der vollständig ungestört abgearbeitet werden muss, damit die Daten konsistent bleiben.

          Entweder drückst Du Dich mistverständlich aus, oder es ist wirklich Mist. Unabhängige Datenmanipulationen kann ich auch unabhängig voneinander durchführen, aber dann müssen sie es auch wirklich sein.

          Wenn ich nach einem Edit Updates in mehr als einer Row durchführen muss (vor allem, wenn diese Rows in mehreren Tabellen stehen), dann habe ich ja gerade eine Abhängigkeit zwischen den DM Statements. Sie müssen entweder alle gelingen, oder keins. Und darum ist die Transaktion nötig. Wenn ich 3 Updates mache, dürfen die Daten aus dem 1. Update für andere Anwender nicht sichtbar werden (das I in ACID), bevor der 2. und 3. Update gelungen sind. Und wenn im 2. oder 3. Update auffällt, dass der conflict counter verändert wurde, dann müssen alle Updates dieses Speichervorgangs, die vorher passiert sind, zurückgenommen werden (das C in ACID).

          Wenn ein User den SAVE Button drückt, dann erwartet er eine vollständige Speicherung. Und wenn es dabei zum Fehler kommt, dann würde zumindest ich erwarten, dass es im System keine Änderung gegeben hat (von Log-Einträgen mal abgesehen) - das A in ACID.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Hallo Rolf,

            du schrammst mit deinem Ton nur knapp an den Sümpfen der Beleidigung vorbei :-O

            Wenn Du mehrere Statements benötigst, um irgendwie zueinander in Relation stehende Datensätze zu verändern, dann braucht das DBMS einen Mechanismus dafür, dies zu erkennen. Dafür können Transaktionen dienen.

            Wenn Du jedoch eine DB-Maschine hast, die von sich aus foreign Keys kennt, so könnte diese auch die Kapselung (Exklusivbereitstellung aller betroffenen Rows in allen Tabellen, ATS automatic transaction scoping/scaling) selbstständig vornehmen. Dies würde sie dann selbstverständlich vor Beginn der Veränderungen tun und die erforderlichen Maßnahmen skalieren. Damit wird dann die Notwendigkeit eines Rollbacks sehr unwahrscheinlich. Leider geht die Vorbereitung auf die Performance, lässt sich aber automatisieren und spart die lästigen Rollbacks ein.

            ATS ist keinesfalls in allen DBMS sichergestellt. Wegen fortschreitender Klicki-Bunti-Queries geht das aber irgendwann kaum noch anders.

            MySQL -so mein letzter Wissensstand- kann das aber noch nicht einmal für Subqueries sicherstellen, die doch schließlich alle in demselben Statement wie das Main Query aufgeführt werden können/müssen.

            Und auch in Triggers bzw. darin benutzten stored Routines (User Functions, Procedures, ...) muss das nicht unbedingt automatisch funktionieren.

            Und beschäftige Dich mal etwas mit Vorgangsbearbeitung in der Datenbanktechnik. Dann kräht da bei Dir auch kein Hahn mehr...

            Grüße
            DB-Willi

            1. Hallo DB-Willi,

              du schrammst mit deinem Ton nur knapp an den Sümpfen der Beleidigung vorbei

              Du solltest es vermeiden, Kritik an deinen Aussagen (oder deren Nichtverstehen) mit Schmähung deiner Person (=Beleidigung) gleichzusetzen.

              Es kann gut sein, dass ich mit diesen Techniken nicht vertraut bin. Die mir bekannten DBs tun das nicht, oder zumindest nicht so, wie ich es gern hätte (implizite Transaktionen). Aber immerhin erkenne ich, dass Du Dir per footgun sauber eine kleine Zehe amputiert hast. ATS - steckt da etwa das Wort Transaktion drin?

              Allerdings bin ich so langsam der Meinung, dass Du da eine Wolkenkuckucksheim skizzierst. Welches real existierende DBMS hat so eine Funktionsweise, und wie würde man den Update einer Tabellenhierarchie mit optimistischem Locking da formulieren? Ich hätte da Bedenken, dass das DBMS viel zu viel lockt und damit die Performance bei hohem Updateaufkommen in die Knie geht.

              Wenn's nicht im DBMS ist, sondern ein Framework ist: Gibt's das für PHP?

              Rolf

              --
              sumpsi - posui - obstruxi
  5. allen zusammen ein großes Dankeschön. Das meiste hier geschriebene verstehe ich nicht :-D

    Aber die für mich interessanten Hauptthemen timestamp und counter habe ich wohl verstanden !

    Ich schaue mal, wie ich das eingebaut bekomme.

    Vielen vielen Dank :)

    Grüße

    1. Hallo PHP-Neuling,

      es reicht, wenn Du das Stichwort "optimistisches Sperren" mitnimmst.

      Die Debatte um Transaktionen ist ein technisches Detail. Wenn Du mehr als ein UPDATE Statement absetzen musst, um deine Daten zu speichern, überprüfe das Transaktionsverhalten deiner Datenbank. Es ist wichtig, dass entweder alle Updates durchgeführt werden, oder keiner.

      Die "klassische" Lösung ist, vor die Updates ein BEGIN TRANSACTION zu setzen. Wenn irgendwas schiefgeht, rufst Du den ROLLBACK Befehl auf und alle Änderungen sich rückgängig gemacht. Ist alles gut gegangen, rufst Du COMMIT auf und alle Änderungen sind persistiert.

      Das ist handgemacht, und es mag Datenbanksysteme oder Persistenz-Frameworks[1] geben, die Dir dafür bessere Werkzeuge bereitstellen. Leider hat DB-Willi das nicht konkretisiert, so dass wir nicht wissen, wie das genau gehen soll. Meine bisherige Erkenntnis ist, dass ATS ein Feature von ORM Frameworks oder DB Frontends ist, nicht vom DBMS. Und der riesige Hit ist es auch nicht, wenn Google zum Begriff "Automated Transaction Scoping" gerade mal 131 Treffer liefert. Insofern: vergiss das.

      Rolf

      --
      sumpsi - posui - obstruxi

      1. Libraries, die die Datenbankzugriffe kapseln und die Daten für Dich bereitstellen und wieder speichern. Gehören zu meinen persönlichen Hassobjekten. ↩︎