sql >> Base de Datos >  >> RDS >> Mysql

Bucle hasta que el código de acceso sea único

Estrictamente hablando, su prueba de unicidad no garantizará la unicidad bajo una carga concurrente. El problema es que verifica la singularidad antes (y por separado) del lugar donde inserta una fila para "reclamar" su código de acceso recién generado. Otro proceso podría estar haciendo lo mismo, al mismo tiempo. Así es como va...

Dos procesos generan exactamente el mismo código de acceso. Cada uno comienza comprobando la singularidad. Dado que ninguno de los procesos (todavía) ha insertado una fila en la tabla, ambos procesos no encontrarán un código de acceso coincidente en la base de datos, por lo que ambos procesos asumirán que el código es único. Ahora que cada uno de los procesos continúa con su trabajo, eventualmente ambos inserta una fila en los files tabla usando el código generado, y por lo tanto obtienes un duplicado.

Para evitar esto, debe realizar la verificación y hacer la inserción en una sola operación "atómica". La siguiente es una explicación de este enfoque:

Si desea que el código de acceso sea único, debe definir la columna en su base de datos como UNIQUE . Esto garantizará la unicidad (incluso si su código php no lo hace) al negarse a insertar una fila que causaría un código de acceso duplicado.

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

Ahora, use SHA1() de mysql y NOW() para generar su contraseña como parte de la declaración de inserción. Combine esto con INSERT IGNORE ... (docs ), y repite hasta que una fila se inserte correctamente:

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: El ejemplo anterior fue editado para dar cuenta de las excelentes observaciones publicadas por @martinstoeckli en la sección de comentarios. Se realizaron los siguientes cambios:

  • cambió mysql_num_rows() (docs ) a mysql_affected_rows() (docs ) -- num_rows no se aplica a las inserciones. También eliminó el argumento de mysql_affected_rows() , ya que esta función opera en el nivel de conexión, no en el nivel de resultado (y en cualquier caso, el resultado de una inserción es booleano, no un número de recurso).
  • Se agregó la verificación de errores en la condición del bucle y se agregó una prueba de error/éxito después de que el bucle sale. El manejo de errores es importante, ya que sin él, los errores de la base de datos (como conexiones perdidas o problemas de permisos) harán que el bucle gire para siempre. El enfoque que se muestra arriba (usando IGNORE y mysql_affected_rows() y probando $res por separado para errores) nos permite distinguir estos "errores reales de la base de datos" de la violación de restricción única (que es una condición sin error completamente válida en esta sección de lógica).

Si necesita obtener el código de acceso después de que se haya generado, simplemente seleccione el registro nuevamente:

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

Editar :cambiado el ejemplo anterior para usar la función mysql LAST_INSERT_ID() , en lugar de la función de PHP. Esta es una forma más eficiente de lograr lo mismo y el código resultante es más limpio, más claro y menos desordenado.