はい、あなたは何か間違ったことをしています。
簡単な例を見てください。
セッション1
postgres=# select * from user_reservation_table;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | f | 0 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
postgres=# \set user 1
postgres=#
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(# SELECT uservalue FROM user_reservation_table
postgres(# WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=#
セッション2 -同時に、ただしわずか10ミリ秒後
postgres=# \set user 2
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(# SELECT uservalue FROM user_reservation_table
postgres(# WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
セッション2がハングし、.......何かを待っています....
セッション1に戻ります
postgres=# commit;
COMMIT
postgres=#
そして再びセッション2
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=# commit;
COMMIT
postgres=#
セッション2はもう待機しておらず、トランザクションを終了します。
そして、最終的な結果は何ですか?:
postgres=# select * from user_reservation_table order by id;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | t | 2 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
2人のユーザーが同じ値1を取りましたが、テーブルにはユーザー2のみが登録されています
======================EDIT ==================================
このシナリオでは、SELECT .. FOR UPDATEを使用して、postgreがRead Committed IsolationLevelモードでクエリを再評価する方法を利用できます。
ドキュメントを参照してください: http://www.postgresql.org/docs/9.2/static/transaction-iso.html
つまり、
一方のセッションが行をロックし、もう一方のセッションが同じ行をロックしようとすると、2番目のセッションは「ハング」し、最初のセッションがコミットまたはロールバックするのを待ちます。最初のセッションの場合トランザクションをコミットすると、2番目のセッションはWHERE検索条件を再評価します。検索条件が一致しない場合(最初のトランザクションが一部の列を変更したため)、2番目のセッションはその行をスキップし、WHEREに一致する次の行を処理します。条件。
注:この動作は、繰り返し可能な読み取り分離レベルでは異なります。その場合、2番目のセッションでエラーがスローされます。同時更新のためにアクセスをシリアル化できなかったため、トランザクション全体を再試行する必要があります。
クエリが次のようになります:
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update ;
そして:
Update .... where id = (id returned by SELECT ... FOR UPDATE)
個人的には、プレーンな古いコンソールクライアント(postgreeの場合はpsql、oracleの場合はmysqlまたはSQLPlus)を使用してロックシナリオをテストすることを好みます
では、psqlでクエリをテストしましょう:
session1 #select * from user_reservation_table order by id;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | t | 2 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
session1 #begin;
BEGIN
session1 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
id
----
2
(1 wiersz)
session1 #update user_reservation_table set usedyesno = true
postgres-# where id = 2;
UPDATE 1
session1 #
セッション1がロックされ、行id=2が更新されました
そして今度はセッション2
session2 #begin;
BEGIN
session2 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
行ID=2をロックしようとしているときにセッション2がハングします
OK、セッション1をコミットします
session1 #commit;
COMMIT
session1 #
セッション2で何が起こるか見てみましょう:
postgres-# for update ;
id
----
3
(1 wiersz)
ビンゴ-セッション2は行ID=2をスキップし、行ID =3を選択(およびロック)しました
したがって、最終的なクエリは次のようになります。
update user_reservation_table
set usedyesno = true
where id = (
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update
) RETURNING uservalue;
いくつかの予約-この例はテスト目的のみであり、postgreでロックがどのように機能しているかを理解するのに役立ちます。
実際、このクエリはテーブルへのアクセスをシリアル化し、スケーラブルではなく、おそらく実行されますマルチユーザー環境では悪い(遅い)。
10セッションが同時にこのテーブルから次の行を取得しようとしていると想像してください。各セッションはハングし、前のセッションがコミットされるまで待機します。
したがって、使用しないでください。プロダクションコードでのこのクエリ。
本当に「テーブルから次の値を見つけて予約」しますか?なぜですか?
はいの場合、シリアル化デバイスが必要です(このクエリのように、または実装が簡単な場合は、テーブル全体をロックする必要があります)が、これがボトルネックになります。