KoderKoder.ai
TarifsEntrepriseÉducationPour les investisseurs
Se connecterCommencer

Produit

TarifsEntreprisePour les investisseurs

Ressources

Contactez-nousSupportÉducationBlog

Légal

Politique de confidentialitéConditions d’utilisationSécuritéPolitique d’utilisation acceptableSignaler un abus

Réseaux sociaux

LinkedInTwitter
Koder.ai
Langue

© 2026 Koder.ai. Tous droits réservés.

Accueil›Blog›Conditions de concurrence dans les applications CRUD : causes et solutions pratiques
20 oct. 2025·8 min

Conditions de concurrence dans les applications CRUD : causes et solutions pratiques

Les conditions de concurrence dans les applications CRUD peuvent provoquer des commandes en double et des totaux erronés. Découvrez les points de collision fréquents et des solutions pratiques : contraintes, verrous et protections côté UX.

Conditions de concurrence dans les applications CRUD : causes et solutions pratiques

À quoi ressemble une condition de concurrence dans une application CRUD

Une condition de concurrence se produit lorsque deux (ou plusieurs) requêtes mettent à jour les mêmes données presque en même temps, et que le résultat final dépend du moment où elles arrivent. Chaque requête a l'air correcte prise séparément. Ensemble, elles produisent un mauvais résultat.

Un exemple simple : deux personnes cliquent sur Enregistrer sur la même fiche client en l'espace d'une seconde. L'une met à jour l'email, l'autre le numéro de téléphone. Si les deux requêtes envoient l'enregistrement complet, la deuxième écriture peut écraser la première, et une modification disparaît sans erreur.

On observe ça plus souvent dans les applications rapides parce que les utilisateurs peuvent déclencher plus d'actions par minute. Ça augmente aussi pendant les moments chargés : ventes flash, clôtures de fin de mois, une grosse campagne d'emailing, ou chaque fois qu'une pile de requêtes cible les mêmes lignes.

Les utilisateurs signalent rarement « une condition de concurrence ». Ils signalent des symptômes : commandes ou commentaires en double, mises à jour manquantes ("J'ai sauvegardé, mais c'est revenu en arrière"), totaux étranges (stock négatif, compteurs qui reculent), ou statuts qui basculent de façon inattendue (approuvé, puis de nouveau en attente).

Les réessais aggravent le problème. Les gens double-cliquent, rafraîchissent après une réponse lente, soumettent depuis deux onglets, ou ont des réseaux instables qui poussent le navigateur ou l'app à renvoyer la requête. Si le serveur traite chaque requête comme une écriture neuve, vous pouvez obtenir deux créations, deux paiements, ou deux changements de statut qui ne devaient arriver qu'une fois.

Pourquoi les conditions de concurrence arrivent plus souvent qu'on le croit

La plupart des applications CRUD semblent simples : lire une ligne, changer un champ, sauvegarder. Le piège, c'est que votre application ne contrôle pas le timing. La base de données, le réseau, les réessais, le travail en arrière-plan et le comportement des utilisateurs se chevauchent.

Un déclencheur fréquent est que deux personnes éditent la même fiche. Les deux chargent les mêmes valeurs « courantes », font des modifications valides, et la dernière sauvegarde écrase silencieusement la première. Personne n'a fait d'erreur, mais une mise à jour est perdue.

Ça arrive aussi avec une seule personne. Un double-clic sur un bouton Enregistrer, taper en arrière puis avant, ou une connexion lente qui pousse à appuyer à nouveau sur Envoyer peut envoyer la même écriture deux fois. Si le point d'API n'est pas idempotent, vous pouvez créer des doublons, facturer deux fois, ou avancer un statut de deux pas.

Les usages modernes ajoutent des recouvrements : plusieurs onglets ou appareils connectés au même compte peuvent faire des mises à jour conflictuelles. Des jobs en arrière-plan (emails, facturation, synchronisation, nettoyage) peuvent toucher les mêmes lignes que les requêtes web. Les réessais automatiques au niveau client, du load balancer ou du job runner peuvent répéter une requête qui a déjà réussi.

Si vous livrez des fonctionnalités rapidement, la même fiche est souvent mise à jour depuis plus d'endroits qu'on ne s'en souvient. Si vous utilisez un constructeur piloté par chat comme Koder.ai, l'application peut croître encore plus vite, donc il vaut mieux considérer la concurrence comme un comportement normal, pas comme un cas marginal.

