msql的樂觀鎖和冪等性問題解決方案

目錄

1、介紹

2、樂觀鎖

2.1、核心思想

2.2、實現方式

1. 使用?version?字段(推薦)

2. 使用?timestamp?字段

2.3、如何處理沖突

2.4、樂觀鎖局限性

3、冪等性

3.1、什么是冪等性

3.2、樂觀鎖與冪等性的關系

1. 樂觀鎖如何輔助冪等性?

2. 樂觀鎖的局限性

3.3、如何設計

3.4、order_no?添加唯一約束

1、防止重復創建訂單

2、保證業務邏輯的正確性

3、支持冪等性設計

4、節點故障場景

4.1. 數據庫層面

1、事務的原子性

2、自動提交(Autocommit)

4.2. 應用層

1、重試機制

2、冪等性設計

3、異步消息隊列

4.3. 網絡恢復后的處理

1、客戶端檢測網絡狀態

2、服務端日志與監控


1、介紹

????????在分布式系統中,樂觀鎖冪等性設計數據插入失敗處理是保障數據一致性和系統可靠性的三大核心機制,它們共同協作以解決并發沖突、重復請求和網絡異常等問題。

1.樂觀鎖
????????通過在數據庫中添加?version?或?timestamp?字段,確保并發更新時的數據一致性。每次更新時檢查版本號是否匹配。

????????若匹配則更新并遞增版本號,否則拋出異常(如?StaleObjectStateException)。適用于讀多寫少的場景,減少鎖競爭,但需業務層配合處理沖突重試。

2.冪等性設計
????????確保同一請求多次執行的結果與一次執行相同,常用于支付、訂單等關鍵業務。通過?唯一業務標識符(如訂單號)請求ID數據庫唯一約束?或?緩存記錄?來攔截重復請求。例如,插入訂單前先檢查?order_no?是否已存在,若存在則直接返回結果,避免重復操作。

3.數據插入失敗的處理

網絡宕機

????????若插入操作未提交,數據庫事務會自動回滾;若已提交部分數據,需通過補償機制(如回滾或修復)修正。

重試機制

????????在網絡恢復后,客戶端可結合?指數退避算法?重試請求,但需確保重試操作是冪等的(如通過唯一約束或請求ID)。

異步隊列

????????將請求放入消息隊列(如 Kafka、RabbitMQ),確保網絡中斷時消息不丟失,恢復后繼續處理。

典型場景示例

? ? ? ? 用戶提交支付請求時,系統通過?order_no?的唯一約束防止重復訂單,使用樂觀鎖避免并發修改價格,若網絡中斷則通過重試機制重新提交(但依賴冪等性設計避免重復扣款)。

核心目標

? ? ????通過?樂觀鎖?保證數據一致性,冪等性?防止重復操作,重試與補償?應對網絡異常,三者結合構建高可用、可靠的分布式系統。


2、樂觀鎖

??????????MySQL的樂觀鎖是一種并發控制機制,它假設數據沖突(多個事務同時修改同一數據)的概率較低,因此在讀取數據時不加鎖,而是在更新時檢查數據是否被其他事務修改過。如果沖突發生,事務會失敗并重試。

2.1、核心思想

  1. 讀取數據時:記錄數據的版本號(或時間戳)。
  2. 更新數據時:檢查版本號是否一致,如果一致則更新,否則拋出異常(沖突)。

2.2、實現方式

在MySQL中,樂觀鎖通常通過以下方式實現:

1. 使用?version?字段(推薦)

  • 在表中添加一個?version?字段(整數類型),每次更新時自動遞增。
  • 讀取數據時獲取當前?version?值。
  • 更新時將?version?作為條件,如果匹配則更新并遞增?version

示例表結構

CREATE TABLE product (id INT PRIMARY KEY,name VARCHAR(50),price DECIMAL(10,2),version INT DEFAULT 0  -- 樂觀鎖版本號
);

示例操作

1.讀取數據

SELECT id, name, price, version FROM product WHERE id = 1;
-- 假設返回: id=1, name='Apple', price=10.00, version=5

2.更新數據(帶版本號檢查):

UPDATE product 
SET price = 12.00, version = version + 1 
WHERE id = 1 AND version = 5;

