Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Faça um loop até que a senha seja única


Estritamente falando, seu teste de exclusividade não garantirá exclusividade sob uma carga simultânea. O problema é que você verifica a exclusividade antes (e separadamente) do local em que insere uma linha para "reivindicar" sua senha recém-gerada. Outro processo poderia estar fazendo a mesma coisa, ao mesmo tempo. Aqui está como isso vai...

Dois processos geram exatamente a mesma senha. Cada um deles começa verificando a singularidade. Como nenhum processo (ainda) inseriu uma linha na tabela, ambos os processos não encontrarão nenhuma senha correspondente no banco de dados e, portanto, ambos os processos assumirão que o código é exclusivo. Agora, à medida que os processos continuam seu trabalho, eventualmente eles ambos insira uma linha nos files table usando o código gerado -- e assim você obtém uma duplicata.

Para contornar isso, você deve realizar a verificação e fazer a inserção em uma única operação "atômica". Segue uma explicação dessa abordagem:

Se você deseja que a senha seja exclusiva, você deve definir a coluna em seu banco de dados como UNIQUE . Isso garantirá exclusividade (mesmo que seu código php não o faça) recusando-se a inserir uma linha que causaria uma senha duplicada.
CREATE TABLE files (
  id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
  filename varchar(255) NOT NULL,
  passcode varchar(64) NOT NULL UNIQUE,
)

Agora, use o SHA1() do mysql e NOW() para gerar sua senha como parte de a instrução de inserção. Combine isso com INSERT IGNORE ... (documentos ) e faça um loop até que uma linha seja inserida com sucesso:
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.
}

Observação: O exemplo acima foi editado para dar conta das excelentes observações postadas por @martinstoeckli na seção de comentários. As seguintes alterações foram feitas:
  • alterado mysql_num_rows() (documentos ) para mysql_affected_rows() (documentos ) -- num_rows não se aplica a inserções. Também removeu o argumento para mysql_affected_rows() , pois esta função opera no nível da conexão, não no nível do resultado (e em qualquer caso, o resultado de uma inserção é booleano, não um número de recurso).
  • adicionada verificação de erros na condição de loop e adicionado um teste de erro/sucesso após a saída do loop. O tratamento de erros é importante, pois sem ele, erros de banco de dados (como conexões perdidas ou problemas de permissão) farão com que o loop gire para sempre. A abordagem mostrada acima (usando IGNORE e mysql_affected_rows() e testando $res separadamente para erros) nos permite distinguir esses "erros de banco de dados reais" da violação de restrição exclusiva (que é uma condição de não erro completamente válida nesta seção da lógica).

Se você precisar obter a senha depois de gerada, basta selecionar o registro novamente:
$res = mysql_query("SELECT * FROM files WHERE id=LAST_INSERT_ID()");
$row = mysql_fetch_assoc($res);
$passcode = $row['passcode'];

Editar :alterado o exemplo acima para usar a função mysql LAST_INSERT_ID() , em vez da função do PHP. Essa é uma maneira mais eficiente de realizar a mesma coisa, e o código resultante é mais limpo, mais claro e menos confuso.