J'ai besoin de conserver un numéro de révision unique (par ligne) dans une table document_revisions, où le numéro de révision est limité à un document, il n'est donc pas unique à la table entière, seulement au document associé.
Au départ, j'ai trouvé quelque chose comme:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Mais il y a une condition de course!
J'essaie de le résoudre avec pg_advisory_lock
, mais la documentation est un peu rare et je ne la comprends pas complètement, et je ne veux pas verrouiller quelque chose par erreur.
Est-ce que ce qui suit est acceptable, ou est-ce que je me trompe, ou y a-t-il une meilleure solution?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Ne devrais-je pas verrouiller la ligne de document (clé1) pour une opération donnée (clé2) à la place? Ce serait donc la bonne solution:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Peut-être que je ne suis pas habitué à PostgreSQL et qu'un SERIAL peut être délimité, ou peut-être une séquence et nextval()
ferait mieux le travail?
la source
Réponses:
En supposant que vous stockez toutes les révisions du document dans un tableau, une approche serait de ne pas stocker le numéro de révision mais de le calculer en fonction du nombre de révisions stockées dans le tableau.
C'est, essentiellement, une valeur dérivée , pas quelque chose que vous devez stocker.
Une fonction de fenêtre peut être utilisée pour calculer le numéro de révision, quelque chose comme
et vous aurez besoin d'une colonne quelque chose comme
change_date
pour garder une trace de l'ordre des révisions.D'un autre côté, si vous avez juste
revision
une propriété du document et qu'il indique "combien de fois le document a changé", alors j'opterais pour l'approche de verrouillage optimiste, quelque chose comme:Si cela met à jour 0 lignes, il y a eu une mise à jour intermédiaire et vous devez en informer l'utilisateur.
En général, essayez de garder votre solution aussi simple que possible. Dans ce cas par
update
déclaration plutôt qu'unselect
suivi d'uninsert
ouupdate
la source
SEQUENCE est garanti d'être unique, et votre cas d'utilisation semble applicable si votre nombre de documents n'est pas trop élevé (sinon vous avez beaucoup de séquences à gérer). Utilisez la clause RETURNING pour obtenir la valeur générée par la séquence. Par exemple, en utilisant 'A36' comme document_id:
La gestion des séquences devra être manipulée avec soin. Vous pouvez peut-être conserver un tableau séparé contenant les noms des documents et la séquence associée à celui-ci
document_id
à référencer lors de l'insertion / mise à jour dudocument_revisions
tableau.la source
Cela est souvent résolu avec un verrouillage optimiste:
Si la mise à jour renvoie 0 lignes mises à jour, vous avez manqué votre mise à jour car quelqu'un d'autre a déjà mis à jour la ligne.
la source
(Je suis venu à cette question en essayant de redécouvrir un article sur ce sujet. Maintenant que je l'ai trouvé, je le poste ici au cas où d'autres chercheraient une option alternative à la réponse actuellement choisie - fenêtrage avec
row_number()
)J'ai ce même cas d'utilisation. Pour chaque enregistrement inséré dans un projet spécifique dans notre SaaS, nous avons besoin d'un nombre incrémentiel unique qui peut être généré face à des
INSERT
s concurrents et est idéalement sans espace.Cet article décrit une belle solution , que je résumerai ici pour plus de facilité et de postérité.
document_id
etcounter
.counter
seraDEFAULT 0
Alternativement, si vous avez déjà unedocument
entité qui regroupe toutes les versions, uncounter
pourrait y être ajouté.BEFORE INSERT
déclencheur à ladocument_versions
table qui incrémente atomiquement le compteur (UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
), puis définitNEW.version
cette valeur de compteur.Alternativement, vous pourriez être en mesure d'utiliser un CTE pour le faire au niveau de la couche application (bien que je préfère que ce soit un déclencheur pour des raisons de cohérence):
Cela est similaire en principe à la façon dont vous tentiez de le résoudre initialement, sauf qu'en modifiant une ligne de compteur dans une seule instruction, il bloque les lectures de la valeur périmée jusqu'à ce que le
INSERT
soit validé.Voici une transcription
psql
montrant cela en action:Comme vous pouvez le voir, vous devez faire attention à la façon dont
INSERT
cela se produit, d'où la version de déclenchement, qui ressemble à ceci:Cela rend
INSERT
s beaucoup plus simple et l'intégrité des données plus robuste face àINSERT
s provenant de sources arbitraires:la source