Points de collision courants à surveiller

Les conditions de concurrence apparaissent rarement dans des démos « créer un enregistrement ». Elles apparaissent là où deux requêtes touchent la même vérité au même instant. Connaître les points chauds habituels vous aide à concevoir des écritures sûres dès le départ.

Compteurs et champs "numéro suivant"

Tout ce qui ressemble à "ajouter 1" peut casser sous charge : likes, compteurs de vues, totaux, numéros de facture, numéros de ticket. Le schéma risqué est lire la valeur, l'incrémenter, puis la réécrire. Deux requêtes peuvent lire la même valeur de départ et s'écraser mutuellement.

Transitions d'état

Des workflows comme Draft -> Submitted -> Approved -> Paid semblent simples, mais les collisions sont courantes. Le problème commence quand deux actions sont possibles en même temps (approuver et éditer, annuler et payer). Sans protections, vous pouvez obtenir un enregistrement qui saute des étapes, revient en arrière, ou affiche des états différents dans des tables différentes.

Traitez les changements d'état comme un contrat : autorisez seulement l'étape suivante valide et refusez le reste.

Inventaire, capacité et créneaux de réservation

Places restantes, quantités en stock, créneaux de rendez-vous et champs "capacité restante" créent le problème classique de survente. Deux acheteurs finalisent au même moment, voient la disponibilité, et réussissent tous les deux. Si la base de données n'est pas le juge final, vous finirez par vendre plus que ce que vous avez.

Unicité et règles du type "une seule chose active"

Certaines règles sont absolues : un email par compte, un abonnement actif par utilisateur, un panier ouvert par utilisateur. Elles échouent souvent quand vous vérifiez d'abord ("est-ce que ça existe ?") puis insérez. Sous concurrence, les deux requêtes peuvent passer la vérification.

Si vous générez des flux CRUD rapidement (par exemple en discutant votre app avec Koder.ai), notez ces points chauds tôt et soutenez-les par des contraintes et des écritures sûres, pas seulement des vérifications côté UI.

Doubles envois et requêtes répétées depuis l'UI

Beaucoup de conditions de concurrence commencent par quelque chose d'ennuyeux : la même action est envoyée deux fois. Les utilisateurs double-cliquent. Le réseau est lent, donc ils cliquent de nouveau. Un téléphone enregistre deux tapes. Parfois ce n'est pas intentionnel : la page se rafraîchit après un POST et le navigateur propose de renvoyer le formulaire.

Quand ça arrive, votre backend peut exécuter deux créations ou mises à jour en parallèle. Si les deux réussissent, vous obtenez des doublons, des totaux incorrects, ou un changement de statut appliqué deux fois (par exemple, approuver puis encore approuver). Ça paraît aléatoire parce que tout dépend du timing.

L'approche la plus sûre est la défense en profondeur. Corrigez l'UI, mais supposez que l'UI peut échouer.

Changements pratiques à appliquer à la plupart des flux d'écriture :

  • Désactiver l'envoi après le premier clic et afficher un état "en cours d'enregistrement" clair.
  • Rediriger après un POST réussi pour que le rafraîchissement ne renvoie pas le même formulaire.
  • Utiliser une clé d'idempotence : un jeton unique par action utilisateur que le serveur stocke et n'accepte qu'une seule fois.
  • Soutenir cela par une règle d'unicité en base de données pour que deux inserts identiques ne puissent pas gagner tous les deux.

Exemple : un utilisateur appuie deux fois sur "Payer la facture" sur mobile. L'UI doit bloquer le second appui. Le serveur doit aussi rejeter la deuxième requête lorsqu'il voit la même clé d'idempotence, et renvoyer le résultat de succès original au lieu de facturer deux fois.

Transitions d'état qui se désynchronisent

Les champs d'état semblent simples jusqu'à ce que deux choses essayent de les changer en même temps. Un utilisateur clique sur Approuver pendant qu'un job automatique marque le même enregistrement Expiré, ou deux membres de l'équipe travaillent le même élément dans des onglets différents. Les deux mises à jour peuvent réussir, mais le statut final dépend du timing, pas de vos règles.

