sql >> Database >  >> RDS >> Mysql

Loop totdat de toegangscode uniek is

Strikt genomen garandeert uw test voor uniciteit geen uniciteit bij gelijktijdige belasting. Het probleem is dat u op uniciteit controleert voorafgaand aan (en apart van) de plaats waar u een rij invoegt om uw nieuw gegenereerde toegangscode te "claimen". Een ander proces zou hetzelfde kunnen doen, op hetzelfde moment. Zo gaat dat...

Twee processen genereren exact dezelfde toegangscode. Ze beginnen elk met het controleren op uniciteit. Aangezien geen van beide processen (nog) een rij aan de tabel heeft toegevoegd, zullen beide processen geen overeenkomende toegangscode in de database vinden, en dus gaan beide processen ervan uit dat de code uniek is. Nu de processen elk hun werk voortzetten, zullen ze uiteindelijk beide voeg een rij toe aan de files tabel met behulp van de gegenereerde code -- en zo krijg je een duplicaat.

Om dit te omzeilen, moet u de controle uitvoeren en het invoegen in een enkele "atomaire" bewerking. Hieronder volgt een uitleg van deze aanpak:

Als u wilt dat de toegangscode uniek is, moet u de kolom in uw database definiëren als UNIQUE . Dit zorgt voor uniekheid (zelfs als uw php-code dat niet doet) door te weigeren een rij in te voegen die een dubbele toegangscode zou veroorzaken.

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

Gebruik nu mysql's SHA1() en NOW() om uw toegangscode te genereren als onderdeel van de insert-statement. Combineer dit met INSERT IGNORE ... (docs ), en herhaal totdat een rij succesvol is ingevoegd:

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.
}

Opmerking: Het bovenstaande voorbeeld is bewerkt om rekening te houden met de uitstekende observaties die door @martinstoeckli in het opmerkingengedeelte zijn geplaatst. De volgende wijzigingen zijn aangebracht:

  • mysql_num_rows() (docs ) naar mysql_affected_rows() (docs ) -- num_rows is niet van toepassing op invoegingen. Ook het argument voor mysql_affected_rows() . verwijderd , aangezien deze functie werkt op het verbindingsniveau, niet op het resultaatniveau (en in ieder geval is het resultaat van een invoeging een boolean, geen resourcenummer).
  • foutcontrole toegevoegd aan de lusvoorwaarde en een test toegevoegd voor fout/succes nadat de lus is afgesloten. De foutafhandeling is belangrijk, omdat zonder deze databasefouten (zoals verbroken verbindingen of problemen met machtigingen) ervoor zullen zorgen dat de lus voor altijd blijft draaien. De hierboven getoonde aanpak (met behulp van IGNORE , en mysql_affected_rows() , en het testen van $res afzonderlijk voor fouten) stelt ons in staat om deze "echte databasefouten" te onderscheiden van de unieke beperkingsschending (wat een volledig geldige niet-foutconditie is in dit gedeelte van de logica).

Als u de toegangscode nodig heeft nadat deze is gegenereerd, selecteert u de record opnieuw:

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

Bewerken :bovenstaand voorbeeld gewijzigd om de mysql-functie LAST_INSERT_ID() te gebruiken , in plaats van de functie van PHP. Dit is een efficiëntere manier om hetzelfde te bereiken, en de resulterende code is schoner, duidelijker en minder rommelig.



  1. Genereer de volgende id volgens de max id in de database met behulp van Java

  2. SQL-query:verwijder alle records uit de tabel behalve de laatste N?

  3. Omgaan met trage zoekopdrachten met PostgreSQL

  4. Hoe controleer ik of MySQL null/leeg retourneert?