3.判斷是否更新成功

    ????????如果?version=5?的記錄還存在,則更新成功。

    ????????如果?version?已經被其他事務修改為?6,則更新失敗(影響行數為0),此時需要拋出異常或重試。

    2. 使用?timestamp?字段

    • 類似?version?字段,但使用?TIMESTAMP?或?DATETIME?類型。
    • 每次更新時自動更新該字段。
    • 更新時檢查?timestamp?是否匹配。

    1.讀取數據

    SELECT id, name, price, update_time FROM product WHERE id = 1;
    -- 假設返回: id=1, name='Apple', price=10.00, update_time='2023-10-01 12:00:00'
    

    2.更新數據(帶時間戳檢查):

    UPDATE product 
    SET price = 12.00, update_time = NOW() 
    WHERE id = 1 AND update_time = '2023-10-01 12:00:00';
    

    3.判斷是否更新成功

      ????????如果?update_time?匹配,則更新成功。

      ????????否則更新失敗(影響行數為0)。

      2.3、如何處理沖突

      當樂觀鎖檢測到沖突時(更新失敗),應用程序需要:

      1. 拋出異常(如?StaleObjectStateException)。
      2. 重試邏輯:重新讀取數據,重新嘗試更新(可能需要限制重試次數)。

      代碼示例(Java)

      int retryCount = 0;
      while (retryCount < MAX_RETRIES) {Product product = getProductFromDatabase(productId); // 包含 versionproduct.setPrice(newPrice);int rowsUpdated = updateProductInDatabase(product); // 使用 version 條件更新if (rowsUpdated == 1) {break; // 更新成功} else {retryCount++;// 可能需要等待一段時間再重試}
      }
      if (retryCount >= MAX_RETRIES) {throw new RuntimeException("樂觀鎖重試失敗");
      }
      

      小結:

      ????????它適用于讀多寫少沖突概率低的場景,能有效提高并發性能,但需要業務層配合實現沖突處理邏輯。

      如下圖所示:

      對比于悲觀鎖

      2.4、樂觀鎖局限性

      1. 需要業務層配合:必須顯式實現版本號檢查和重試邏輯。

      2. 無法完全避免沖突:在極端高并發下仍可能發生沖突。

      3. 不適合復雜事務:如果事務涉及多個表,樂觀鎖可能難以維護一致性。


      3、冪等性

      通過上面對于樂觀鎖的介紹,感覺是不是可以作為冪等性的處理手段呢?

      ????????樂觀鎖可以作為處理冪等性問題的一種手段,但它的作用和適用范圍需要結合具體場景來看。

      3.1、什么是冪等性

      冪等性(Idempotency)是指同一個操作多次執行的結果與執行一次的結果相同

      例如:

      • 發送重復的支付請求,不會導致重復扣款。
      • 提交重復的訂單,不會生成多個訂單。
      • 更新資源時,多次相同請求不會改變最終狀態。

      冪等性設計的核心目標:防止因網絡重傳、用戶重復點擊、系統故障等原因導致的重復請求對業務邏輯產生副作用。

      3.2、樂觀鎖與冪等性的關系

      ????????樂觀鎖(Optimistic Locking)主要用于解決并發更新時的數據一致性問題,而冪等性解決的是重復請求對業務邏輯的影響。兩者的結合可以增強系統的健壯性。

      1. 樂觀鎖如何輔助冪等性?

      ????????樂觀鎖通過?版本號(version)或時間戳(timestamp)?保證數據更新的原子性,防止并發沖突。

      在某些場景下,它可以間接支持冪等性:

      • 場景:更新某個資源時,重復的請求可能因版本號不匹配而失敗,避免重復操作。
      • 示例:用戶多次提交更新請求,若第一次請求已修改了數據版本號,后續重復請求會因版本號不一致而失敗,從而避免重復操作。

      2. 樂觀鎖的局限性

      ????????樂觀鎖無法直接解決冪等性問題,因為它不處理“重復請求”的識別和過濾。

      例如:

      • 如果用戶多次提交相同的請求參數(如相同的訂單號、交易號),樂觀鎖無法識別這是重復請求,只會檢查版本號是否沖突。
      • 如果請求參數不同(如不同的版本號),樂觀鎖可能允許更新,但業務邏輯可能需要拒絕重復操作。

      3.3、如何設計

      在實際開發中,通常需要將樂觀鎖與其他冪等性策略結合使用,例如:

      1. 唯一業務標識符(Business Key)
      2. 請求ID(Request ID)
      3. 數據庫唯一約束
      4. 緩存記錄已處理的請求

      示例:支付接口的冪等性設計

      假設用戶發起支付請求,接口需要確保同一筆訂單不會被重復扣款:

      -- 表結構
      CREATE TABLE orders (id INT PRIMARY KEY,order_no VARCHAR(50) UNIQUE,  -- 唯一業務標識符amount DECIMAL(10,2),status VARCHAR(20),version INT DEFAULT 0  -- 樂觀鎖版本號
      );
      

      處理流程

      1. 客戶端發送請求,包含?order_no?和?request_id(唯一請求ID)。
      2. 服務端處理
        • 檢查緩存或數據庫,是否存在已處理的?order_no?或?request_id
          • 如果存在,直接返回結果(冪等性保障)。
          • 如果不存在,繼續處理。
        • 執行支付操作時,使用樂觀鎖更新訂單狀態:
      UPDATE orders 
      SET status = 'PAID', version = version + 1 
      WHERE id = ? AND version = ?;
      

      如果更新失敗(版本號不匹配),說明訂單狀態已被其他事務修改,需重試或報錯。

      關鍵點

      • 唯一業務標識符(order_no):直接過濾重復請求。
      • 請求ID(request_id):記錄已處理的請求,避免重復消費。
      • 樂觀鎖(version):防止并發更新導致的數據不一致。

      3.4、order_no?添加唯一約束

      1、防止重復創建訂單

      ????????假設用戶點擊“提交訂單”按鈕多次,或網絡重傳導致相同請求被多次發送。如果沒有唯一約束,可能會導致以下問題:

      • 重復插入訂單:系統生成多個相同?order_no?的訂單,浪費資源。
      • 業務邏輯混亂:例如,重復扣款、重復發貨等。
      -- 假設沒有唯一約束
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
      -- 用戶重復提交相同訂單號
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 會成功插入第二條數據!
      

      后果:系統會認為這是兩個不同的訂單,可能導致重復扣款、庫存異常等問題。


      2、保證業務邏輯的正確性

      order_no?是業務的核心標識符,如果允許重復,會導致:

      • 數據不一致:無法通過?order_no?準確查詢或修改訂單。
      • 冪等性失效:重復請求無法被攔截,破壞系統的一致性。

      示例

      -- 有唯一約束后,第二次插入會失敗
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 成功
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100); -- 報錯:Duplicate entry
      

      3、支持冪等性設計

      唯一約束是實現冪等性的關鍵手段之一:

      • 冪等性:同一請求多次執行的結果與執行一次的結果相同。
      • 唯一約束:通過數據庫層強制攔截重復請求,避免業務邏輯重復執行。

      示例

      -- 用戶多次提交相同的訂單號
      BEGIN TRANSACTION;-- 嘗試插入訂單INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
      COMMIT;-- 如果已經存在相同 order_no,會拋出異常,事務回滾,避免重復操作
      

      4、節點故障場景

      ????????在插入數據的過程中如果發生網絡宕機,處理方式取決于數據庫的事務機制應用層的容錯設計以及網絡恢復后的重試策略

      以下是詳細的分析和解決方案:

      4.1. 數據庫層面

      1、事務的原子性

      • 如果插入操作被包裹在事務中(例如使用?BEGIN TRANSACTION?和?COMMIT),且數據庫支持事務(如 MySQL 的 InnoDB 引擎):
        • 網絡中斷時:事務未提交,數據庫會自動回滾未提交的更改。
        • 恢復后:需要重新發送插入請求。
        • 示例(MySQL)
      BEGIN;
      INSERT INTO orders (order_no, amount) VALUES ('20231001-001', 100);
      -- 網絡中斷,事務未提交,數據不會寫入數據庫
      

      2、自動提交(Autocommit)

      • 如果數據庫處于自動提交模式(默認開啟),每次插入操作會立即提交:
        • 網絡中斷時:可能已部分提交數據(如部分字段寫入),導致數據不一致。
        • 解決方案:在應用層顯式關閉自動提交,手動控制事務邊界。

      4.2. 應用層

      1、重試機制

      • 重試邏輯:在網絡恢復后,客戶端可以重試插入請求。
        • 關鍵點:需確保重試操作是冪等的(見下文)。
      • 重試策略
        • 指數退避(Exponential Backoff):重試間隔逐漸增大(如 1s → 2s → 4s → ...),避免網絡擁塞。
        • 最大重試次數限制:防止無限循環重試(如最多重試 3 次)。

      2、冪等性設計

      • 唯一約束:通過數據庫的?UNIQUE?約束(如訂單號?order_no)防止重復插入。
        • 示例:即使重試,只要?order_no?唯一,重復插入會失敗,避免數據冗余。
      • 請求 ID(Request ID):為每個請求生成唯一 ID,記錄已處理的請求。
        • 示例:在插入前檢查請求 ID 是否已存在,若存在則直接返回結果。

      3、異步消息隊列

      • 可靠性隊列:將插入操作放入消息隊列(如 Kafka、RabbitMQ),確保網絡中斷時消息不丟失。
        • 生產者:將插入請求發送到隊列,即使網絡中斷,消息仍保留在隊列中。
        • 消費者:網絡恢復后,繼續消費消息并執行插入操作。
        • 優點:解耦生產與消費,提高系統魯棒性。

      4.3. 網絡恢復后的處理

      1、客戶端檢測網絡狀態

      • 心跳機制:客戶端定期檢測與數據庫的連接狀態。
      • 自動重連:網絡恢復后,客戶端自動重新建立連接并重試未完成的請求。

      2、服務端日志與監控

      • 記錄失敗請求:在服務端記錄失敗的插入請求(如日志或數據庫表),便于人工介入處理。
      • 告警通知:通過監控工具(如 Prometheus、Zabbix)檢測異常,及時通知運維人員。

      總結:

      本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
      如若轉載,請注明出處:http://www.pswp.cn/bicheng/82611.shtml
      繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82611.shtml
      英文地址,請注明出處:http://en.pswp.cn/bicheng/82611.shtml

      如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

      相關文章

      成都鼎訊--通信信號模擬設備?

      在現代電磁通信領域&#xff0c;精準模擬復雜多變的信號環境&#xff0c;是提升通信裝備性能與人員作戰能力的核心命題。成都鼎訊以技術創新為驅動&#xff0c;憑借深厚的研發實力&#xff0c;重磅推出通信信號模擬設備&#xff0c;以前所未有的強大功能與卓越性能&#xff0c;…

      C# Windows Forms應用程序-003

      目錄 項目結構 命名空間和類定義 主要控件 GroupBox 控件 Label 控件 TextBox 控件 Button 控件 OpenFileDialog 控件 方法說明 構造函數 Form1() Dispose(bool disposing) Main() InitializeComponent() button1_Click(object sender, System.EventArgs e) but…

      【C/C++】死鎖的四大條件與預防策略詳解

      文章目錄 死鎖的四大條件與預防策略詳解一、死鎖的產生條件&#xff08;四個必要條件&#xff09;二、代碼示例三、死鎖的預防手段&#xff08;以 C/C 為例&#xff09;1. 破壞“循環等待” —— 統一加鎖順序&#xff08;推薦&#xff09;2. 使用 std::lock 一次性加多個鎖3. …

      Rust編程環境安裝

      文章目錄 Rust編程環境安裝一、安裝準備二、安裝步驟對于Linux/macOS用戶對于Windows用戶 三、驗證安裝四、環境配置檢查五、工具鏈管理六、附加功能七、常見問題處理八、編輯器支持九、其他 Rust編程環境安裝 一、安裝準備 1. 支持系統&#xff1a;Windows/Linux/macOS 2. 所…

      OpenHarmony平臺驅動使用(五),HDMI

      OpenHarmony平臺驅動使用&#xff08;五&#xff09; HDMI 概述 功能簡介 HDMI&#xff08;High Definition Multimedia Interface&#xff09;&#xff0c;即高清多媒體接口&#xff0c;主要用于DVD、機頂盒等音視頻Source到TV、顯示器等Sink設備的傳輸。 HDMI以主從方式工…

      【Git】Commit Hash vs Change-Id

      文章目錄 1、Commit 號2、Change-Id 號3、區別與聯系4、實際場景示例5、為什么需要兩者&#xff1f;6、總結附錄——Gerrit 在 Git 和代碼審查工具&#xff08;如 Gerrit&#xff09;中&#xff0c;Commit 號&#xff08;Commit Hash&#xff09; 和 Change-Id 號 是兩個不同的…

      leetcode hot100刷題日記——21.不同路徑

      和20題一樣的思路link 題解&#xff1a; class Solution { public:int dfs(int i,int j,vector<vector<int>>&memo){//超過了邊界&#xff0c;return 0if(i<0||j<0){return 0;}//從&#xff08;0&#xff0c;0&#xff09;到&#xff08;0&#xff0c;0…

      day2 MySQL表數據操作

      一&#xff1a;數據操作 注&#xff1a;在編寫MySQL代碼時可以不用區分大小寫 1.查看表結構 desc 表名; -- 查看表中的字段類型&#xff0c;長度&#xff0c;約束。 2.字段的增加 AFTER table 表名 add 字段名 數據類型; -- 默認末尾添加 after table 表名 add 字段名 …

      GitAny - 無需登入的 GitHub 最新倉庫檢索工具

      地址&#xff1a;https://github.com/MartinxMax/gitany GitAny - 無需登入的 GitHub 專案搜尋工具 GitAny 是一款基於 Python 的工具&#xff0c;允許你在無需登入的情況下搜尋當天最新的 GitHub 專案。它支援模糊搜尋、條件篩選以及倉庫資料的視覺化分析。 安裝依賴 $ pip…

      格恩朗金屬管浮子流量計 高精度測量的不二之選?

      在流量測量的復雜領域&#xff0c;精度就是生命線&#xff0c;直接關乎生產的穩定性、產品的質量以及資源的合理利用。大連格恩朗品牌的金屬管浮子流量計&#xff0c;憑借其卓越的精度表現&#xff0c;成為各行業在流量測量時的最佳之選。? 格恩朗金屬管浮子流量計運用經典的可…

      【R語言編程繪圖-箱線圖】

      基本箱線圖繪制 使用ggplot2繪制箱線圖的核心函數是geom_boxplot()。以下是一個基礎示例&#xff0c;展示如何用iris數據集繪制不同物種&#xff08;Species&#xff09;的萼片長度&#xff08;Sepal.Length&#xff09;分布&#xff1a; library(ggplot2) ggplot(iris, aes(…

      深度學習能取代機器學習嗎?

      在人工智能領域&#xff0c;“機器學習”和“深度學習”這兩個詞經常被混為一談。很多新手甚至以為只要跟 AI 有關的任務&#xff0c;都該用深度學習。但其實&#xff0c;它們并不是誰強誰弱的關系&#xff0c;而是適合不同場景的工具。 這篇文章就來幫你理清楚&#xff1a; 機…

      UPS的工作原理和UPS系統中旁路的作用

      UPS&#xff08;不間斷電源&#xff09;根據工作原理和適用場景的不同&#xff0c;主要分為以下三種類型&#xff0c;每種類型的特點和適用場景如下&#xff1a; 1. 后備式UPS&#xff08;Offline/Standby UPS&#xff09; 工作原理&#xff1a; 正常供電時&#xff0c;負載直接…

      一級菜單401問題

      正常代碼生成的前后臺文件&#xff0c;菜單類型是一級標題&#xff0c; 菜單路徑和前端組件的地址都正常寫的:/projects/xxx/xxx/xxx/XxxList 其他生成的新列表都能點進去&#xff0c;只有這個點進去就是顯示空白的像首頁那個頁面一樣&#xff0c; 問題就出現在我第一次建這…

      ROS2 robot控制學習(一)

      controller_position.yaml使用說明 ROS 2 的 controller_manager 用途典型工作流程示例關鍵服務與話題擴展功能JointTrajectoryController 參數詳解基本參數軌跡參數插值參數前饋控制代碼示例動態參數調試參數ForwardCommandController 概述參數解釋`joints``interface``allow_…

      LightGBM的python實現及參數優化

      文章目錄 1. LightGBM模型參數介紹2. 核心優勢3. python實現LightGBM3.1 基礎實現3.1.1 Scikit-learn接口示例3.1.2 Python API示例 3.2 模型調優3.2.1 GridSearchCV簡介3.2.2 LightGBM超參調優3.2.3 GridSearchCV尋優結果 在之前的文章 Boosting算法【AdaBoost、GBDT 、XGBoo…

      Map集合(雙列集合)

      Map結合也稱為“鍵值對集合”&#xff0c;格式&#xff1a;{key1value1&#xff0c;key2value2....} Map集合的特點&#xff1a; 鍵唯一&#xff1a;在Map集合中&#xff0c;鍵&#xff08;key&#xff09;是唯一的&#xff0c;不能有重復的鍵。如果嘗試插入一個已經存在的鍵…

      springBoot項目測試時瀏覽器返回406問題解決方案

      1. 如果基于最新版本的SpringBoot官方骨架創建的SpringBoot項目&#xff0c;在勾選了lombok的依賴之后&#xff0c;會在pom.xml中引入如下兩個插件&#xff1a; 2. 由于第一個插件 maven-compiler-plugin 的引入導致了這個問題&#xff0c;解決這個問題的方案呢&#xff0c;就是…

      21.享元模式:思考與解讀

      原文地址:享元模式&#xff1a;思考與解讀 更多內容請關注&#xff1a;深入思考與解讀設計模式 引言 在軟件開發中&#xff0c;特別是當你處理大量相似對象時&#xff0c;是否會遇到一個問題&#xff1a;大量的對象會占用大量的內存&#xff0c;而這些對象有許多相同的狀態&…

      java方法重寫學習筆記

      方法重寫介紹 子類和父類有兩個返回值&#xff0c;參數&#xff0c;名稱都一樣的方法&#xff0c; 子類的方法會覆蓋父類的方法。 調用 public class Overide01 {public static void main(String[] args) {Dog dog new Dog();dog.cry();} }Animal類 public class Animal {…