Imaginez que vous travaillez sur un système de commerce électronique et que des milliers de personnes tentent d'acheter le dernier produit restant en même temps. Cependant, beaucoup d’entre eux ont pu passer à la caisse et finaliser la commande. Lorsque vous vérifiez votre stock, vous disposez d'un produit avec une quantité négative. Comment cela a-t-il été possible et comment pouvez-vous résoudre ce problème ?
Codons ! La première chose à laquelle vous pourriez penser est de vérifier le stock avant de passer à la caisse. Peut-être quelque chose comme ça :
public void validateAndDecreaseSolution(long productId, int quantity { OptionalstockByProductId = stockRepository.findStockByProductId(productId); int stock = stockByProductId.orElseThrow().getStock(); int possibleStock = stock - quantity; if (stock Vous pouvez utiliser cette validation, mais quand on parle de centaines, de milliers, de millions, voire de dizaines de requêtes par seconde, cette validation ne suffira pas. Lorsque 10 requêtes atteignent ce morceau de code exactement au même moment et que la base de données renvoie la même valeur pour stockByProductId, votre code sera interrompu. Vous avez besoin d'un moyen de bloquer les autres demandes pendant que nous effectuons cette vérification.
Première solution - POUR LA MISE À JOUR
Ajoutez une instruction de verrouillage sur votre SELECT. Dans cet exemple, j'ai fait cela en utilisant FOR UPDATE avec Spring Data. Comme le dit la documentation PostgreSQL
FOR UPDATE provoque le verrouillage des lignes récupérées par l'instruction SELECT comme pour une mise à jour. Cela empêche leur modification ou leur suppression par d'autres transactions jusqu'à la fin de la transaction en cours.
@Query(value = "SELECT * FROM stocks s WHERE s.product_id = ?1 FOR UPDATE", nativeQuery = true) OptionalfindStockByProductIdWithLock(Long productId); public void validateAndDecreaseSolution1(long productId, int quantity) { OptionalstockByProductId = stockRepository.findStockByProductIdWithLock(productId); // ... validate stockRepository.decreaseStock(productId, quantity); } Toutes les demandes adressées à la table des stocks utilisant l'ID de produit attendront la fin de la transaction réelle. L'objectif ici est de vous assurer d'obtenir la dernière valeur mise à jour du stock.
Deuxième solution - pg_advisory_xact_lock
Cette solution est similaire à la précédente, mais vous pouvez sélectionner quelle est la clé de verrouillage. Nous verrouillerons l'intégralité de la transaction jusqu'à ce que nous ayons terminé tout le traitement de validation et de décrémentation de stock.
public void acquireLockAndDecreaseSolution2(long productId, int quantity) { Query nativeQuery = entityManager.createNativeQuery("select pg_advisory_xact_lock(:lockId)"); nativeQuery.setParameter("lockId", productId); nativeQuery.getSingleResult(); OptionalstockByProductId = stockRepository.findStockByProductId(productId); // check stock and throws exception if it is necessary stockRepository.decreaseStock(productId, quantity); } La requête suivante n'interagira avec un produit portant le même identifiant qu'une fois cette transaction terminée.
Troisième solution - Clause WHERE
Dans ce cas, nous ne verrouillerons pas notre ligne ou notre transaction. Laissons cette transaction se poursuivre jusqu'à l'instruction de mise à jour. Notez la dernière condition : stock > 0. Cela ne permettra pas à notre stock d'être inférieur à zéro. Ainsi, si deux personnes tentent d'acheter en même temps, l'une d'elles recevra une erreur car notre base de données n'autorisera pas le stock
@Transactional @Modifying @Query(nativeQuery = true, value = "UPDATE stocks SET stock = stock - :quantity WHERE product_id = :productId AND stock > 0") int decreaseStockWhereQuantityGreaterThanZero(@Param("productId") Long productId, @Param("quantity") int quantity);Conclusion
Les première et deuxième solutions utilisent le verrouillage pessimiste comme stratégie. Le troisième est le verrouillage optimiste. La stratégie de verrouillage pessimiste est utilisée lorsque vous souhaitez restreindre l'accès à une ressource pendant que vous effectuez une tâche impliquant cette ressource. La ressource cible sera verrouillée pour tout autre accès jusqu'à ce que vous ayez terminé votre processus. Soyez prudent avec les blocages !
Avec le verrouillage optimiste, vous pouvez effectuer diverses requêtes sur la même ressource sans aucun blocage. Il est utilisé lorsque les conflits ne risquent pas de se produire. Habituellement, vous aurez une version liée à votre ligne, et lorsque vous mettrez à jour cette ligne, la base de données comparera la version de votre ligne avec la version de la ligne dans la base de données. Si les deux sont égaux, le changement sera réussi. Sinon, vous devez réessayer. Comme vous pouvez le voir, je n'utilise aucune ligne de version dans cet article, mais ma troisième solution ne bloque aucune requête et contrôle la simultanéité en utilisant la condition stock > 0.
Si vous souhaitez voir le code complet, vous pouvez consulter mon GitHub.
Il existe de nombreuses autres stratégies pour implémenter le verrouillage pessimiste et optimiste, vous pouvez en rechercher davantage sur FOR UPDATE WITH SKIP LOCKED par exemple.
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3