web-dev-qa-db-ja.com

スレッドセーフな方法で値(カウンター)をクエリしてインクリメントする方法は? (競合状態を回避)

各行にカウンター(整数値のみ)があるテーブルでは、現在の値を取得してそれを増やす必要があります同時に

効果的に、私はこれをしたいです:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

しかし、これを2つのクエリで実行することは明らかにスレッドセーフではありません。同じことを(同じ行で)実行する複数のプロセスが同じカウンタ値を取得する場合があります。私はそれらすべてが一意であることを必要とするので、各プロセスはactual現在の値を取得し、それを1つ増やします。

行ごとに手動ロックを実装する構成を考えることができますが、これを行う簡単な方法はあるのでしょうか。

10
RocketNuts

更新ステートメントは、前の選択なしで完全に正常に動作します!定義上、単一のステートメントは安全であるため、同時に実行される2つのUPDATEクエリだけでも、行が2回インクリメントされます。

実際にPHPスクリプトの値を選択し、それを使用して、後でこの正確なカウンター値を更新したい場合は、次のようにします。

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

これにより、新しいトランザクションが開始され、更新する行が選択されて排他的にロックされます。その後、他のクライアントがコンテンツを変更したり、ロックされた行にアクセスしたりすることを心配することなく、それらを安全に更新できます。最後に、変更をコミットする必要があります。

分離レベル についてもお読みください。分離レベルとしてREAD UNCOMMITTEDのような値は望ましくないでしょう。このユースケースでは、他のすべてが問題ないはずです。

15
GhostGambler