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


10

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

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

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

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

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


おそらくトランザクションを使用していますか?
ypercubeᵀᴹ

回答:


15

更新ステートメントは、前の選択なしで完全に正常に動作します!定義上、単一のステートメントは安全であるため、同時に実行される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、分離レベルのような値は必要ありません。このユースケースでは、他のすべてが問題ないはずです。


私は他の場所でそれUPDATE table SET counter = counter + 1が十分に原子的であると読みましたか?それを取り巻くトランザクションステートメントはまだ必要ですか?
CMCDragonkai、2015年

@CMCDragonkaiクエリのみがアトミックですが、以前に値を選択し、FOR UPDATEトランザクションを使用しなかった場合、選択した値は更新クエリで使用された値と異なる場合があります。クエリの組み合わせでは、値が選択されるとすぐに行がロックされるため、この正確なカウンタ値が更新クエリで使用されることが保証されます。
GhostGambler

わかりましたが、それは、インクリメント以外の作業を行う場合にのみ必要ですか?現状では、アトミックな更新クエリが1つあれば十分です。
CMCDragonkai、2015年

1
@CMCDragonkai列に触れる別のクエリを実行しない場合は、問題ありません。
GhostGambler
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.