Traitez l'état comme une petite machine à états. Gardez une table courte des mouvements autorisés (par exemple : Draft -> Submitted -> Approved, et Submitted -> Rejected). Ensuite, chaque écriture vérifie : "Ce mouvement est-il autorisé depuis le statut courant ?" Si non, rejetez plutôt que d'écraser silencieusement.

Le verrouillage optimiste vous aide à détecter les mises à jour obsolètes sans bloquer les autres utilisateurs. Ajoutez un numéro de version (ou updated_at) et exigez qu'il corresponde lors de la sauvegarde. Si quelqu'un d'autre a changé la ligne après que vous l'ayez chargée, votre mise à jour affectera zéro ligne et vous pouvez afficher un message clair comme "Cet élément a changé, rafraîchissez et réessayez."

Un schéma simple pour les mises à jour d'état :

  • Lire le statut courant et la version
  • Valider la transition
  • Mettre à jour avec une garde (WHERE status = ? AND version = ?)
  • Incrémenter la version

Aussi, centralisez les changements d'état. Si les mises à jour sont dispersées entre écrans, jobs en arrière-plan et webhooks, vous manquerez une règle. Mettez-les derrière une seule fonction ou un seul endpoint qui applique les mêmes vérifications à chaque fois.

Compteurs et totaux qui dérivent dans le temps

Relisez et prenez possession du code
Générez l'application, puis exportez le code source pour revoir les verrous et les limites transactionnelles.
Exporter le code

Le bug de compteur le plus courant paraît inoffensif : l'app lit une valeur, ajoute 1, puis la réécrit. Sous charge, deux requêtes peuvent lire le même nombre et écrire la même nouvelle valeur, si bien qu'un incrément est perdu. C'est facile à manquer parce que ça "fonctionne généralement" en test.

Préférez les mises à jour atomiques plutôt que lire puis écrire

Si une valeur est juste incrémentée ou décrémentée, laissez la base de données le faire en une seule instruction. La base garantira la sécurité même quand de multiples requêtes frappent en même temps.

UPDATE posts
SET like_count = like_count + 1
WHERE id = $1;

La même idée s'applique à l'inventaire, aux compteurs de vues, aux compteurs de réessai, et à tout ce qui s'exprime comme "new = old + delta".

Les totaux dérivent quand vous stockez ce que vous pourriez calculer

Les totaux se trompent souvent quand vous stockez un nombre dérivé (order_total, account_balance, project_hours) puis le mettez à jour depuis plusieurs endroits. Si vous pouvez calculer le total à partir des lignes sources (positions de commande, écritures de journal), vous évitez toute une classe de bugs de dérive.

Quand il faut garder un total pour des raisons de performance, traitez-le comme une écriture critique. Gardez les mises à jour des lignes sources et du total stocké dans la même transaction. Assurez-vous qu'un seul écrivain puisse modifier ce total à la fois (verrouillage, mises à jour gardées, ou chemin de propriétaire unique). Ajoutez des contraintes qui empêchent des valeurs impossibles (par exemple, inventaire négatif). Puis réconciliez occasionnellement avec un job en arrière-plan qui recalcule et signale les écarts.

Un exemple concret : deux utilisateurs ajoutent des articles au même panier en même temps. Si chaque requête lit cart_total, ajoute le prix de son article, puis le réécrit, une addition peut disparaître. Si vous mettez à jour les lignes du panier et le total du panier ensemble dans une transaction, le total reste correct même sous clics parallèles intensifs.

Évitez les collisions avec des contraintes en base de données en premier

Si vous voulez moins de conditions de concurrence, commencez par la base de données. Le code applicatif peut réessayer, expirer ou s'exécuter deux fois. Une contrainte en base est la porte finale qui reste correcte même lorsque deux requêtes frappent en même temps.

Les contraintes d'unicité empêchent les doublons qui "ne devraient jamais arriver" mais arrivent : adresses email, numéros de commande, identifiants de facture, ou une règle "un abonnement actif par utilisateur". Quand deux inscriptions arrivent simultanément, la base accepte une ligne et rejette l'autre.

Les clés étrangères empêchent les références cassées. Sans elles, une requête peut supprimer une ligne parent pendant qu'une autre crée une ligne enfant qui pointe vers rien, laissant des orphelins difficiles à nettoyer.

