Alex: MySQL - LOCK TABLES - Reservierungssystem

Hallo Leute,

ich habe mal wieder eine Frage.

Zuerstmal mein Thema an sich, um zu sehen, ob ich überhaupt das richtige Problem anspreche, oder ob es vielleicht einen anderen Ausweg gibt:

Ich baue ein Reservierungssystem. Bei einer Reservierung muss man ein paar Daten angeben. Dann wir man weitergeleitet und muss bezahlen. Vor der Weiterleitung (an PayPal) speichert das System die Reservierung mit dem Status "pending" (DB-Feld "pending" Wert 1).

Wenn man von PayPal wiederkommt wird erstmal gecheckt, ob die Zahlung geklappt hat. Wenn ja sollen die PayPal-Infos in die DB und der Status "pending" soll auf 0 gesetzt werden.

Soweit alles kein Problem. Aber: Meistens ist die Zahl der Reservierungen begrenzt. Also kann ich nicht einfach pending=0 setzen und fertig sondern ich muss checken, ob ich noch im vorgegebenen Rahmen bin.

Das mache ich im Moment so, dass ich erstmal für den Termin abfrage, wie viele Reservierungen schon mit dem Status "pending=0" vorhanden sind.

Dann schaue ich, ob die alten Reservierungen + die neuen kleiner oder gleich der Höchstzahl sind.

Wenn JA: Dann wird pending auf 0 gesetzt und die Reservierung ist komplett.

Jetzt zu meiner Frage:
Das ist glaube ich schon alles so richtig und sollte klappen. Das Problem ist nur, dass ja ein anderer User, beim dem das irgendwie schneller geht, in der Zwischenzeit auch seine Reservierung durchzieht und es dann eine Überbuchung gibt, oder?

Ich glaube, das kann man mit LOCK TABLES lösen, habe mir dazu auch schon ein bisschen durchgelesen aber verstehe es noch nicht so ganz.

Ich habe erkannt, dass man mit LOCK TABLES sowohl bei WRITE als auch bei READ andere User von allen Aktionen aussperrt. Wenn ich also während die Tabelle gesperrt ist in einem anderen Browserfenster z.B. den Termin aufrufe, in dem auch auf die Reservierungstabelle zugegriffen wird, um die Anzahl auszulesen, wartet das ab, bis die Tabelle entsperrt ist.

Das ist schon irgendwie nicht so gut, weil eben die ganzen Termin-Seiten und nicht nur die Reservierung dran hängen.

In meinem Testbeispiel ist es besonders krass, weil ich zum testen sleep für 30 Sekunden genutzt habe. Wie wirkt sich das in echt aus, kann das spürbar werden.

Oder wie sollte man sowas generell machen?

