PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

postgresql - le script qui utilise des blocs de transaction ne parvient pas à créer tous les enregistrements

Oui, vous faites quelque chose de mal.
Regardez un exemple simple.

Session 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=#


Séance 2 - en même temps, mais seulement 10 ms plus tard

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;

La session 2 se bloque ....... et attend quelque chose ....

de retour dans la Session 1

postgres=# commit;
COMMIT
postgres=#



et encore Session 2

 uservalue
-----------
         1
(1 wiersz)


UPDATE 1
postgres=# commit;
COMMIT
postgres=#

La session 2 n'attend plus, et termine sa transaction.

Et quel est le résultat 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)

Deux utilisateurs ont pris la même valeur 1, mais seul l'utilisateur 2 est enregistré dans la table





=====================MODIFIER =================================

Dans ce scénario, nous pouvons utiliser SELECT .. FOR UPDATE et utiliser une manière dont postgre réévalue la requête en mode Read Committed Isolation Level,
voir la documentation :http://www.postgresql.org/docs/9.2/static/transaction-iso.html

En bref :
si une session a verrouillé la ligne et que l'autre session essaie de verrouiller la même ligne, la deuxième session "se bloquera" et attendra que la première session soit validée ou annulée.Lorsque la première session valide la transaction, la deuxième session réévaluera la condition de recherche WHERE. Si la condition de recherche ne correspond pas (parce que la première transaction a modifié certaines colonnes), la deuxième session ignorera cette ligne et traitera une ligne suivante qui correspond à WHERE conditions.

Remarque :ce comportement est différent dans les niveaux d'isolement de lecture répétables. Dans ce cas, la deuxième session générera une erreur :impossible de sérialiser l'accès en raison d'une mise à jour simultanée et vous devez réessayer toute la transaction.

Notre requête peut ressemble à :

select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update ;

puis :

  Update .... where id = (id returned by SELECT ... FOR UPDATE)



Personnellement, je préfère tester les scénarios de verrouillage à l'aide d'anciens clients de console simples (psql pour postgree, mysql ou SQLPlus pour oracle)

Alors testons notre requête dans 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 #

Session 1 verrouillée et mise à jour d'une ligne id=2

Et maintenant session2

session2 #begin;
BEGIN
session2 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;

La session 2 se bloque lors de la tentative de verrouillage de la ligne id =2

OK, commençons par valider la session 1

session1 #commit;
COMMIT
session1 #

et regardez ce qui se passe dans la session 2 :

postgres-# for update ;
 id
----
  3
(1 wiersz)

Bingo - la session 2 a ignoré l'identifiant de ligne = 2 et a sélectionné (et verrouillé) l'identifiant de ligne = 3


Donc, notre requête finale pourrait être :

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;

Certaines réserves - cet exemple est uniquement à des fins de test et son but est d'aider à comprendre comment le verrouillage fonctionne dans postgre.
En fait, cette requête sérialisera l'accès à la table, et n'est pas évolutive et fonctionnera probablement mauvais (lent) dans un environnement multi-utilisateur.
Imaginez que 10 sessions essaient simultanément d'obtenir la ligne suivante de cette table - chaque session se bloquera et attendra que la session précédente soit validée.
Donc, n'utilisez pas cette requête dans le code de production.
Voulez-vous vraiment "trouver et réserver la prochaine valeur de la table" ? Pourquoi ?
Si oui, vous devez disposer d'un dispositif de sérialisation (comme cette requête, ou, peut-être plus facile à mettre en œuvre, verrouiller toute la table), mais ce sera un goulot d'étranglement.