Les contraintes CHECK maintiennent les valeurs dans un intervalle sûr et imposent des règles d'état simples. Par exemple, quantity >= 0, rating entre 1 et 5, ou status limité à un ensemble autorisé.

Traitez les violations de contrainte comme des résultats attendus, pas comme des "erreurs serveur". Capturez les violations d'unicité, de clé étrangère et de check, renvoyez un message clair comme "Cet email est déjà utilisé", et consignez les détails pour le débogage sans divulguer d'internes.

Exemple : deux personnes cliquent sur "Créer commande" pendant un délai. Avec une contrainte unique sur (user_id, cart_id), vous n'avez pas deux commandes. Vous avez une commande et un rejet propre et explicable.

Utilisez des transactions et des verrous quand un changement doit l'emporter

Certaines écritures ne sont pas une seule instruction. Vous lisez une ligne, vérifiez une règle, mettez à jour un statut, et peut-être insérez un log d'audit. Si deux requêtes font cela en même temps, elles peuvent toutes deux passer la vérification et écrire. C'est le schéma classique de défaillance.

Encapsulez l'écriture multi-étapes dans une transaction afin que toutes les étapes réussissent ensemble ou échouent ensemble. Plus important, la transaction vous donne un endroit pour contrôler qui est autorisé à changer les mêmes données en même temps.

Quand un seul acteur peut éditer une ligne à la fois, utilisez un verrou de ligne. Par exemple : verrouillez la ligne de commande, confirmez qu'elle est toujours en "pending", puis passez-la en "approved" et écrivez l'entrée d'audit. La seconde requête attendra, puis reverra l'état et s'arrêtera.

Verrouillage optimiste vs pessimiste

Choisissez selon la fréquence des collisions :

  • Verrouillage optimiste : ajoutez un numéro de version (ou updated_at) et mettez à jour seulement s'il correspond.
  • Verrouillage pessimiste : verrouillez la ligne d'abord, puis lisez et mettez à jour dans la même transaction.

Gardez le temps de verrouillage court. Faites le moins de travail possible pendant que vous le détenez : pas d'appels API externes, pas de travail de fichier lent, pas de grandes boucles. Si vous construisez des flows dans un outil comme Koder.ai, maintenez la transaction juste pour les étapes en base, puis faites le reste après le commit.

Étape par étape : sécuriser un flux d'écriture de bout en bout

Arrêtez les surventes et la dérive
Créez un flux de commande et d'inventaire où le stock ne peut pas devenir négatif lors de paiements parallèles.
Créer le flux

Choisissez un flux qui peut coûter de l'argent ou de la confiance si une collision survient. Un flux courant est : créer une commande, réserver du stock, puis définir le statut de la commande sur confirmé.

Écrivez les étapes exactes que votre code effectue aujourd'hui, dans l'ordre. Soyez précis sur ce qui est lu, ce qui est écrit, et ce que signifie "succès". Les collisions se cachent dans le vide entre une lecture et une écriture ultérieure.

