Optimisation des mises à jour simultanées dans Postgres

9

J'exécute des requêtes Postgres simultanées comme ceci:

UPDATE foo SET bar = bar + 1 WHERE baz = 1234

Chaque requête affecte le nombre K fixe de lignes, et je ne peux pas trouver un moyen d'appliquer l'ordre dans lequel les lignes sont mises à jour, je me retrouve avec des blocages. Actuellement, je résout le problème en appliquant l'ordre manuellement, mais cela signifie que je dois exécuter beaucoup plus de requêtes que je ne le ferais normalement tout en augmentant la complexité de la recherche de O (log N + K) à O (K log N).

Existe-t-il un moyen d'améliorer les performances sans se retrouver vulnérable aux blocages? Je soupçonne que le remplacement de l' (baz)index par l' (baz, id)index pourrait fonctionner à condition que Postgres mette à jour les lignes dans le même ordre qu'il les a scannées, est-ce une approche qui mérite d'être poursuivie?

Alexei Averchenko
la source
Je vous suggère d'ajouter le CREATE TABLEcode.
ypercubeᵀᴹ

Réponses:

15

Il n'y ORDER BYen a pas dans une SQL UPDATEcommande. Postgres met à jour les lignes dans un ordre arbitraire:

Pour éviter les blocages avec une certitude absolue, vous pouvez exécuter vos instructions dans une isolation de transaction sérialisable . Mais c'est plus cher et vous devez vous préparer à répéter les commandes en cas d'échec de la sérialisation.

Votre meilleure solution consiste probablement à verrouiller explicitement avec SELECT ... ORDER BY ... FOR UPDATEdans une sous-requête ou de manière autonome SELECTdans une transaction - par défaut, en niveau d'isolement "lecture validée". Citant Tom Lane sur pgsql-general :

Cela devrait être correct --- le verrouillage FOR UPDATE est toujours la dernière étape du pipeline SELECT.

Cela devrait faire le travail:

BEGIN;

SELECT 1
FROM   foo 
WHERE  baz = 1234
ORDER  BY bar
FOR    UPDATE;

UPDATE foo
SET    bar = bar + 1
WHERE  baz = 1234;

COMMIT;

Un index multicolonne sur (baz, bar)pourrait être parfait pour les performances. Mais comme il barest évidemment beaucoup mis à jour , un index sur une seule colonne (baz)pourrait être encore mieux. Cela dépend de quelques facteurs. Combien de lignes par baz? Les mises à jour HOT sont-elles possibles sans l'index multicolonne? ...

Si baz est mis à jour simultanément, il y a toujours une chance improbable de cas de coin pour les conflits (par documentation) :

Il est possible pour une SELECTcommande s'exécutant au READ COMMITTED niveau de l'isolement des transactions et utilisant ORDER BYet une clause de verrouillage de renvoyer les lignes dans le désordre. ...

En outre, si vous devez avoir une contrainte unique impliquant bar, envisagez une DEFERRABLEcontrainte pour éviter les violations uniques au sein de la même commande. Réponse connexe:

Erwin Brandstetter
la source
1
SI je commande par idou une autre colonne unique au lieu de bar, il ne devrait pas y avoir de cas d'angle ou de performance, non?
Alexei Averchenko
@AlexeiAverchenko: Oui, une colonne unique qui n'est jamais mise à jour serait parfaite pour cela - et un index multicolonne incluant cette colonne en deuxième position.
Erwin Brandstetter