Sim, você está fazendo algo errado.
Veja um exemplo simples.
Sessão 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=#
Sessão 2 - ao mesmo tempo, mas apenas 10 ms depois
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;
A sessão 2 trava ....... e está esperando por algo ....
de volta à Sessão 1
postgres=# commit;
COMMIT
postgres=#
e novamente Sessão 2
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=# commit;
COMMIT
postgres=#
A sessão 2 não está mais esperando, e termina sua transação.
E qual é o resultado final?:
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)
Dois usuários assumiram o mesmo valor 1, mas apenas o usuário 2 está registrado na tabela
======================EDIT ==================================
Nesse cenário, podemos usar SELECT .. FOR UPDATE e utilizar uma maneira pela qual o postgre reavalia a consulta no modo Read Committed Isolation Level,
consulte a documentação:http://www.postgresql.org/docs/9.2/static/transaction-iso.html
Resumindo:
se uma sessão bloqueou a linha e a outra sessão está tentando bloquear a mesma linha, a segunda sessão "travará" e aguardará a confirmação ou reversão da primeira sessão. confirma a transação, a segunda sessão reavaliará a condição de pesquisa WHERE. Se a condição de pesquisa não corresponder (porque a primeira transação alterou algumas colunas), a segunda sessão ignorará essa linha e processará uma próxima linha que corresponda a WHERE condições.
Nota:este comportamento é diferente em níveis de isolamento de leitura repetitiva. Nesse caso, a segunda sessão gerará erro:não foi possível serializar o acesso devido à atualização simultânea e você deve tentar novamente toda a transação.
Nossa consulta pode parece:
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update ;
e depois:
Update .... where id = (id returned by SELECT ... FOR UPDATE)
Pessoalmente, prefiro testar cenários de bloqueio usando clientes de console simples e antigos (psql para postgree, mysql ou SQLPlus para oracle)
Então vamos testar nossa consulta no 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 #
A sessão 1 bloqueou e atualizou um id de linha=2
E agora a sessão2
session2 #begin;
BEGIN
session2 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
A sessão 2 trava ao tentar bloquear o ID da linha =2
OK, vamos confirmar a sessão 1
session1 #commit;
COMMIT
session1 #
e veja o que acontece na sessão 2:
postgres-# for update ;
id
----
3
(1 wiersz)
Bingo - a sessão 2 pulou o id da linha =2 e selecionou (e bloqueou) o id da linha =3
Então nossa consulta final pode ser:
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;
Algumas ressalvas - este exemplo é apenas para fins de teste e sua finalidade é ajudar a entender como o bloqueio está funcionando no postgre.
Na verdade, essa consulta serializará o acesso à tabela e não é escalável e provavelmente funcionará ruim (lento) em ambiente multiusuário.
Imagine que 10 sessões estão tentando simultaneamente obter a próxima linha desta tabela - cada sessão irá travar e estará esperando até que a sessão anterior seja confirmada.
Então não use esta consulta no código de produção.
Você realmente deseja "encontrar e reservar o próximo valor da tabela" ? Por que ?
Se sim, você deve ter algum dispositivo de serialização (como esta consulta, ou talvez mais fácil de implementar, travando toda a tabela), mas isso será um gargalo.