WernerK: SQL Update bei vielen Anwendern

Hallo,

Ein PHP Script (IIS) macht in einer MS SQL Tabelle einen Udate auf eine Spalte. Hier steht nur eine Zahl drin, die um 1 erhöht wird. Die Zahl wird später für eine Auftragsnummer verwendet.

Besteht die Gefahr, dass bei vielen Anwendern die event. zeitgleich das Script aufrufen doppelte Nummern zurckgeliefert werden? Also z.b. User A macht Update und bekommt eine 3 zurück. Zeitgleich macht auch User B ein Update und bekommt auch eine 3? Oder handelt das der SQL Server das selber so aus, dass die Tabelle gesperrt ist und nichts passieren kann? (Ich kann in diesem speziellen Fall nicht mit Transaktionen arbeiten)

Gruss

Werner

  1. Tach!

    Ein PHP Script (IIS) macht in einer MS SQL Tabelle einen Udate auf eine Spalte. Hier steht nur eine Zahl drin, die um 1 erhöht wird. Die Zahl wird später für eine Auftragsnummer verwendet.

    Meinst du so? UPDATE table SET x=x+1;

    Besteht die Gefahr, dass bei vielen Anwendern die event. zeitgleich das Script aufrufen doppelte Nummern zurckgeliefert werden?

    Das kommt darauf an, wie du die neue Zahl ermittelst. Führst du anschließend ein SELECT aus? Wenn ja, dann kann da beliebig viel Zeit dazwischen liegen, in der andere Prozesse abgewickelt werden. Ohne Locking oder Transaction mit passenden Isolation Level kann es inkonsistente Daten geben.

    Oder handelt das der SQL Server das selber so aus, dass die Tabelle gesperrt ist und nichts passieren kann?

    Wie genau stellst du dir eine solche Sperre vor? Von wann bis wann soll die gehen?

    (Ich kann in diesem speziellen Fall nicht mit Transaktionen arbeiten)

    Soweit ich das lese, kommt es auch bei Transactions darauf an, wie man sie ausführt. Bestimmte Isolation Level erlauben weiterhin das Lesen des alten Wertes, auch wenn eine unabgeschlossene Transaction bereits für sich einen Wert geändert hat. Select - Pause - Update kann durchaus zu einem TOCTTOU-Problem führen.

    Mehr kann ich dir aber nicht antworten, weil ich da keine Erfahrungen habe sondern nur das Wissen aus der öffentlich verfügbaren Dokumentation.

    dedlfix.

  2. Hallo WernerK,

    Ein PHP Script (IIS) macht in einer MS SQL Tabelle einen Udate auf eine Spalte. Hier steht nur eine Zahl drin, die um 1 erhöht wird. Die Zahl wird später für eine Auftragsnummer verwendet.

    Klingt nach einem Fall für Sequences.

    Besteht die Gefahr, dass bei vielen Anwendern die event. zeitgleich das Script aufrufen doppelte Nummern zurckgeliefert werden?

    Wenn du ein UPDATE machst, wird die Tabelle bei MSSQL exklusiv gesperrt. Bist du sicher, dass du nur ein UPDATE machst? Oder ist das eher ein SELECT mit einem folgenden UPDATE?

    Also z.b. User A macht Update und bekommt eine 3 zurück. Zeitgleich macht auch User B ein Update und bekommt auch eine 3? Oder handelt das der SQL Server das selber so aus, dass die Tabelle gesperrt ist und nichts passieren kann?

    Ein UPDATE-Statement löst einen exklusiven Lock aus, es können nicht zwei Prozesse gleichzeitig die gleiche Zeile ändern. Da aber nicht ganz klar ist, was genau du tust, kann ich dir keine erschöpfende Antwort geben. Wenn du etwa erst ein SELECT und danach ein UPDATE machst, dann bist du anfällig für race conditions, ja. Wenn du ein UPDATE mit einem festen Wert machst und nicht nach dem Schema UPDATE foo SET col = col + 1, dann bist du auch anfällig für race coditions. Wenn du ein UPDATE foo SET col = col + 1 OUTPUT inserted.col machst, bist du safe.

    Was genau machst du? 😉

    (Ich kann in diesem speziellen Fall nicht mit Transaktionen arbeiten)

    MSSQL verwendet immer Transaktionen…

    LG,
    CK

    1. Hallo Christian,

      also es ist tatsächlich so, das ich zuerste einen Select mache um eine Nummer zu holen. Ich Prinzip vereinfacht so:

      SELECT nummer FROM TableX
      $auftragno = $row['numer'];
      if($auftragno == ""){
        $auftragno = 1;
        INSERT INTO tableX nummer ($auftragno)
      ..
      )
      }else{
        $auftragno = $auftragno + 1;
        $sql_update = "
                      UPDATE TableX
                      SET nummer = '".$auftragno ."'WHERE CODE = '".$code."'
                  ";
      }
      
      

      Also wenn ich euch richtig verstanden habe, dann besteht hier doch die Gefahr, dass es Duppletten geben kann. Nicht wegen dem Update an sich, sondern wegen dem Zeitpunkt des Selects?

      Was ich gemeint habe das ich keine Transaktion verwenden kann ist das ich kein "sqlsrv" habe wie im Beispiel bei php.net.

      /* Begin the transaction. */
      if ( sqlsrv_begin_transaction( $conn ) === false ) {
           die( print_r( sqlsrv_errors(), true ));
      }
      
      /* Initialize parameter values. */
      $orderId = 1; $qty = 10; $productId = 100;
      
      /* Set up and execute the first query. */
      $sql1 = "INSERT INTO OrdersTable (ID, Quantity, ProductID)
                VALUES (?, ?, ?)";
      $params1 = array( $orderId, $qty, $productId );
      $stmt1 = sqlsrv_query( $conn, $sql1, $params1 );
      
      /* Set up and execute the second query. */
      $sql2 = "UPDATE InventoryTable 
                SET Quantity = (Quantity - ?) 
                WHERE ProductID = ?";
      $params2 = array($qty, $productId);
      $stmt2 = sqlsrv_query( $conn, $sql2, $params2 );
      
      /* If both queries were successful, commit the transaction. */
      /* Otherwise, rollback the transaction. */
      if( $stmt1 && $stmt2 ) {
           sqlsrv_commit( $conn );
           echo "Transaction committed.<br />";
      } else {
           sqlsrv_rollback( $conn );
           echo "Transaction rolled back.<br />";
      }
      

      Dann muss ich vermutlich doch wie ursprünglich angedacht, mit SQL Sequenzen arbeiten.

      viele Grüße

      Werner

      1. Hallo WernerK,

        also es ist tatsächlich so, das ich zuerste einen Select mache um eine Nummer zu holen. Ich Prinzip vereinfacht so:

        SELECT nummer FROM TableX
        $auftragno = $row['numer'];
        if($auftragno == ""){
          $auftragno = 1;
          INSERT INTO tableX nummer ($auftragno)
        ..
        )
        }else{
          $auftragno = $auftragno + 1;
          $sql_update = "
                        UPDATE TableX
                        SET nummer = '".$auftragno ."'WHERE CODE = '".$code."'
                    ";
        }
        
        

        Ja, der Code ist anfällig für race conditions.

        Also wenn ich euch richtig verstanden habe, dann besteht hier doch die Gefahr, dass es Duppletten geben kann.

        Sofern du nicht mit einem Unique Constraint arbeitest (und das solltest du, wenn du einen eindeutigen Datensatz haben möchtest), dann ja.

        Nicht wegen dem Update an sich, sondern wegen dem Zeitpunkt des Selects?

        Jein. Weil zwischen SELECT und UPDATE/INSERT beliebige andere Operationen passieren können. Zwei Prozesse machen gleichzeitig ein SELECT und führen dann die gleiche Operation aus. Damit würde Prozess 1 das Ergebnis von Prozess 2 überschreiben.

        Dann muss ich vermutlich doch wie ursprünglich angedacht, mit SQL Sequenzen arbeiten.

        Klingt nach der besseren Lösung, ja. Und klingt auch zusätzlich noch nach einem unique constraint.

        LG,
        CK

      2. Hallo WernerK,

        Wie erreichst du den MSSQL Server, wenn nicht mit sqlsrv–Funktionen?

        PDO? Kann auch Transaktionen...

        Rolf

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

          das ist eine in sich geschlossene Anwendung die mit eigenen Klassen arbeiten. Ich glaube aber es wird intern auch sqlsrv verwendet.

          Gruss Werner

          1. Hallo WernerK,

            Ok. Dann solltest du vielleicht auch noch Identity–Spalten in Betracht ziehen. Die vergeben sich bei INSERT ihre Werte selbstständig. Nach dem Insert kann man den vergebenen Wert auch abfragen. Allerdings ist der Identity-Zähler dann auch vor mutwilligen Änderungen geschützt (nicht komplett, über spezielle Admin Kommandos geht was)

            Rolf

            --
            sumpsi - posui - clusi
  3. hi

    warum denn so umständlich? Eine Dateilösung ist da deutlich einfacher, LOCK_EX und gut. Und eine 0xFFFFffff wird in UNSEREM Leben nicht erreicht, selbst dannn nicht wenn jede Sekunde eine neue Nummer generiert wird 😉

    MfG

    PS: Seit 1900 zählt der ptbtime2.ptb.de die Sekunden.

    1. OP: „Kann mir jemand erklären, wie man einen Rinderbraten schön saftig bekommt?“

      PL: „Mach Dir doch ein Brot, ist viel einfacher!“

      1. Hallo,

        OP: „Kann mir jemand erklären, wie man einen Rinderbraten schön saftig bekommt?“

        PL: „Mach Dir doch ein Brot, ist viel einfacher!“

        Und wie krieg dann den saftigen Rinderbraten aufs Brot???

        Gruß
        Kalk

        1. Hallo Tabellenkalk,

          OP: „Kann mir jemand erklären, wie man einen Rinderbraten schön saftig bekommt?“

          PL: „Mach Dir doch ein Brot, ist viel einfacher!“

          Und wie krieg dann den saftigen Rinderbraten aufs Brot???

          Warum willst du das tun? Den Braten isst man doch viel besser ohne Brot!!!

          LG,
          CK

          1. Hallo,

            OP: „Kann mir jemand erklären, wie man einen Rinderbraten schön saftig bekommt?“

            PL: „Mach Dir doch ein Brot, ist viel einfacher!“

            Und wie krieg dann den saftigen Rinderbraten aufs Brot???

            Warum willst du das tun? Den Braten isst man doch viel besser ohne Brot!!!

            Weil man dann noch ordentlich Butter zwischen Brot und Braten haben kann!hundertelf

            Gruß
            Kalk

            1. Hallo Tabellenkalk,

              OP: „Kann mir jemand erklären, wie man einen Rinderbraten schön saftig bekommt?“

              PL: „Mach Dir doch ein Brot, ist viel einfacher!“

              Und wie krieg dann den saftigen Rinderbraten aufs Brot???

              Warum willst du das tun? Den Braten isst man doch viel besser ohne Brot!!!

              Weil man dann noch ordentlich Butter zwischen Brot und Braten haben kann!hundertelf

              Butter hat nichts mit einem deutschen Braten zu tun, lass die lieber weg!11

              LG,
              CK

          2. Warum willst du das tun? Den Braten isst man doch viel besser ohne Brot!!!

            Bei meiner Oma hieß das so: „In der Not schmeckt die Wurst auch ohne Brot!“

    2. hi

      warum denn so umständlich? Eine Dateilösung ist da deutlich einfacher, LOCK_EX und gut. Und eine 0xFFFFffff wird in UNSEREM Leben nicht erreicht, selbst dannn nicht wenn jede Sekunde eine neue Nummer generiert wird 😉

      PS: Falls Du OO arbeitetst, es bietet an, eine solche Funktion im Rahmen einer Factory aufzustellen. Dann hast Du einfach nur noch einen einzigen Funktionsaufruf $lfdnr=$this->lfdnr(); wenn eine neue laufende Nummer gebraucht wird und kannst die Funktion an beliebiger Stelle aufrufen.

      MfG

    3. Hi,

      und wenn man z.b. keinen Zugriff aufs Dateisystem hat oder dort nichts speichern darf?

      Gruss Werner

      1. Hi,

        und wenn man z.b. keinen Zugriff aufs Dateisystem hat oder dort nichts speichern darf?

        Dann würde ich mich an Deiner Stelle mal fragen, wie man PHP Dateien speichern kann wenn man keinen Zugriff aufs Dateisystem hat.

        MfG

        1. Dann würde ich mich an Deiner Stelle mal fragen, wie man PHP Dateien speichern kann wenn man keinen Zugriff aufs Dateisystem hat.

          Dedizierte SQL-Server brauchen kein PHP

        2. Hallo pl,

          und wenn man z.b. keinen Zugriff aufs Dateisystem hat oder dort nichts speichern darf?

          Dann würde ich mich an Deiner Stelle mal fragen, wie man PHP Dateien speichern kann wenn man keinen Zugriff aufs Dateisystem hat.

          🧐 offensichtlich gar nicht.

          LG,
          CK

        3. Tach!

          und wenn man z.b. keinen Zugriff aufs Dateisystem hat oder dort nichts speichern darf?

          Dann würde ich mich an Deiner Stelle mal fragen, wie man PHP Dateien speichern kann wenn man keinen Zugriff aufs Dateisystem hat.

          "Man" ist nicht unbedingt gleich "man". Der Nutzer, unter dem PHP ausgeführt wird, muss nicht derselbe sein, der die PHP-Dateien ins System bringt.

          dedlfix.

          1. Tach!

            und wenn man z.b. keinen Zugriff aufs Dateisystem hat oder dort nichts speichern darf?

            Dann würde ich mich an Deiner Stelle mal fragen, wie man PHP Dateien speichern kann wenn man keinen Zugriff aufs Dateisystem hat.

            "Man" ist nicht unbedingt gleich "man". Der Nutzer, unter dem PHP ausgeführt wird, muss nicht derselbe sein, der die PHP-Dateien ins System bringt.

            Also, wenn ich nach 20km autofahren feststelle daß der Zündschlüssel nicht steckt, würd' mich schonmal interessieren wer mich die ganze Zeit geschoben hat 😉

            MfG

            1. Hallo,

              "Man" ist nicht unbedingt gleich "man". Der Nutzer, unter dem PHP ausgeführt wird, muss nicht derselbe sein, der die PHP-Dateien ins System bringt.

              Also, wenn ich nach 20km autofahren feststelle daß der Zündschlüssel nicht steckt, würd' mich schonmal interessieren wer mich die ganze Zeit geschoben hat 😉

              Ich erinnere mich noch sehr gut an Zeiten, in denen Apache / PHP unter einem eigenen Benutzer lief und nicht zum Benutzer des jeweiligen Hosting-Kunden geändert wurde.

              Gruß
              Patrick