Un chemin de renforcement qui marche dans la plupart des stacks :

  • Définissez les mauvais résultats que vous devez bloquer (par exemple : stock négatif, ou deux réservations pour la même ligne de commande).
  • Ajoutez une règle en base qui rend ce résultat impossible (par exemple : une contrainte CHECK empêchant stock < 0, et une contrainte UNIQUE sur (order_id, sku) pour les réservations).
  • Enveloppez tout le flux dans une transaction, et verrouillez les lignes qui décident du résultat (par exemple : verrouillez la ligne d'inventaire produit avant de soustraire le stock).
  • Effectuez la mise à jour en une seule instruction quand c'est possible (par exemple : "update stock where stock >= qty" pour échouer proprement).
  • Gérez proprement les conflits : réessayez quelques fois, ou renvoyez un message clair "Rupture de stock, veuillez rafraîchir".

Ajoutez un test qui prouve la correction. Lancez deux requêtes en même temps contre le même produit et la même quantité. Vérifiez qu'une seule commande est confirmée, et que l'autre échoue de manière contrôlée (pas de stock négatif, pas de lignes de réservation en double).

Si vous générez des apps rapidement (y compris avec des plateformes comme Koder.ai), cette checklist vaut la peine pour les quelques chemins d'écriture qui comptent le plus.

Erreurs courantes qui maintiennent les conditions de concurrence

Une des causes majeures est de faire confiance à l'UI. Les boutons désactivés et les vérifications côté client aident, mais les utilisateurs peuvent double-cliquer, rafraîchir, ouvrir deux onglets ou rejouer une requête depuis une connexion instable. Si le serveur n'est pas idempotent, des doublons passent.

Un autre bug discret : vous attrapez une erreur de base (comme une violation d'unicité) mais vous continuez le flux quand même. Cela devient souvent "création échouée, mais on a quand même envoyé l'email" ou "paiement échoué, mais on a marqué la commande comme payée". Une fois que des effets secondaires sont déclenchés, c'est difficile à annuler.

Les transactions longues sont aussi un piège. Si vous gardez une transaction ouverte pendant que vous appelez un service d'email, un paiement ou une API tierce, vous conservez des verrous plus longtemps que nécessaire. Ça augmente les attentes, les timeouts et les blocages entre requêtes.

Mélanger jobs en arrière-plan et actions utilisateur sans une source de vérité unique crée un état en split-brain. Un job réessaie et met à jour une ligne pendant qu'un utilisateur l'édite, et maintenant les deux pensent être le dernier écrivain.

Quelques "solutions" qui ne règlent pas réellement le problème :

  • Prévention des doubles envois uniquement côté UI
  • Avaler une erreur et continuer comme si de rien n'était
  • Faire des appels réseau lents dans une transaction
  • Laisser jobs et requêtes utilisateur mettre à jour le même état différemment
  • Mettre à jour des compteurs avec read-modify-write au lieu d'opérations atomiques

Si vous construisez avec un outil chat-to-app comme Koder.ai, les mêmes règles s'appliquent : demandez des contraintes côté serveur et des limites transactionnelles claires, pas seulement de meilleures protections UI.

Vérifications rapides avant mise en production

Prototyper puis sécuriser
Utilisez le plan gratuit pour prototyper, puis ajoutez transactions et protections avant l'arrivée des utilisateurs réels.
Commencer gratuitement

Les conditions de concurrence apparaissent souvent seulement sous vraie charge. Un passage pré-livraison peut détecter les points de collision les plus courants sans réécriture complète.

Commencez par la base de données. Si quelque chose doit être unique (emails, numéros de facture, un abonnement actif par utilisateur), faites-en une vraie contrainte unique, pas une règle côté application "on vérifie d'abord". Ensuite assurez-vous que votre code s'attend à ce que la contrainte échoue parfois и renvoie une réponse claire et sûre.

Ensuite, regardez l'état. Tout changement d'état (Draft -> Submitted -> Approved) doit être validé par un ensemble explicite de transitions autorisées. Si deux requêtes essaient de déplacer la même ligne, la seconde doit être rejetée ou devenir un no-op, pas créer un état intermédiaire.

Une checklist pratique avant release :

  • Confirmez que des contraintes d'unicité existent pour chaque règle qui doit être unique, y compris les contraintes composites.
  • Assurez-vous que chaque endpoint d'écriture est sûr en cas de réessai (clés d'idempotence, IDs de requête ou idempotence naturelle).
  • Enveloppez les écritures multi-étapes dans une transaction, surtout quand vous lisez puis écrivez en fonction de la lecture.
  • Utilisez des verrous seulement quand un changement doit l'emporter (par exemple, une approbation par commande).
  • Consignez les signaux de conflit (erreurs de clé dupliquée, échecs de sérialisation, timeouts de verrou) et alertez sur les pics.

Si vous construisez des flows dans Koder.ai, prenez ces points comme critères d'acceptation : l'app générée doit échouer proprement sous réessais et en concurrence, pas seulement réussir le happy path.

Scénario d'exemple : empêcher une double approbation

Deux membres du personnel ouvrent la même demande d'achat. Les deux cliquent sur Approuver en quelques secondes. Les deux requêtes atteignent le serveur.

Ce qui peut mal tourner est confus : la demande est "approuvée" deux fois, deux notifications partent, et tout total lié aux approbations (budget utilisé, compte quotidien d'approbations) peut augmenter de 2. Les deux mises à jour sont valides séparément, mais elles entrent en collision.

Voici un plan de correction qui fonctionne bien avec une base de données de type PostgreSQL.

1) Rendre la double approbation impossible en base

Ajoutez une règle qui garantit qu'une seule ligne d'approbation peut exister pour une demande. Par exemple, stockez les approbations dans une table séparée et imposez une contrainte UNIQUE sur request_id. Ainsi, le second insert échoue même si le code applicatif comporte un bug.

2) Envelopper le changement d'état dans une transaction

