私はこのようなスキーマを持つ座席と呼ばれるテーブルを持っています
id、taken
ユーザーごとに、ランダムな未使用のIDを取得してそのユーザーに割り当てます。ここでは簡単にするために、taken = 1とします。私が使用しているクエリ
update seats u inner join (
SELECT id from seats
where taken is null limit 1) s
on s.id = u.id set taken = 1;
このクエリは、フラグがnullのランダムシートを取得し、そのシートに対してフラグを1にします。このクエリは正常に機能していますが、このスレッドは安全ですか?
このシナリオを考えると、2人のユーザーが並行しています。 user1の場合は行Xを選択し、更新クエリが実行される直前にuser2がチェックインし、そのユーザーの選択クエリはuser1と同じ行を返します。したがって、同じ行の更新を2回終了します。
このシナリオはこのクエリで可能ですか?
クエリは、MySQLクエリオプティマイザーにとって危険です。
古い投稿があります( MySQLサブクエリの問題 )。提起された質問はこのクエリに関するものでした
DELETE FROM test WHERE id =
(SELECT id FROM (SELECT * FROM test) temp ORDER BY Rand() LIMIT 1);
MySQL Query Parserはこのクエリで動作し、その構文を受け入れますが、MySQLドキュメントでは、特定の最適化フェーズ中に一部のデータが消えることがあると示唆しています。あなたのケースでは、IDを取得するために1つの行だけにアクセスし、その行を更新しています。それにもかかわらず、断続的に消える可能性があるためにid
を返さない可能性のあるサブクエリからデータにアクセスする可能性は、トランザクションセーフではなく、スレッドセーフでもありません。
あなたのクエリはその古い投稿のクエリに似ています
この問題は、スレッドセーフであるかどうかだけでは限界を超えています。たとえそうであっても、サブクエリの途中で空いているシートを取得するというMySQLの解釈を信用すべきではありません。クエリはトランザクションセーフである必要があります。
これをトランザクション内の3つのクエリとして書き直すことをお勧めします。
おそらくこのようなもの:
START TRANSACTION;
SELECT id from seats WHERE taken is null limit 1 FOR UPDATE;
SELECT id INTO @available_id from seats WHERE taken is null limit 1;
UPDATE seats SET taken = 1 WHERE id = @available_id;
COMMIT;
または2つのクエリで
START TRANSACTION;
SELECT id INTO @available_id from seats WHERE taken is null limit 1 FOR UPDATE;
UPDATE seats SET taken = 1 WHERE id = @available_id;
COMMIT;
詳細については、 MySQLのドキュメントSELECT ... FOR UPDATE
をご覧ください。
また、seats
テーブルが取得した列にインデックスを持っていることを確認します。
そうでない場合は、このインデックスを追加します
ALTER TABLE seats ADD INDEX (taken);
開発/ステージング環境でこれを行い、本番環境でこれを行う前にクエリをテストします。
InnoDBを使用している場合、SELECT ... FOR UPDATE
文でトランザクションを保護できます
http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
これにより、トランザクションが終了していないときに行に対して排他ロックを実行できます。他のセッションは、行ロックが解放されるまで待機します。