Strengt taget vil din test for unikhed ikke garantere unikhed under en samtidig belastning. Problemet er, at du tjekker for unikhed før (og separat fra) det sted, hvor du indsætter en række for at "kræve" din nyligt genererede adgangskode. En anden proces kunne være at gøre det samme på samme tid. Sådan går det...
To processer genererer nøjagtig den samme adgangskode. De begynder hver især med at tjekke, om de er unikke. Da ingen af processerne (endnu) har indsat en række i tabellen, vil begge processer ikke finde nogen matchende adgangskode i databasen, og derfor vil begge processer antage, at koden er unik. Nu hvor processerne fortsætter deres arbejde, vil de til sidst begge indsæt en række til files
tabel ved hjælp af den genererede kode -- og dermed får du en dublet.
For at komme uden om dette, skal du udføre kontrollen og lave indsættelsen i en enkelt "atomisk" operation. Følgende er en forklaring på denne tilgang:
Hvis du vil have adgangskoden til at være unik, skal du definere kolonnen i din database som UNIQUE
. Dette vil sikre unikhed (selvom din php-kode ikke gør det) ved at nægte at indsætte en række, der ville forårsage en dublet adgangskode.
CREATE TABLE files (
id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
filename varchar(255) NOT NULL,
passcode varchar(64) NOT NULL UNIQUE,
)
Brug nu mysqls SHA1()
og NOW()
for at generere din adgangskode som en del af indsættelseserklæringen. Kombiner dette med INSERT IGNORE ...
(dokumenter
), og løkke, indtil en række er indsat:
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.
}
Bemærk: Ovenstående eksempel blev redigeret for at tage højde for de fremragende observationer, som @martinstoeckli har skrevet i kommentarfeltet. Følgende ændringer blev foretaget:
- ændrede
mysql_num_rows()
(dokumenter ) tilmysql_affected_rows()
(dokumenter ) -- num_rows gælder ikke for indsættelser. Fjernede også argumentet tilmysql_affected_rows()
, da denne funktion fungerer på forbindelsesniveauet, ikke resultatniveauet (og under alle omstændigheder er resultatet af en indsættelse boolesk, ikke et ressourcenummer). - tilføjede fejlkontrol i sløjfetilstanden og tilføjede en test for fejl/succes efter sløjfeafslutning. Fejlhåndteringen er vigtig, da uden den vil databasefejl (såsom mistede forbindelser eller problemer med tilladelser) få sløjfen til at snurre for evigt. Fremgangsmåden vist ovenfor (ved hjælp af
IGNORE
, ogmysql_affected_rows()
, og test$res
separat for fejl) giver os mulighed for at skelne disse "rigtige databasefejl" fra den unikke begrænsningsovertrædelse (som er en fuldstændig gyldig ikke-fejltilstand i denne logiksektion).
Hvis du har brug for at få adgangskoden, efter at den er blevet genereret, skal du blot vælge posten igen:
$res = mysql_query("SELECT * FROM files WHERE id=LAST_INSERT_ID()");
$row = mysql_fetch_assoc($res);
$passcode = $row['passcode'];
Rediger :ændret ovenstående eksempel til at bruge mysql-funktionen LAST_INSERT_ID()
, snarere end PHPs funktion. Dette er en mere effektiv måde at opnå det samme på, og den resulterende kode er renere, klarere og mindre rodet.