Mysql
 sql >> Database >  >> RDS >> Mysql

Ciclo fino a quando il passcode è univoco

A rigor di termini, il tuo test di unicità non garantirà l'unicità sotto un carico simultaneo. Il problema è che controlli l'unicità prima (e separatamente da) il luogo in cui inserisci una riga per "rivendicare" il tuo passcode appena generato. Un altro processo potrebbe essere fare la stessa cosa, allo stesso tempo. Ecco come va...

Due processi generano lo stesso identico passcode. Ognuno di loro inizia controllando l'unicità. Poiché nessuno dei due processi ha (ancora) inserito una riga nella tabella, entrambi i processi non troveranno alcun passcode corrispondente nel database, quindi entrambi i processi presumeranno che il codice sia univoco. Ora, mentre i processi continuano il loro lavoro, alla fine lo faranno entrambi inserisci una riga nei files tabella utilizzando il codice generato -- e quindi ottieni un duplicato.

Per aggirare questo problema, è necessario eseguire il controllo, ed eseguire l'inserimento in un'unica operazione "atomica". Di seguito è riportata una spiegazione di questo approccio:

Se vuoi che il passcode sia univoco, dovresti definire la colonna nel tuo database come UNIQUE . Ciò garantirà l'unicità (anche se il tuo codice php non lo fa) rifiutando di inserire una riga che causerebbe un passcode duplicato.

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

Ora usa SHA1() di mysql e NOW() per generare il tuo passcode come parte di la dichiarazione di inserimento. Combina questo con INSERT IGNORE ... (documenti ), e continua finché una riga non viene inserita correttamente:

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

Nota: L'esempio sopra è stato modificato per tenere conto delle eccellenti osservazioni pubblicate da @martinstoeckli nella sezione commenti. Sono state apportate le seguenti modifiche:

  • cambiato mysql_num_rows() (documenti ) in mysql_affected_rows() (documenti ) -- num_rows non si applica agli inserti. Rimosso anche l'argomento in mysql_affected_rows() , in quanto questa funzione opera a livello di connessione, non a livello di risultato (e comunque il risultato di un inserimento è booleano, non un numero di risorsa).
  • aggiunto il controllo degli errori nella condizione del loop e aggiunto un test di errore/successo dopo l'uscita dal loop. La gestione degli errori è importante, poiché senza di essa, gli errori del database (come connessioni perse o problemi di autorizzazioni) faranno girare il ciclo per sempre. L'approccio mostrato sopra (usando IGNORE e mysql_affected_rows() e testare $res separatamente per gli errori) ci consente di distinguere questi "errori di database reali" dalla violazione del vincolo univoco (che è una condizione di non errore completamente valida in questa sezione della logica).

Se devi ottenere il passcode dopo che è stato generato, seleziona nuovamente il record:

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

Modifica :modificato nell'esempio precedente per utilizzare la funzione mysql LAST_INSERT_ID() , piuttosto che la funzione di PHP. Questo è un modo più efficiente per ottenere la stessa cosa e il codice risultante è più pulito, più chiaro e meno disordinato.