Gruß
Alex

  1. Hi,

    Das Problem ist nur, dass ja ein anderer User, beim dem das irgendwie schneller geht, in der Zwischenzeit auch seine Reservierung durchzieht und es dann eine Überbuchung gibt, oder?

    Erlaube nicht mehr Reservierungen, als es noch freie (nicht gebuchte und noch nicht reservierte/vorgemerkte) Plätze gibt.

    Reservierungen, die nicht innerhalb eines gewissen Zeitraumes bestätigt worden sind, sind dann wieder frei zu geben.
    Erfolgt ggf. danach die Zahlung, ist diese wieder zurück zu buchen.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    1. Hi,

      Erlaube nicht mehr Reservierungen, als es noch freie (nicht gebuchte und noch nicht reservierte/vorgemerkte) Plätze gibt.

      Tue ich nicht. Im 1. Schritt, wo man die Daten eingibt, können nur genau so viele Plätze eingetragen werden, wie noch zur Verfügung stehen.

      Reservierungen, die nicht innerhalb eines gewissen Zeitraumes bestätigt worden sind, sind dann wieder frei zu geben.
      Erfolgt ggf. danach die Zahlung, ist diese wieder zurück zu buchen.

      Das denke ich, muss ich nicht extra einstellen. Über PayPal ist die Zeit eh stark begrenzt, die die Session hält. Auch über meine eigene Session ist man da eingeschränkt. Wenn die Session weg ist, kann man auch nicht mehr von pending=1 auf 0 kommen.

      Das Problem ist eher das, dass jemand ganz legitim alle Plätze reservieren will. Er wird dann zu PayPal geleitet und muss zahlen. In der Zwischenzeit (die Plätze sind ja noch frei vor Abschluss der Zahlung) kommt ein anderer und will ebenfalls plätze erservieren.

      Der andere ist bei Paypal schneller und holt zum 1. auf. Da habe ich dann Angst, dass die Abfragen sich nach PayPal überschneiden und es zu einer Überbuchung kommt.

      Vielleicht meinst du es so, dass ich beim Ausfüllen des 1. Schrittes schon pending=1 als Reservierung werte und dementsprechen dann den Zweiten gar nicht mehr bis PayPal kommen lassen würde.

      Ich glaube aber, dass das nicht so toll ist, weil ja wohl grundsätzlich der 1. auch als 1. fertig sein wird. Wenn er es nicht ist, hat er es wahrscheinlich sein lassen. Für den 2. wäre es dann schlecht, weil ihm gesagt wird es ist voll und er nicht mehr reservieren kann.

      Aber selbst wenn man das schon so im 1. Schritt macht, könnte es doch zu irgendwelchen Überschneidungen kommen, oder nicht?

      Gruß
      Alex

      1. Hi,

        Das Problem ist eher das, dass jemand ganz legitim alle Plätze reservieren will.

        Wenn er diese Möglichkeit nicht haben soll, beschränke die Anzahl reservierbarer Posten.

        Ich glaube aber, dass das nicht so toll ist, weil ja wohl grundsätzlich der 1. auch als 1. fertig sein wird. Wenn er es nicht ist, hat er es wahrscheinlich sein lassen. Für den 2. wäre es dann schlecht, weil ihm gesagt wird es ist voll und er nicht mehr reservieren kann.

        Dann sag ihm, er solle es später noch mal versuchen.

        Oder erstelle ein mehrstufiges Reservierungssystem.
        Wenn 1 die letzten freien Posten alle reserviert, und nach ihm 2 kommt und auch noch X Stück reservieren will - dann teile 2 mit, dass es momentan eher schlecht aussieht, aber er sich informieren lassen kann, wenn er trotzdem noch zum Zug kommen könnte (nämlich dann, wenn 1 die Frist zur Bestätigung seiner Reservierung verstreichen lässt, und damit diese Reservierungen wieder im „zu haben“-Pool landen).
        Also quasi eine „Warteliste“, auf der Interessenten sich eintragen können.

        MfG ChrisB

        --
        RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
        1. Hi,

          Wenn er diese Möglichkeit nicht haben soll, beschränke die Anzahl reservierbarer Posten.

          Die Möglichkeit soll er doch haben. "Ganz legitim"

          Es geht einfach darum, dass 2 Leute praktisch gleichzeitig den/die letzten Plätze buchen. Das soll einfach nicht dazu führen können, dass eine Überbuchung passiert.

          Dann sag ihm, er solle es später noch mal versuchen.

          Hm, ich denke das ist nicht schön. Dann hat er vielleicht keine Lust mehr.

          Beispiel:
          A will Buchen, trödelt ein bisschen herum, macht noch während des Buchungsprozesses ein Telefonat oder was auch immer. Dabei geht viel Zeit verloren. Irgendwann schließt er die Reservierung dann doch ab.

          Jetzt stellt sich heraus er war der einzige, der buchen wollte. Es sind also noch alle Plätze frei. Hier ist es also völlig OK, wenn es ein lange Zeit gedauert hat und er seine Plätze trotzdem bekommt, ohne alles neu eingeben zu müssen.

          Wenn aber in der Zwischenzeit B kommt und auch buchen will schaut das anders aus. Da würde ich sagen, der der eine normale Geschwindikeit beim Buchen hat sollte bevorzugt werden, weil er sonst 1. vielleicht nicht mehr wiederkommt und der 2. es sich wohl in der Regel eh schon anders überlegt hat, weil man ja solche Transaktionen i.d.R. zügig durchführt.

          Aber ganz egal, wie ich das mache - mit oder ohne "vorreservierung". Irgendwo kann doch immer mal so eine Überbuchung passieren, wenn 2 praktisch gleichzeitig ankommen oder?

          Gruß
          Alex

          1. Moin!

            Beispiel:
            A will Buchen, trödelt ein bisschen herum, macht noch während des Buchungsprozesses ein Telefonat oder was auch immer. Dabei geht viel Zeit verloren. Irgendwann schließt er die Reservierung dann doch ab.

            Jetzt stellt sich heraus er war der einzige, der buchen wollte. Es sind also noch alle Plätze frei. Hier ist es also völlig OK, wenn es ein lange Zeit gedauert hat und er seine Plätze trotzdem bekommt, ohne alles neu eingeben zu müssen.

            Wenn aber in der Zwischenzeit B kommt und auch buchen will schaut das anders aus.

            B kann erstmal nicht buchen, weil A den letzten Platz reserviert hat, und deshalb alle Plätze weg sind.

            Wenn A für den Abschluss des Buchungsvorgangs länger als eine maximale Zeit braucht, wird der Vorgang abgebrochen und der reservierte Platz ist wieder frei. A wird über das Scheitern der Buchung inkl. des Grundes informiert, seine bei Paypal autorisierte Zahlung wird von dir nicht ausgeführt.

            Mit Ablauf der Reservierungszeit von A kann ein B kommen und den jetzt wieder freien Platz (schneller) buchen.

            Wenn A am Ende die Buchung, zwar zu langsam, aber immerhin doch noch abschließt, und dein System noch Plätze vorrätig hat, dann kriegt A instant einen Platz aus dem Pool, denn es sind alle Parameter für eine sofortige Transaktion erfüllt: Das Vertragsobjekt ist bekannt, die Zahlung dafür autorisiert und kann erfolgen.

            Da würde ich sagen, der der eine normale Geschwindikeit beim Buchen hat sollte bevorzugt werden, weil er sonst 1. vielleicht nicht mehr wiederkommt und der 2. es sich wohl in der Regel eh schon anders überlegt hat, weil man ja solche Transaktionen i.d.R. zügig durchführt.

            Die Frage ist, wie du "first-come-first-served" definierst. Derjenige, der zuerst den ersten "Ich will"-Button klickt, oder der zuerst den letzten "Bezahlen"-Button.

            Aber ganz egal, wie ich das mache - mit oder ohne "vorreservierung". Irgendwo kann doch immer mal so eine Überbuchung passieren, wenn 2 praktisch gleichzeitig ankommen oder?

            Ja, selbst Amazon kriegt das nicht sauber hin, wenn bei Werbeaktionen begrenzte Kontingente angeboten werden. Da werden den Kunden dann einfach die Produkte wieder aus dem Warenkorb rausoperiert, oder "1-Click-Buy" funktioniert nicht mit dem gewünschten Erfolg.

            - Sven Rautenberg

            1. hi Sven,

              [..] Da werden den Kunden dann einfach die Produkte wieder aus dem Warenkorb rausoperiert,

              *LOL*

              Danke, hab herzlich gelacht ;)

              Aber Moment mal, hier im Forum kann sowas ähnliches auch passieren, wenn jemand auf ne Nachricht eine Antwort schreibt und beim Abschicken merken muss, dass es die ursprüngliche Nachricht gar nicht mehr gibt ;)

              Hotti

              PS: Hab noch ein Skalpell zum Schärfen auf meinem Schreibtisch liegen. Das dient jedoch nur als Brieföffner.

  2. Hi!

    Das ist glaube ich schon alles so richtig und sollte klappen. Das Problem ist nur, dass ja ein anderer User, beim dem das irgendwie schneller geht, in der Zwischenzeit auch seine Reservierung durchzieht und es dann eine Überbuchung gibt, oder?

    Bei Überbuchung sollte vor dem Bezahlvorgang abgebrochen werden, sonst bekommst du unter Umständen Scherereien, die sich nicht lohnen. Und ich denke, in die Richtung wird auch die Lösung gehen müssen.

    Ich habe erkannt, dass man mit LOCK TABLES sowohl bei WRITE als auch bei READ andere User von allen Aktionen aussperrt. Wenn ich also während die Tabelle gesperrt ist in einem anderen Browserfenster z.B. den Termin aufrufe, in dem auch auf die Reservierungstabelle zugegriffen wird, um die Anzahl auszulesen, wartet das ab, bis die Tabelle entsperrt ist.

    Ja, aber der Lock ist nur so lange aktiv, wie auch die Session andauert. Wenn die Session beispielsweise an einem Script-Ende automatisch geschlossen wird, ist auch der Lock weg.

    Das ist schon irgendwie nicht so gut, weil eben die ganzen Termin-Seiten und nicht nur die Reservierung dran hängen.

    Wenn, dann müsstest du nur eine Tabelle sperren. Was davon alles betroffen ist, hängt von deinem Tabellendesign ab.

    In meinem Testbeispiel ist es besonders krass, weil ich zum testen sleep für 30 Sekunden genutzt habe. Wie wirkt sich das in echt aus, kann das spürbar werden.

    Das heißt also, dass der Bezahlvorgang innerhalb eines Scripts abgearbeitet wird, denn ansonsten wäre der Sleep-Test nicht praxisrelevant.

    Ich würde das ohne Lock machen. Wärend der eine Bezahlvorgang läuft, sind die Plätze (oder was auch immer) aus der Sicht weiterer Interessenten nicht verfügbar. Ich weiß auch nicht, wie oft es geschätzt vorkommt, dass mehrere gleichzeitig an der Limit-Grenze buchen wollen, und was es für einen Eindruck macht, wenn jemand buchen wollte, es nicht mehr geht, eine Weile später aber doch wieder. Andererseits, dass Buchungen storniert werden und dann wieder zur Verfügung stehen, ist so ungewöhnlich auch wieder nicht.

    Lo!

  3. hi,

    Ich habe erkannt, dass man mit LOCK TABLES sowohl bei WRITE als auch bei READ andere User von allen Aktionen aussperrt.

    Allgemein sehe ich es so:

    Einen exclusiv Lock (Lesen UND Schreiben zusammen atomar) brauchst Du, wenn sichergestellt werden muss, dass jeder Prozess den Datenbestand der letzten Änderung kennen muss. Das ist z.B. bei Kontenbewegungen so.

    Wenn ein Prozess den letzten Datenbestand nicht zu kennen braucht (z.B. um Daten einfach irgendwo anzuhängen) reicht es, nur den Schreibvorgang zu locken.

    Hotti

    --
    Brust raus! Sagt meine Frau und schaut sich die Kataloge mit den Büstenhaltern an.
  4. hi again,

    Oder wie sollte man sowas generell machen?

    Noch ein paar "Nachtgedanken"...

    Der Vorgang über das Bezahlsystem muss transaktionssicher sein, er ist, auf Deine Tabelle bezogen, vergleichbar mit einer Kontenbewegung. Transaktionssicher heißt, "Lesen UND Schreiben" ist unteilbar, atomar. Dieser Prozess dauert jedoch länger als gewünscht, unschön wäre es, wenn im Zeitraum eines (oder mehrerer) Bezahlvorganges keiner mehr Lesen kann. Und eine Überbuchung sollte nicht stattfinden. Ein Ex_Lock auf die Tabelle wäre technisch ok, praktisch jedoch unbrauchbar, weil das Lesen blockiert wird. Du brauchst einen "Lese-Puffer" für Deine Besucher.

    Ich würde es so machen, dass ich den Lesepuffer in eine normale Tabelle(n) lege, die Transaktionen jedoch eine transaktionssichere Tabelle(n). Jedesmal, wenn eine Transaktion abgeschlossen ist (commit), werden per Trigger die Daten aus der Transaktion mit dem Puffer abgeglichen.

    Natürlich hat das auch den Nachteil, dass sich einer über einen freien Platz freut und beim Bezahlen merken muss, dass ein Anderer schneller war. Aber wie das immer so ist im Leben: Wer zuerst kommt, mahlt zuerst *G.

    Hotti

    1. Hi,

      Der Vorgang über das Bezahlsystem muss transaktionssicher sein, er ist, auf Deine Tabelle bezogen, vergleichbar mit einer Kontenbewegung. Transaktionssicher heißt, "Lesen UND Schreiben" ist unteilbar, atomar. Dieser Prozess dauert jedoch länger als gewünscht, unschön wäre es, wenn im Zeitraum eines (oder mehrerer) Bezahlvorganges keiner mehr Lesen kann. Und eine Überbuchung sollte nicht stattfinden. Ein Ex_Lock auf die Tabelle wäre technisch ok, praktisch jedoch unbrauchbar, weil das Lesen blockiert wird. Du brauchst einen "Lese-Puffer" für Deine Besucher.

      Ich würde es so machen, dass ich den Lesepuffer in eine normale Tabelle(n) lege, die Transaktionen jedoch eine transaktionssichere Tabelle(n). Jedesmal, wenn eine Transaktion abgeschlossen ist (commit), werden per Trigger die Daten aus der Transaktion mit dem Puffer abgeglichen.

      Das ist auch interessant. Danke.

      Aber für so viel Aufwand habe ich gerade keine Zeit - werde ich mir aber mal merken. (Oder vielleicht auch erst mal abwarten und sehen, ob es überhaupt zu Problemen kommt.)

      Gruß
      Alex

    2. Hi!

      Und eine Überbuchung sollte nicht stattfinden. Ein Ex_Lock auf die Tabelle wäre technisch ok, praktisch jedoch unbrauchbar, weil das Lesen blockiert wird. Du brauchst einen "Lese-Puffer" für Deine Besucher.

      Damit kommt er meiner Meinung nach nicht weiter. Wenn er eine Transaktion startet, in der jemand x Mal $ding reserviert, dann sieht jemand, der die Tabelle liest, diese Reservierung nicht, weil alles was innerhalb der Transaktion läuft, öffentlich nicht sichtbar ist. Das heißt also, dass der nächste Besucher die gleich freie $ding-Anzahl sieht wie der, der grad dabei ist x Stück davon zu erwerben.

      Ich würde es so machen, dass ich den Lesepuffer in eine normale Tabelle(n) lege, die Transaktionen jedoch eine transaktionssichere Tabelle(n). Jedesmal, wenn eine Transaktion abgeschlossen ist (commit), werden per Trigger die Daten aus der Transaktion mit dem Puffer abgeglichen.

      Bleibt das Problem, das schwebende Erwerbungen sich nicht auf den noch verfügbaren Bestand auswirken und somit Überbuchungsversuche möglich bleiben. Eine schwebende Buchung muss bereits für alle anderen Besucher die Anzahl an verfügbaren $ding reduzieren - zumindest temporär, falls der Bezahlvorgang nicht erfolgreich verläuft. Da hilft ein Lock, doch alle anderen Besucher werden mit Wartezeiten genervt. Für eine Transaktion sehe ich nicht, wie sie in der Hinsicht helfen kann.

      Natürlich hat das auch den Nachteil, dass sich einer über einen freien Platz freut und beim Bezahlen merken muss, dass ein Anderer schneller war. Aber wie das immer so ist im Leben: Wer zuerst kommt, mahlt zuerst *G.

      Genau da soll ja aber nicht passieren.

      Lo!

      1. Hi,

        Natürlich hat das auch den Nachteil, dass sich einer über einen freien Platz freut und beim Bezahlen merken muss, dass ein Anderer schneller war. Aber wie das immer so ist im Leben: Wer zuerst kommt, mahlt zuerst *G.

        Genau da soll ja aber nicht passieren.

        Doch, genau so soll es aus den von mir beschriebenen Gründen sein.

        Gruß
        Alex

        1. hi Alex,

          Natürlich hat das auch den Nachteil, dass sich einer über einen freien Platz freut und beim Bezahlen merken muss, dass ein Anderer schneller war. Aber wie das immer so ist im Leben: Wer zuerst kommt, mahlt zuerst *G.

          Genau da soll ja aber nicht passieren.

          Doch, genau so soll es aus den von mir beschriebenen Gründen sein.

          Überbuchungen würde ich auf gar keinen Fall zulassen. Wie ich gestern schrieb, eine Trennung zwischen Transaktionen und Besuchersicht ist der richtige Weg, denke ich, so wird die Sicht nicht blockiert. Die Trigger über der Transaktionstabelle müssen ja nur in der Sicht den Status (frei, pending, verbucht) einer Ressource ändern:

          Begin => pending
            Commit => verbucht
            Rollback => frei

          Wobei es mit Sicherheit Besucher gibt, die beim Status 'pending' alle drei Sekunden F5 drücken in der Hoffnung, dass der Zahlvorgang des Kontrahenten schief geht und die Ressource wieder frei wird ;)

          Hotti

        2. Hi!

          Natürlich hat das auch den Nachteil, dass sich einer über einen freien Platz freut und beim Bezahlen merken muss, dass ein Anderer schneller war. Aber wie das immer so ist im Leben: Wer zuerst kommt, mahlt zuerst *G.
          Genau da soll ja aber nicht passieren.
          Doch, genau so soll es aus den von mir beschriebenen Gründen sein.

          Nun, dann solltest du aufpassen, dass das Ganze nicht als Lockvogelangebot betrachtet wird.

          Lo!