Maison  >  Article  >  Java  >  Comment gérer les conditions de concurrence avec Java et PostgreSQL

Comment gérer les conditions de concurrence avec Java et PostgreSQL

WBOY
WBOYoriginal
2024-07-18 10:15:30985parcourir

How to deal with race conditions using Java and PostgreSQL

Utiliser le verrouillage pour contrôler la simultanéité des bases de données

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 est-ce 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 {
    Optional<StockEntity> stockByProductId = 
 stockRepository.findStockByProductId(productId);

    int stock = stockByProductId.orElseThrow().getStock();
    int possibleStock = stock - quantity;

    if (stock <= 0 || possibleStock < 0) {
        throw new OutOfStockException("Out of stock");
    }

    stockRepository.decreaseStock(productId, quantity);
}

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 qu'ils soient modifiés ou supprimés 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)
Optional<StockEntity> findStockByProductIdWithLock(Long productId);
public void validateAndDecreaseSolution1(long productId, int quantity) {
    Optional<StockEntity> stockByProductId = stockRepository.findStockByProductIdWithLock(productId);

    // ... validate

    stockRepository.decreaseStock(productId, quantity);
}

Toutes les demandes de table de 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();

    Optional<StockEntity> stockByProductId = 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 <= -1.

@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 demande et contrôle la simultanéité à l'aide du stock > 0 état.

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.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn