web-dev-qa-db-ja.com

この更新では、組み合わせクエリスレッドセーフを選択しますか?

私はこのようなスキーマを持つ座席と呼ばれるテーブルを持っています

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回終了します。

このシナリオはこのクエリで可能ですか?

6
Dude

評価

クエリは、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を返さない可能性のあるサブクエリからデータにアクセスする可能性は、トランザクションセーフではなく、スレッドセーフでもありません。

あなたのクエリはその古い投稿のクエリに似ています

  • 非SELECTクエリ
  • SELECTサブクエリ
  • サブクエリはLIMITを使用します

この問題は、スレッドセーフであるかどうかだけでは限界を超えています。たとえそうであっても、サブクエリの途中で空いているシートを取得するというMySQLの解釈を信用すべきではありません。クエリはトランザクションセーフである必要があります。

提案#1

これをトランザクション内の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 をご覧ください。

提案#2

また、seatsテーブルが取得した列にインデックスを持っていることを確認します。

そうでない場合は、このインデックスを追加します

ALTER TABLE seats ADD INDEX (taken);

開発/ステージング環境でこれを行い、本番環境でこれを行う前にクエリをテストします。

試してみる !!!

2
RolandoMySQLDBA

InnoDBを使用している場合、SELECT ... FOR UPDATE文でトランザクションを保護できます

http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html

これにより、トランザクションが終了していないときに行に対して排他ロックを実行できます。他のセッションは、行ロックが解放されるまで待機します。

0
vegatripy