sql >> Databasteknik >  >> RDS >> Mysql

Loop tills lösenordet är unikt

Strängt taget kommer ditt test för unikhet inte att garantera unikhet under en samtidig belastning. Problemet är att du kontrollerar unikhet före (och separat från) platsen där du infogar en rad för att "häva" ditt nygenererade lösenord. En annan process kan vara att göra samma sak, samtidigt. Så här går det till...

Två processer genererar exakt samma lösenord. De börjar var och en med att kontrollera om de är unika. Eftersom ingen av processerna (ännu) har infogat en rad i tabellen, kommer båda processerna inte att hitta någon matchande lösenord i databasen, och så båda processerna kommer att anta att koden är unik. Nu när processerna fortsätter sitt arbete kommer de så småningom att båda infoga en rad i files tabell med den genererade koden -- och därmed får du en dubblett.

För att komma runt detta måste du utföra kontrollen och göra insättningen i en enda "atomär" operation. Följande är en förklaring av detta tillvägagångssätt:

Om du vill att lösenordet ska vara unikt bör du definiera kolumnen i din databas som UNIQUE . Detta säkerställer unikhet (även om din php-kod inte gör det) genom att vägra infoga en rad som skulle orsaka en dubblett av lösenordet.

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

Använd nu mysqls SHA1() och NOW() för att generera ditt lösenord som en del av infoga uttalandet. Kombinera detta med INSERT IGNORE ... (dokument ), och loop tills en rad har infogats:

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

Obs! Ovanstående exempel redigerades för att redogöra för de utmärkta observationer som publicerades av @martinstoeckli i kommentarsektionen. Följande ändringar gjordes:

  • ändrade mysql_num_rows() (dokument ) till mysql_affected_rows() (dokument ) -- num_rows gäller inte för inlägg. Tog även bort argumentet till mysql_affected_rows() , eftersom den här funktionen fungerar på anslutningsnivån, inte på resultatnivån (och i alla fall är resultatet av en infogning booleskt, inte ett resursnummer).
  • har lagt till felkontroll i looptillståndet och lagt till ett test för fel/framgång efter looputgångar. Felhanteringen är viktig, eftersom databasfel (som förlorade anslutningar eller behörighetsproblem) utan den kommer att få slingan att snurra för alltid. Tillvägagångssättet som visas ovan (med IGNORE , och mysql_affected_rows() , och testa $res separat för fel) gör det möjligt för oss att skilja dessa "riktiga databasfel" från den unika begränsningsöverträdelsen (vilket är ett fullständigt giltigt icke-felvillkor i detta avsnitt av logiken).

Om du behöver få lösenkoden efter att den har genererats, välj bara posten igen:

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

Redigera :ändrade exemplet ovan för att använda mysql-funktionen LAST_INSERT_ID() snarare än PHPs funktion. Detta är ett mer effektivt sätt att åstadkomma samma sak, och den resulterande koden är renare, tydligare och mindre rörig.



  1. Fixar ORA-65096-fel när man skapar automatiserade tester i Django med Oracle

  2. Hur sparar man genererade PDF-filer till MySQL-databas med Java?

  3. Hur uppdaterar man en stor tabell med miljontals rader i SQL Server?

  4. SQLAlchemy skiftlägesokänslig IN-baserad sökfråga?