Stellen Sie sich vor, Sie arbeiten an einem E-Commerce-System und Tausende von Menschen versuchen gleichzeitig, das letzte verbleibende Produkt zu kaufen. Viele von ihnen könnten jedoch zur Kasse gehen und die Bestellung abschließen. Wenn Sie Ihren Lagerbestand überprüfen, haben Sie ein Produkt mit einer negativen Menge. Wie war das möglich und wie können Sie das Problem lösen?
Lass uns programmieren! Das erste, woran Sie vielleicht denken, ist, den Lagerbestand vor dem Bezahlen zu überprüfen. Vielleicht so etwas:
public void validateAndDecreaseSolution(long productId, int quantity { OptionalstockByProductId = stockRepository.findStockByProductId(productId); int stock = stockByProductId.orElseThrow().getStock(); int possibleStock = stock - quantity; if (stock Sie können diese Validierung verwenden, aber wenn wir von Hunderten, Tausenden, Millionen oder sogar Dutzenden von Anfragen pro Sekunde sprechen, wird diese Validierung nicht ausreichen. Wenn 10 Anfragen genau zur gleichen Zeit diesen Codeabschnitt erreichen und die Datenbank denselben Wert für stockByProductId zurückgibt, bricht Ihr Code zusammen. Sie benötigen eine Möglichkeit, andere Anfragen zu blockieren, während wir diese Überprüfung durchführen.
Erste Lösung - FÜR UPDATE
Fügen Sie eine Sperranweisung zu Ihrem SELECT hinzu. In diesem Beispiel habe ich dies mit FOR UPDATE mit Spring Data gemacht. Wie es in der PostgreSQL-Dokumentation heißt
FOR UPDATE bewirkt, dass die von der SELECT-Anweisung abgerufenen Zeilen wie für die Aktualisierung gesperrt werden. Dadurch wird verhindert, dass sie von anderen Transaktionen geändert oder gelöscht werden, bis die aktuelle Transaktion endet.
@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); } Alle Anfragen an die Lagerbestandstabelle unter Verwendung der Produkt-ID warten, bis die eigentliche Transaktion abgeschlossen ist. Ziel ist es, sicherzustellen, dass Sie den letzten aktualisierten Wert der Aktie erhalten.
Zweite Lösung – pg_advisory_xact_lock
Diese Lösung ähnelt der vorherigen, Sie können jedoch auswählen, was der Sperrschlüssel ist. Wir sperren die gesamte Transaktion, bis wir die gesamte Verarbeitung der Validierung und Bestandsreduzierung abgeschlossen haben.
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); } Die nächste Anfrage interagiert erst mit einem Produkt mit derselben ID, nachdem diese Transaktion beendet ist.
Dritte Lösung – WHERE-Klausel
In diesem Fall werden wir unsere Zeile oder Transaktion nicht sperren. Lassen wir zu, dass diese Transaktion bis zur Aktualisierungsanweisung fortgesetzt wird. Beachten Sie die letzte Bedingung: Lagerbestand > 0. Dadurch wird nicht zugelassen, dass unser Lagerbestand kleiner als Null ist. Wenn also zwei Personen gleichzeitig versuchen zu kaufen, erhält einer von ihnen eine Fehlermeldung, da unsere Datenbank keinen Lagerbestand
@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);Abschluss
Die erste und die zweite Lösung verwenden pessimistisches Sperren als Strategie. Das dritte ist das optimistische Sperren. Die pessimistische Sperrstrategie wird verwendet, wenn Sie einen restriktiven Zugriff auf eine Ressource wünschen, während Sie eine Aufgabe ausführen, die diese Ressource betrifft. Die Zielressource wird für jeden anderen Zugriff gesperrt, bis Sie Ihren Vorgang abschließen. Seien Sie vorsichtig mit Deadlocks!
Mit optimistischer Sperrung können Sie verschiedene Abfragen für dieselbe Ressource ohne Blockierung durchführen. Es wird verwendet, wenn Konflikte unwahrscheinlich sind. Normalerweise verfügen Sie über eine Version, die sich auf Ihre Zeile bezieht, und wenn Sie diese Zeile aktualisieren, vergleicht die Datenbank Ihre Zeilenversion mit der Zeilenversion in der Datenbank. Wenn beide gleich sind, ist die Änderung erfolgreich. Wenn nicht, müssen Sie es erneut versuchen. Wie Sie sehen, verwende ich in diesem Artikel keine Versionszeile, aber meine dritte Lösung blockiert keine Anfragen und steuert die Parallelität mithilfe der Bedingung „Bestand > 0“.
Wenn Sie den vollständigen Code sehen möchten, können Sie ihn auf meinem GitHub überprüfen.
Es gibt viele andere Strategien, um pessimistisches und optimistisches Sperren zu implementieren. Sie können beispielsweise mehr über FOR UPDATE WITH SKIP LOCKED suchen.
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3