J'essaye de construire une application de pyramide. J'ai commencé avec l'échafaudage SQLAlchemy. Je me heurte à un problème et je me demande quel est le meilleur moyen d'y remédier. Dans l'une de mes vues, j'ai besoin de sélectionner beaucoup de lignes à partir de deux tables non liées. Je dois m'assurer qu'aucune ligne n'a été insérée dans la deuxième table entre le moment où j'ai sélectionné les lignes de la première table et le moment où j'ai sélectionné les lignes de la deuxième table. J'ai trois modèles, Node
, Test
et Tasking
. Les deux Nodes
et Tests
ont un peu de métadonnées. Compte tenu d'une liste de Nodes
et une liste de Tests
, une liste globale de Taskings
peut être créée. Par exemple, nous pourrions avoir trois Nodes
, a
, b
et c
et deux Tests
"nous avons besoin d'un nœud pour faire la tâche P
" et "nous avons besoin de deux nœuds pour faire la tâche Q
".Comment gérer l'intégrité de plusieurs tables sur plusieurs sélections dans SQLAlchemy dans Pyramid?
À partir de ces informations, trois Tasks
doivent être créés. Par exemple:
- "Noeud
a
devrait faire la tâcheP
" - "Noeud
b
doit faire la tâcheQ
" - "Noeud
c
devrait faire la tâcheQ
"
Maintenant, je suis essayer de fournir une API REST pour cela. La grande majorité des clients demandera la liste Tasks
, ce qui doit être rapide. Cependant, parfois un client peut ajouter un Node
ou un Test
. Lorsque cela se produit, j'ai besoin de régénérer la liste entière de Tasks
.
Voici un exemple grossier:
@view_config(route_name='list_taskings')
def list_taskings(request):
return DBSession.Query(Tasking).all()
@view_config(route_name='add_node')
def add_node(request):
DBSession.add(Node())
_update_taskings()
@view_config(route_name='add_test')
def add_test(request):
DBSession.add(Test())
_update_taskings()
def _update_taskings():
nodes = DBSession.query(Node).all()
tests = DBSession.query(Test).all()
# Process...
Tasking.query.delete()
for t in taskings:
DBSession.add(t)
J'utilise la valeur par défaut Pyramide échafaudage SQLAlchemy. Ainsi, chaque requête démarre automatiquement une transaction. Donc, si _update_tasking
est appelée à partir d'une requête (disons add_node
), alors le nouveau nœud sera ajouté au DBSession
local, et l'interrogation de tous les Nodes
et Tests
dans _update_tasking
retournera ce nouvel élément. En outre, la suppression de tous les Taskings
existants et l'ajout de ceux nouvellement calculés est également sans danger.
J'ai deux problèmes:
Ce qui se passe si une nouvelle ligne est ajoutée dans la table
Tests
entre le moment où je reçois ma liste denodes
et ma liste detests
à_update_taskings
? Dans mon système de production réel, ces sélections sont proches les unes des autres, mais pas les unes à côté des autres. Il y a la possibilité d'une condition de course. Comment est-ce que je m'assure que deux demandes qui mettront à jour leTaskings
ne se remplacent pas l'un l'autre? Par exemple, imaginez si notre système existant en avait unNode
et unTest
. Deux demandes arrivent en même temps, l'une pour ajouter unNode
et l'autre pour ajouter unTest
.Même si le problème n ° 1 n'était pas un problème et que je savais que la paire de sélections de chaque requête représentait "une seule instance de temps dans la base de données", il y a toujours le problème d'une requête écrasant l'autre. Si la première demande se termine d'abord avec maintenant deuxNodes
et unTest
, la deuxième demande sera toujours à sélectionner les anciennes données (potentiellement) et générer une liste deTaskings
avec unNode
et deuxTests
.
Alors, quelle est la meilleure façon de gérer cela? J'utilise SQLite pour le développement et PostgreSQL en production, mais je voudrais une solution agnostique de base de données. Je ne suis pas inquiet pour les autres applications accédant à cette base de données. Mon API REST sera le seul mécanisme d'accès. Devrais-je mettre un verrou autour des demandes qui modifient la base de données (en ajoutant un Node
ou un Test
)? Dois-je verrouiller la base de données en quelque sorte?
Merci pour toute aide!
Merci, c'est exactement ce dont j'avais besoin. Bien que ce ne soit pas 100% joli, sachant que le verrouillage se passe au niveau de la base de données et qu'il est intelligent sur ce qui est considéré comme une erreur de sérialisation. J'ai un problème cependant. J'utilise l'échafaudage Pyramid qui utilise pyramid_tm. De plus, ma session SQLAlchemy est une scoped_session. Je ne peux pas .commit() (bien que je pense que .flush() fasse la même chose dans ce cas particulier), et si .rollback() alors je ne peux pas réessayer la transaction: "ResourceClosedError: Cette transaction est fermée" . Je vais google autour d'un peu. Merci! – jmacdonagh
J'ai mis à jour la réponse pour couvrir le cas du middleware de transaction. – rkhayrov
J'ai posté mon précédent message "pas 100% joli" avant la mise à jour impressionnante que vous avez faite. L'idée tween est parfaite. Ça découpe bien tout. Parfait! Y a-t-il une raison pour laquelle vous déclenchez une SerializationConflictError plutôt qu'une simple TransientError? – jmacdonagh