2

MySQL を使用して「貧乏人のキューイング システム」を構築しています。これは、実行する必要があるジョブを含む単一のテーブルです (テーブル名は ですqueue)。複数のマシンにいくつかのプロセスがあり、その仕事はfetch_next2sprocを呼び出してキューからアイテムを取得することです。

この手順の要点は、2 つのクライアントに同じ仕事をさせないようにすることです。を使用すると、1 つの行をロックできるので、1 人の呼び出し元によってのみ更新されたことを確認できます ( 「準備完了」のジョブをフィルター処理するために使用されるSELECT .. LIMIT 1 FOR UPDATE基準に適合しなくなるように更新されます)。SELECT処理されます)。

誰が私が間違っているのか教えてもらえますか? 同じジョブが 2 つの異なるプロセスに与えられた例がいくつかあったので、正しく動作しないことがわかっています。:)

CREATE DEFINER=`masteruser`@`%` PROCEDURE `fetch_next2`()
BEGIN
    SET @id = (SELECT q.Id FROM queue q WHERE q.State = 'READY' LIMIT 1 FOR UPDATE);

    UPDATE queue
    SET State = 'PROCESSING', Attempts = Attempts + 1
    WHERE Id = @id;

    SELECT Id, Payload
    FROM queue
    WHERE Id = @id;
END
4

1 に答える 1

2

答えのコード:

CREATE DEFINER=`masteruser`@`%` PROCEDURE `fetch_next2`()
BEGIN
    SET @id := 0; 
    UPDATE queue SET State='PROCESSING', Id=(SELECT @id := Id) WHERE State='READY' LIMIT 1;

    #You can do an if @id!=0 here
    SELECT Id, Payload
    FROM queue
    WHERE Id = @id;
END

あなたがしていることの問題は、操作のアトミックなグループ化がないことです。SELECT ... FOR UPDATE 構文を使用しています。ドキュメントには、「特定のトランザクション分離レベルでのデータの読み取り」をブロックすると書かれています。しかし、すべてのレベルではありません (と思います)。最初の SELECT と UPDATE の間に、別のスレッドから別の SELECT が発生する可能性があります。MyISAM または InnoDB を使用していますか? MyISAM がサポートしていない可能性があります。

これが適切に機能することを確認する最も簡単な方法は、テーブルをロックすることです。


Id=(SELECT @id := Id)[編集] ここで説明する方法は、上記のコードで使用する方法よりも時間がかかります。

別の方法は、次のようにすることです。

  1. 通常は 0 に設定されている列があります。
  2. 「UPDATE ... SET ColName=UNIQ_ID WHERE ColName=0 LIMIT 1 を実行します。これにより、1 つのプロセスだけがその行を更新できるようになり、後で SELECT を介して取得できます。(UNIQ_ID は MySQL の機能ではなく、単なる変数です。 )

一意の ID が必要な場合は、そのためだけに auto_increment を持つテーブルを使用できます。


トランザクションでこれを行うこともできます。テーブルでトランザクションを開始し、あるスレッドから実行UPDATE foobar SET LockVar=19 WHERE LockVar=0 LIMIT 1;し、別のスレッドでまったく同じことを行うと、2 番目のスレッドは、最初のスレッドがコミットするまで待機してから行を取得します。ただし、それは完全なテーブル ブロッキング操作になる可能性があります。

于 2016-10-11T02:03:28.423 に答える