厳密に言えば、一意性のテストでは、同時負荷の下での一意性は保証されません。問題は、新しく生成されたパスコードを「要求」するために行を挿入する場所の前に(そしてそれとは別に)一意性をチェックすることです。別のプロセスが同時に同じことをしている可能性があります。これがその方法です...
2つのプロセスがまったく同じパスコードを生成します。それらはそれぞれ、一意性をチェックすることから始まります。どちらのプロセスも(まだ)テーブルに行を挿入していないため、両方のプロセスはデータベースに一致するパスコードを検出しません。したがって、両方のプロセスはコードが一意であると見なします。プロセスはそれぞれ作業を継続するため、最終的には両方になります。 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,
)
ここで、mysqlのSHA1()
を使用します および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()
(ドキュメント )からmysql_affected_rows()
(ドキュメント )-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の関数ではなく。これは同じことを達成するためのより効率的な方法であり、結果のコードはよりクリーンで明確になり、雑然としません。