想像一下,您正在開發一個電子商務系統,成千上萬的人試圖同時購買最後剩下的產品。然而,他們中的許多人可以繼續結帳並完成訂單。當您檢查庫存時,您的產品數量為負數。這是怎麼可能的,你該如何解決這個問題?
讓我們來編碼吧!您可能想到的第一件事是在結帳前檢查庫存。也許是這樣的:
public void validateAndDecreaseSolution(long productId, int quantity { OptionalstockByProductId = stockRepository.findStockByProductId(productId); int stock = stockByProductId.orElseThrow().getStock(); int possibleStock = stock - quantity; if (stock 您可以使用此驗證,但是當我們談論每秒數百、數千、數百萬甚至數十個請求時,此驗證是不夠的。當 10 個請求同時到達這段程式碼並且資料庫為 stockByProductId 傳回相同的值時,您的程式碼將會崩潰。在我們進行此驗證時,您需要一種方法來阻止其他請求。
第一個解決方案 - 用於更新
在 SELECT 上新增鎖定語句。在此範例中,我使用 Spring Data 的 FOR UPDATE 來完成此操作。正如 PostgreSQL 文件所述
FOR UPDATE 導致 SELECT 語句檢索的行被鎖定,就像要更新一樣。這可以防止它們被其他事務修改或刪除,直到目前事務結束。
@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); } 所有使用產品ID對stocks表的請求都會等待,直到實際事務完成。這裡的目標是確保您獲得股票的最新更新價值。
第二個解決方案 - pg_advisory_xact_lock
此解決方案與上一個類似,但您可以選擇鎖定鍵是什麼。我們將鎖定整個交易,直到完成所有驗證和庫存減量的處理。
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); } 本次交易結束後,下一次請求只會與同ID的商品互動。
第三種解決方案 - WHERE 子句
在這種情況下,我們不會鎖定行或事務。讓我們允許此事務繼續進行,直到更新語句為止。請注意最後一個條件:庫存 > 0。這將不允許我們的庫存小於零。因此,如果兩個人嘗試同時購買,其中一個人會收到錯誤,因為我們的資料庫不允許庫存
@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);結論
第一個和第二個解決方案使用悲觀鎖定作為策略。第三是樂觀鎖。當您在執行涉及某個資源的任何任務時希望限制對該資源的存取時,可以使用悲觀鎖定策略。在您完成進程之前,目標資源將被鎖定以進行任何其他存取。小心死鎖!
使用樂觀鎖,您可以對相同資源執行各種查詢,而不會出現任何阻塞。當衝突不太可能發生時使用它。通常,您會有一個與您的行相關的版本,當您更新該行時,資料庫會將您的行版本與資料庫中的行版本進行比較。如果兩者相等,則變更將成功。如果沒有,您必須重試。正如您所看到的,我在本文中沒有使用任何版本行,但我的第三個解決方案不會阻止任何請求並使用 stock > 0 條件控制並發性。
如果想看完整的程式碼,可以查看我的GitHub。
還有許多其他策略來實現悲觀鎖定和樂觀鎖定,例如您可以搜尋更多有關 FOR UPDATE WITH SKIP LOCKED 的內容。
免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。
Copyright© 2022 湘ICP备2022001581号-3