Строго погледнато, вашият тест за уникалност няма да гарантира уникалност при едновременно натоварване. Проблемът е, че проверявате за уникалност преди (и отделно от) мястото, където вмъквате ред, за да "искате" новата си генерирана парола. Друг процес може да прави същото нещо по едно и също време. Ето как става...
Два процеса генерират една и съща парола. Всеки от тях започва с проверка за уникалност. Тъй като нито един процес (все още) не е вмъкнал ред в таблицата, и двата процеса няма да намерят съответстваща парола в базата данни и така и двата процеса ще приемат, че кодът е уникален. Сега, тъй като всеки процес продължава своята работа, в крайна сметка те ще и двата вмъкнете ред в files
таблица, използвайки генерирания код - и по този начин получавате дубликат.
За да заобиколите това, трябва да извършите проверката и да направите вмъкването с една "атомна" операция. Следва обяснение на този подход:
Ако искате паролата да е уникална, трябва да дефинирате колоната във вашата база данни като UNIQUE
. Това ще гарантира уникалност (дори ако вашият php код не го прави), като откаже да вмъкнете ред, който би причинил дублиран парола.
CREATE TABLE files (
id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
filename varchar(255) NOT NULL,
passcode varchar(64) NOT NULL UNIQUE,
)
Сега използвайте SHA1()
на mysql и NOW()
за генериране на вашата парола като част от изявлението за вмъкване. Комбинирайте това с INSERT IGNORE ...
(документи
) и повтаряйте, докато редът бъде успешно вмъкнат:
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.
}
Забележка: Горният пример беше редактиран, за да отчете отличните наблюдения, публикувани от @martinstoeckli в секцията за коментари. Бяха направени следните промени:
- променен
mysql_num_rows()
(docs ) къмmysql_affected_rows()
(docs ) -- num_rows не се отнася за вмъквания. Също така премахна аргумента наmysql_affected_rows()
, тъй като тази функция работи на ниво връзка, а не на резултатно ниво (и във всеки случай резултатът от вмъкването е булев, а не номер на ресурс). - добавена е проверка на грешки в условието на цикъла и е добавен тест за грешка/успех след излизане от цикъла. Обработката на грешки е важна, тъй като без нея грешките в базата данни (като загубени връзки или проблеми с разрешенията) ще накарат цикъла да се върти завинаги. Подходът, показан по-горе (използвайки
IGNORE
иmysql_affected_rows()
и тестване на$res
отделно за грешки) ни позволява да разграничим тези „реални грешки в базата данни“ от уникалното нарушение на ограничението (което е напълно валидно условие без грешка в този раздел на логиката).
Ако трябва да получите паролата, след като е била генерирана, просто изберете записа отново:
$res = mysql_query("SELECT * FROM files WHERE id=LAST_INSERT_ID()");
$row = mysql_fetch_assoc($res);
$passcode = $row['passcode'];
Редактиране :променен горният пример за използване на функцията mysql LAST_INSERT_ID()
, а не функцията на PHP. Това е по-ефективен начин за постигане на същото нещо и полученият код е по-чист, по-ясен и по-малко претрупан.