PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

postgresql - script que usa blocos de transação falha ao criar todos os registros


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.