78 votes

Django: Comment puis-je me protéger contre la modification concurrente des entrées de la base de données

S'il y a un moyen de se protéger contre les modifications concurrentes de la même entrée de base de données par deux utilisateurs ou plus ?

Il serait acceptable d'afficher un message d'erreur à l'utilisateur effectuant la deuxième opération de validation/enregistrement, mais les données ne doivent pas être écrasées silencieusement.

Je pense que verrouiller l'entrée n'est pas une option, car un utilisateur pourrait utiliser le bouton "Retour" ou tout simplement fermer son navigateur, laissant le verrouillage pour toujours.

47voto

Andrei Savu Points 2203

C'est ainsi que je fais le verrouillage optimiste dans Django :

updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\
          .update(updated_field=new_value, version=e.version+1)
if not updated:
    raise ConcurrentModificationException()

Le code ci-dessus peut être implémenté comme une méthode dans Gestionnaire Personnalisé.

Je fais les hypothèses suivantes :

  • filter().update() résultera en une seule requête à la base de données car filter est paresseux
  • une requête à la base de données est atomique

Ces hypothèses suffisent pour garantir qu'aucun autre utilisateur n'a mis à jour l'entrée avant. Si plusieurs lignes sont mises à jour de cette manière, vous devriez utiliser des transactions.

AVERTISSEMENT Documentation Django:

Sachez que la méthode update() est convertie directement en une commande SQL. Il s'agit d'une opération en masse pour les mises à jour directes. Elle n'exécute pas les méthodes save() sur vos modèles, ni n'émet les signaux pre_save ou post_save

36voto

giZm0 Points 730

Cette question est un peu ancienne et ma réponse un peu tardive, mais d'après ce que je comprends, cela a été résolu dans Django 1.4 en utilisant :

select_for_update(nowait=True)

voir la documentation

Renvoie un queryset qui verrouillera les lignes jusqu'à la fin de la transaction, en générant une déclaration SQL SELECT ... FOR UPDATE sur les bases de données prises en charge.

En général, si une autre transaction a déjà acquis un verrou sur l'une des lignes sélectionnées, la requête se bloquera jusqu'à ce que le verrou soit libéré. Si ce n'est pas le comportement que vous voulez, appelez select_for_update(nowait=True). Cela rendra l'appel non bloquant. Si un verrou en conflit est déjà acquis par une autre transaction, DatabaseError sera levée lors de l'évaluation du queryset.

Évidemment, cela ne fonctionnera que si le back-end prend en charge la fonction "select for update", ce qui n'est pas le cas par exemple de sqlite. Malheureusement : nowait=True n'est pas pris en charge par MySql, vous devez donc utiliser : nowait=False, ce qui bloquera uniquement jusqu'à ce que le verrou soit libéré.

28voto

Guillaume Points 8549

En fait, les transactions ne vous sont pas vraiment utiles ici ... sauf si vous voulez que des transactions s'exécutent sur plusieurs requêtes HTTP (ce que vous ne voulez probablement pas).

Ce que nous utilisons généralement dans ces cas, c'est le "Verrou optimiste". L'ORM Django ne prend pas en charge cela autant que je sache. Mais il y a eu des discussions sur l'ajout de cette fonctionnalité.

Donc, vous êtes seul. Fondamentalement, ce que vous devriez faire est d'ajouter un champ "version" à votre modèle et le transmettre à l'utilisateur sous forme de champ caché. Le cycle normal pour une mise à jour est :

  1. lire les données et les montrer à l'utilisateur
  2. l'utilisateur modifie les données
  3. l'utilisateur envoie les données
  4. l'application les enregistre dans la base de données.

Pour implémenter le verrou optimiste, lorsque vous enregistrez les données, vous vérifiez si la version que vous avez reçue de l'utilisateur est la même que celle dans la base de données, puis mettez à jour la base de données et incrémentez la version. Si elles ne le sont pas, cela signifie qu'il y a eu une modification depuis le chargement des données.

Vous pouvez le faire avec un seul appel SQL avec quelque chose comme :

UPDATE ... WHERE version = 'version_de_utilisateur';

Cet appel mettra à jour la base de données uniquement si la version est toujours la même.

3voto

Stijn Debrouwere Points 181

Pour référence future, consultez https://github.com/RobCombs/django-locking. Il effectue le verrouillage d'une manière qui ne laisse pas de verrous éternels, grâce à une combinaison de déverrouillage Javascript lorsque l'utilisateur quitte la page et de délais de verrouillage (par exemple, en cas de plantage du navigateur de l'utilisateur). La documentation est assez complète.

1voto

Lee B Points 1748

Vous devriez probablement utiliser le middleware de transaction django au moins, même indépendamment de ce problème.

Quant à votre problème réel d'avoir plusieurs utilisateurs éditant les mêmes données... oui, utilisez un verrouillage. OU:

Vérifiez contre quelle version un utilisateur met à jour (faites-le de manière sécurisée, afin que les utilisateurs ne puissent pas simplement pirater le système pour dire qu'ils mettaient à jour la dernière copie!), et mettez à jour uniquement si cette version est à jour. Sinon, renvoyez à l'utilisateur une nouvelle page avec la version originale qu'ils éditaient, leur version soumise, et les nouvelles versions écrites par d'autres. Demandez-leur de fusionner les modifications en une seule version complètement à jour. Vous pouvez essayer de fusionner automatiquement celles-ci en utilisant un ensemble d'outils comme diff+patch, mais vous devrez de toute façon avoir la méthode de fusion manuelle opérationnelle pour les cas d'échec, alors commencez par là. De plus, vous devrez préserver l'historique des versions et permettre aux administrateurs de revenir en arrière sur les modifications, au cas où quelqu'un perturberait involontairement ou intentionnellement la fusion. Mais vous devriez probablement avoir cela de toute façon.

Il y a très probablement une application/bibliothèque django qui fait la plupart de cela pour vous.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X