Lors de l'approbation, faites toute la transition dans une seule transaction :

  • Verrouillez la ligne de demande (ou mettez à jour avec une condition comme WHERE status = 'pending').
  • Vérifiez qu'elle est toujours en pending.
  • Écrivez la ligne d'approbation.
  • Mettez à jour le statut de la demande en approved.

Si le second membre arrive en retard, il voit soit 0 lignes mises à jour, soit une erreur de contrainte d'unicité. Dans tous les cas, une seule modification l'emporte.

3) Fournir un retour UI clair

Après la correction, le premier utilisateur voit Approved et reçoit la confirmation normale. Le second voit un message convivial du type : "Cette demande a déjà été approuvée par quelqu'un d'autre. Rafraîchissez pour voir le statut le plus récent." Pas de notifications en double, pas d'échecs silencieux.

Si vous générez un flux CRUD sur une plateforme comme Koder.ai (backend Go avec PostgreSQL), vous pouvez intégrer ces vérifications dans l'action d'approbation une fois et réutiliser le modèle pour d'autres actions du type "un seul gagnant".

Étapes suivantes : intégrer les corrections contre la concurrence dans votre routine de développement

Les conditions de concurrence sont plus faciles à corriger lorsque vous les traitez comme une routine répétable, pas comme une chasse au bug ponctuelle. Concentrez-vous sur les quelques chemins d'écriture qui importent, et rendez-les ennuyeusement corrects avant de polir le reste.

Commencez par nommer vos principaux points de collision. Dans beaucoup d'apps CRUD, c'est le même trio : compteurs (likes, inventaire, soldes), changements d'état (Draft -> Submitted -> Approved), et doubles envois (double-clics, réessais, réseaux lents).

Une routine solide :

  • Écrivez la règle immuable pour chaque flux (par exemple : une commande ne peut être approuvée qu'une fois, et seulement depuis pending).
  • Ajoutez d'abord des contraintes en base (clés uniques, clés étrangères, contraintes CHECK) afin que la base refuse les états impossibles.
  • Ajoutez des transactions et des verrous seulement là où un changement doit l'emporter (par exemple verrouiller une ligne lors de l'approbation).
  • Ajoutez un petit test de concurrence par workflow critique, ciblant l'échec que vous craignez (deux approbations, deux incréments, deux envois de formulaire).
  • Revoyez le traitement des erreurs pour que l'UI affiche un message clair lorsque la base rejette une écriture conflictuelle.

Si vous construisez sur Koder.ai, Planning Mode est un bon endroit pour cartographier chaque flux d'écriture en étapes et règles avant de générer le code Go et PostgreSQL. Les snapshots et le rollback sont aussi utiles quand vous déployez de nouvelles contraintes ou comportements de verrou et voulez un retour rapide en cas de cas limites.

Avec le temps, cela devient une habitude : chaque nouvelle fonctionnalité d'écriture obtient une contrainte, un plan transactionnel et un test de concurrence. C'est ainsi que les conditions de concurrence dans les applications CRUD cessent d'être des surprises.

Sommaire
À quoi ressemble une condition de concurrence dans une application CRUDPourquoi les conditions de concurrence arrivent plus souvent qu'on le croitPoints de collision courants à surveillerDoubles envois et requêtes répétées depuis l'UITransitions d'état qui se désynchronisentCompteurs et totaux qui dérivent dans le tempsÉvitez les collisions avec des contraintes en base de données en premierUtilisez des transactions et des verrous quand un changement doit l'emporterÉtape par étape : sécuriser un flux d'écriture de bout en boutErreurs courantes qui maintiennent les conditions de concurrenceVérifications rapides avant mise en productionScénario d'exemple : empêcher une double approbationÉtapes suivantes : intégrer les corrections contre la concurrence dans votre routine de développement
Partager
Koder.ai
Créez votre propre app avec Koder aujourd'hui!

La meilleure façon de comprendre la puissance de Koder est de le voir par vous-même.

Commencer gratuitementRéserver une démo