1

J'utilise une table intermédiaire que je mets à jour pour m'assurer qu'aucune autre action simultanée ne peut être traitée sur une table critique qui ne doit pas être accédée simultanément.Protection des tables de ressources critiques dans une clause CTE (WITH)

Transaction 1

BEGIN 
UPDATE locktable 
/* Do some stuff */ 
... 
COMMIT 

transaction concurrente 2

BEGIN 
Update locktable 
/* Do some other stuff */ 
... 
COMMIT 

De cette façon, je suis sûr que la transaction 1 et 2 sont la transaction atomique. J'ai changé mon code en une clause clause WITH pour des raisons de simplification et de performance. Je me demande si je peux garantir l'atomicité de fonctionnement de la même manière avec les CTE.

CTE Exemple simplifié:

Transaction 1

WITH 

lock_op AS (
UPDATE locktable 
... 
RETURNING id), 

some_stuff AS 
(
/* Do insert and update operations with RETURNING clause*/ 
... 
) 

SELECT * 
FROM some_stuff 
WHERE EXISTS (SELECT 1 FROM lock_op) 

transaction concurrente 2

WITH 

lock_op AS (
UPDATE locktable 
... 
RETURNING id), 

other_stuff AS 
(
/* Do insert and update operations with RETURNING clause*/ 
... 
) 

SELECT * 
FROM other_stuff 
WHERE EXISTS (SELECT 1 FROM lock_op) 

En fait, je me demande si "SELECT 1 FROM lock_op" est initiée avant toute INSERT et UPDATE de some_stuff et other_stuff et par conséquent, protège mes données critiques pour le moment de la transaction délimitée par la portée WITH?

Répondre

2

Vous n'avez pas les mêmes garanties de commande ici. Il n'y a aucune promesse que la requête de lock_op s'exécutera avant some_stuff.

Sinon, c'est raisonnablement OK. Le verrou de ligne est pris en lock_op et maintenu jusqu'à ce que la transaction implicite qui enveloppe le CTE (si vous n'utilisez pas begin/commit explicite) soit validée.

Pour obtenir une telle garantie de commande, vous pouvez utiliser un sous-requête en avec un OFFSET 0 DE, ou vous pouvez faire la requête dans some_stuff dépendent directement lock_op pour vérifier qu'elle évalue d'abord.

Personnellement, je le laisserais tel quel, peut-être avec un SELECT ... FOR UPDATE au lieu d'un UPDATE si vous pouvez réduire le taux de désabonnement MVCC.


Pour d'autres lecteurs, il est important de noter que cette affiche est pas en supposant que les choses en quelque sorte faire dans une déclaration unique qui les rend se produisent atomiquement, à l'abri des effets de concurrence. Cette hypothèse serait absolument faux. Les CTE ne sont pas une sauce magique de fixation simultanée.

Vous devez utiliser le verrouillage de ligne ou de table ou (avec soin et compréhension) utiliser SERIALIZABLE isolation + une boucle de réessai.

L'approche la plus simple consiste à LOCK TABLE ... IN EXCLUSIVE MODE dans la transaction qui effectue les modifications. Cela autorise les lectures simultanées, mais pas les écritures.

Pour un verrouillage à grain fin, utilisez une sous-requête ou un terme CTE avec SELECT ... FOR UPDATE.

+0

Merci pour cette réponse très claire.Je pense que je vais opter pour une solution hybride mettant en œuvre deux requêtes distinctes: 1/une pour verrouiller la table lock_op et 2/une autre pour le CTE. Les deux dans une seule transaction. Mon CTE contient de nombreuses commandes qui doivent être globalement atomiques pour des raisons de cohérence et (si j'ai bien compris) SELECT FOR UPDATE ne garantira que des opérations individuelles à préserver des accès/changements simultanés. – Nitseg

+0

Si vous avez besoin d'une isolation globale, envisagez un verrou consultatif. Il devrait être beaucoup moins cher que 'SELECT ... FOR UPDATE' -ing une table de verrouillage à une rangée. –

+0

Merci pour l'indice. Je ne connaissais pas les verrous consultatifs. Il pourrait éventuellement répondre à mes besoins puisque toutes les opérations que j'ai besoin d'isoler sont liées à un seul identifiant que je peux fournir à la fonction pg_advisory_lock. – Nitseg