Да, правите нещо нередно.
Вижте прост пример.
Сесия 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 ms по-късно
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)
Двама потребители взеха една и съща стойност 1, но само потребител 2 е регистриран в таблицата
======================РЕДАКТИРАНЕ ===================================
В този сценарий можем да използваме SELECT .. FOR UPDATE и да използваме начин, по който postgre преоценява заявката в режим Read Committed Isolation Level,
вижте документацията:http://www.postgresql.org/docs/9.2/static/transaction-iso.html
Накратко:
ако една сесия е заключила реда, а другата сесия се опитва да заключи същия ред, тогава втората сесия ще "замръзне" и ще изчака първата сесия да се ангажира или върне назад. Когато първата сесия ангажира транзакцията, тогава втората сесия ще преоцени условието за търсене WHERE. Ако условието за търсене не съвпада (тъй като първата транзакция промени някои колони), тогава втората сесия ще пропусне този ред и ще обработи следващ ред, който съответства на WHERE условия.
Забележка:това поведение е различно при повтарящи се нива на изолация при четене. В този случай втората сесия ще изведе грешка:не може да сериализира достъпа поради едновременна актуализация и трябва да опитате отново цялата транзакция.
Нашата заявка може изглежда така:
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)
Лично аз предпочитам да тествам сценарии за заключване с помощта на обикновени, стари конзолни клиенти (psql за postgree, mysql или SQLPlus за oracle)
Така че нека тестваме нашата заявка в 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 ;
Сесия 2 увисва, докато се опитва да заключи ред id =2
ОК, нека се ангажира сесия 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 сесии се опитват едновременно да получат следващия ред от тази таблица - всяка сесия ще виси и ще чака предишната сесия да се ангажира.
Така че не използвайте тази заявка в производствения код.
Наистина ли искате да „намерите и запазите следващата стойност от таблицата“? Защо?
Ако отговорът е да, трябва да имате някакво устройство за сериализиране (като тази заявка или, може би по-лесно за изпълнение, заключване на цялата таблица), но това ще бъде пречка.