Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Schleife, bis der Passcode eindeutig ist

Genau genommen garantiert Ihr Test auf Eindeutigkeit keine Eindeutigkeit unter gleichzeitiger Last. Das Problem ist, dass Sie vor (und getrennt von) der Stelle, an der Sie eine Zeile einfügen, um Ihren neu generierten Passcode zu "beanspruchen", auf Eindeutigkeit prüfen. Ein anderer Prozess könnte zur gleichen Zeit dasselbe tun. So geht's...

Zwei Prozesse generieren genau denselben Passcode. Sie beginnen jeweils mit der Prüfung auf Eindeutigkeit. Da keiner der Prozesse (noch) eine Zeile in die Tabelle eingefügt hat, finden beide Prozesse keinen passenden Passcode in der Datenbank, und beide Prozesse gehen davon aus, dass der Code eindeutig ist. Jetzt, da die Prozesse ihre Arbeit fortsetzen, werden sie schließlich beides fügen Sie eine Zeile in die files ein Tabelle mit dem generierten Code -- und Sie erhalten ein Duplikat.

Um dies zu umgehen, müssen Sie die Überprüfung und die Einfügung in einer einzigen "atomaren" Operation durchführen. Im Folgenden finden Sie eine Erläuterung dieses Ansatzes:

Wenn Sie möchten, dass der Passcode eindeutig ist, sollten Sie die Spalte in Ihrer Datenbank als UNIQUE definieren . Dies stellt die Eindeutigkeit sicher (selbst wenn Ihr PHP-Code dies nicht tut), indem es sich weigert, eine Zeile einzufügen, die zu einem doppelten Passcode führen würde.

CREATE TABLE files (
  id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
  filename varchar(255) NOT NULL,
  passcode varchar(64) NOT NULL UNIQUE,
)

Verwenden Sie jetzt mysql's SHA1() und NOW() um Ihren Passcode als Teil von zu generieren die Insert-Anweisung. Kombinieren Sie dies mit INSERT IGNORE ... (docs ) und Schleife, bis eine Zeile erfolgreich eingefügt wurde:

do {
    $query = "INSERT IGNORE INTO files 
       (filename, passcode) values ('whatever', SHA1(NOW()))";
    $res = mysql_query($query);
} while( $res && (0 == mysql_affected_rows()) )

if( !$res ) {
   // an error occurred (eg. lost connection, insufficient permissions on table, etc)
   // no passcode was generated.  handle the error, and either abort or retry.
} else {
   // success, unique code was generated and inserted into db.
   // you can now do a select to retrieve the generated code (described below)
   // or you can proceed with the rest of your program logic.
}

Hinweis: Das obige Beispiel wurde bearbeitet, um die hervorragenden Beobachtungen von @martinstoeckli im Kommentarbereich zu berücksichtigen. Die folgenden Änderungen wurden vorgenommen:

  • mysql_num_rows() geändert (docs ) zu mysql_affected_rows() (docs ) -- num_rows gilt nicht für Einfügungen. Außerdem wurde das Argument für mysql_affected_rows() entfernt , da diese Funktion auf der Verbindungsebene arbeitet, nicht auf der Ergebnisebene (und in jedem Fall ist das Ergebnis einer Einfügung ein boolescher Wert, keine Ressourcennummer).
  • Fehlerprüfung in der Schleifenbedingung hinzugefügt und Test auf Fehler/Erfolg nach Schleifenende hinzugefügt. Die Fehlerbehandlung ist wichtig, da ohne sie Datenbankfehler (wie verlorene Verbindungen oder Berechtigungsprobleme) dazu führen, dass sich die Schleife für immer dreht. Der oben gezeigte Ansatz (mit IGNORE und mysql_affected_rows() , und testen Sie $res separat für Fehler) ermöglicht es uns, diese "echten Datenbankfehler" von der eindeutigen Einschränkungsverletzung zu unterscheiden (die in diesem Abschnitt der Logik eine vollständig gültige Nicht-Fehler-Bedingung ist).

Wenn Sie den Passcode benötigen, nachdem er generiert wurde, wählen Sie einfach den Datensatz erneut aus:

$res = mysql_query("SELECT * FROM files WHERE id=LAST_INSERT_ID()");
$row = mysql_fetch_assoc($res);
$passcode = $row['passcode'];

Bearbeiten :obiges Beispiel geändert, um die mysql-Funktion LAST_INSERT_ID() zu verwenden , und nicht die Funktion von PHP. Dies ist ein effizienterer Weg, um dasselbe zu erreichen, und der resultierende Code ist sauberer, klarer und weniger überladen.