」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 如何使用 Java 和 PostgreSQL 處理競爭條件

如何使用 Java 和 PostgreSQL 處理競爭條件

發佈於2024-08-01
瀏覽:195

How to deal with race conditions using Java and PostgreSQL

使用鎖來控制資料庫並發

想像一下,您正在開發一個電子商務系統,成千上萬的人試圖同時購買最後剩下的產品。然而,他們中的許多人可以繼續結帳並完成訂單。當您檢查庫存時,您的產品數量為負數。這是怎麼可能的,你該如何解決這個問題?

讓我們來編碼吧!您可能想到的第一件事是在結帳前檢查庫存。也許是這樣的:

public void validateAndDecreaseSolution(long productId, int quantity {
    Optional stockByProductId = 
 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)
Optional findStockByProductIdWithLock(Long productId);
public void validateAndDecreaseSolution1(long productId, int quantity) {
    Optional stockByProductId = 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();

    Optional stockByProductId = 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 的內容。

版本聲明 本文轉載於:https://dev.to/ramoncunha/how-to-deal-with-race-conditions-using-java-and-postgresql-4jk6?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何正確處理帶有空白邊界的 CSV 檔案?
    如何正確處理帶有空白邊界的 CSV 檔案?
    使用Scanner() 讀取CSV 問題使用Scanner() 讀取CSV 檔案時,通常會遇到文字包含空格的問題被移動到下一行。發生這種情況是因為 Scanner 遵循空格邊界。 Scanner() 用法中的 CSV 處理不正確提供的程式碼片段使用 Scanner() 讀取和處理 CSV 檔案。但是...
    程式設計 發佈於2024-12-21
  • 如何解決Hibernate中的LazyInitializationException?
    如何解決Hibernate中的LazyInitializationException?
    LazyInitializationException:無法從方法呼叫存取代理遇到「LazyInitializationException:無法初始化代理程式- 無會話」錯誤通常表示Hibernate 應用程式中的延遲載入問題。當您嘗試存取活動 Hibernate 會話範圍之外的延遲初始化的實體(例...
    程式設計 發佈於2024-12-21
  • 為什麼 Go 在套件中定義字串函數而不是方法?
    為什麼 Go 在套件中定義字串函數而不是方法?
    Go中基本類型的方法程式語言Go包含多種字串函數,例如ToUpper()和Split() 。與其他語言可能將這些函數定義為字串類型的方法不同,Go 將它們定義為 strings 套件的一部分。為什麼會這樣呢? 簡單性和靈活性根據Go 的創建者的說法,將方法保留在字符串等基本類型之外的主要原因之一是維...
    程式設計 發佈於2024-12-21
  • 大批
    大批
    方法是可以在物件上呼叫的 fns 數組是對象,因此它們在 JS 中也有方法。 slice(begin):將陣列的一部分提取到新數組中,而不改變原始數組。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index ...
    程式設計 發佈於2024-12-21
  • 將 Azure SQL 資料庫升級到 v12 後,為什麼會出現 TLS 握手錯誤?
    將 Azure SQL 資料庫升級到 v12 後,為什麼會出現 TLS 握手錯誤?
    v12 升級後Azure SQL 資料庫TLS 握手錯誤Azure SQL 資料庫實例升級到v12 後,您可能會遇到TLS 握手錯誤。當伺服器提供的憑證與用戶端連線字串中指定的主機名稱不符時,就會發生此錯誤。 錯誤詳細資料錯誤訊息通常表示憑證對於用戶端連線中使用的主機名稱以外的主機名稱有效。例如:T...
    程式設計 發佈於2024-12-21
  • 如何處理 SQL 資料庫中多個表的外鍵關係?
    如何處理 SQL 資料庫中多個表的外鍵關係?
    處理多個表的外鍵您有三個表:地區、國家和州。國家和國家可以屬於地區,形成等級結構。現在,您想要建立一個包含「region_id」和「popular_place_id」欄位的「popular_areas」表,並基於「popular_place_type」列在「popular_place_id」與國家或...
    程式設計 發佈於2024-12-21
  • Go 是否為 Goroutine 特定資料提供 ThreadLocal 等效項?
    Go 是否為 Goroutine 特定資料提供 ThreadLocal 等效項?
    了解Go 中的Goroutine-本地儲存在使用Go 時,開發者經常會遇到需要追蹤與特定Goroutine 相關的資訊的情況。在 Java 等其他語言中,ThreadLocal 為此任務提供了一個優雅的解決方案。 Go 是否提供類似的機制? Go 的 Goroutine 本地儲存方法Go 的標準庫不...
    程式設計 發佈於2024-12-21
  • Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta:列偏移的刪除和恢復Bootstrap 4 在其Beta 1 版本中引入了重大更改柱子偏移了。然而,隨著 Beta 2 的後續發布,這些變化已經逆轉。 從 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    程式設計 發佈於2024-12-21
  • 在 Go 中使用 WebSocket 進行即時通信
    在 Go 中使用 WebSocket 進行即時通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSocke...
    程式設計 發佈於2024-12-21
  • 如何使用 Prototype.js 實作自動調整文字區域大小的功能?
    如何使用 Prototype.js 實作自動調整文字區域大小的功能?
    使用Prototype 實現自動調整大小的TextArea要增強內部銷售應用程式中的使用者體驗,請考慮向用於送貨地址的文字區域。以下是實現此目的的詳細指南:目標是創建一個可以動態調整其高度以適應文字輸入的文字區域,確保最佳的空間利用率和可讀性。為此,我們將利用 JavaScript 框架 Proto...
    程式設計 發佈於2024-12-21
  • Spring Boot如何配置多個資料來源?
    Spring Boot如何配置多個資料來源?
    在Spring Boot中配置多個資料來源在Spring Boot中,使用多個資料來源可以讓你隔離不同實體的資料存取管理或應用程式。為了實現這一點,使用了 application.properties 檔案和 Bean 配置方法。 application.properties若要新增第二個資料來源,...
    程式設計 發佈於2024-12-21
  • 為什麼 C++ 中的零長度陣列會導致錯誤 2233,如何修復它?
    為什麼 C++ 中的零長度陣列會導致錯誤 2233,如何修復它?
    在 C 中處理「零長度數組」 在 C 中,在遺留程式碼中可能會遇到「零長度數組」的情況。這涉及包含長度為零的陣列的結構。雖然警告被編譯指示抑制,但創建包含此類數組的新結構可能會導致錯誤 2233。為什麼會發生這種情況,可以採取什麼措施來解決它? 使用零長度數組的原因是一個歷史原因允許動態分配數組的...
    程式設計 發佈於2024-12-21
  • 如何設定 HTML `` 標籤的樣式並確保其可見性?
    如何設定 HTML `` 標籤的樣式並確保其可見性?
    HTML標籤的樣式和可見性問題聲明在HTML中,標籤用來定義一個區域可以連結到另一個資源的圖像。然而,使用者在設計樣式並使這些區域始終可見方面遇到了困難。 jQuery 外掛解決方案克服這項挑戰的一種方法是利用 MapHilight jQuery 外掛程式。該插件提供了使用 CSS 懸停效果突出顯示...
    程式設計 發佈於2024-12-21
  • 存取類別中的資料庫物件時如何避免全域變數?
    存取類別中的資料庫物件時如何避免全域變數?
    在類別中使用全域變數建立分頁功能涉及從類別中存取資料庫物件。但是,嘗試存取類別內部的外部變數可能會導致錯誤。讓我們深入研究處理此問題的可能解決方案。 為了解決致命錯誤“在非物件上呼叫成員函數 query()”,資料庫物件需要在類別中可存取。不使用全域變量,更合適的方法是將資料庫物件注入到類別或其方法...
    程式設計 發佈於2024-12-21
  • 如何在C++中產生特定範圍內均勻分佈的隨機數?
    如何在C++中產生特定範圍內均勻分佈的隨機數?
    跨範圍均勻隨機數產生您尋求一種在指定範圍[min, max]內均勻產生隨機數的方法。 rand 的缺陷您目前使用 rand() 和模運算子的實作可能無法確保均勻分佈,因為它的行為取決於 RAND_MAX 和範圍本身。 C 11 和統一範圍產生在C 11 中,std::uniform_int_dis...
    程式設計 發佈於2024-12-21

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3