記一次for update“同一事務”中update無法獲取數據鎖的解決


背景:銀行多個異步通知先后到達,需要依次更新同一條數據A(wherte acountId=aaa)(acountId是唯一索引)的不同狀態,每一次更新需要在上一次更新的基礎上進行。

  及數據A(wherte acountId=aaa)原本狀態status=0、openstatus=0

  ——》在收到通知1后,在方法methodA中,將狀態更新為status=1、openstaus=0,注更新前需要查詢到數據A(wherte acountId=aaa)

  ——》在收到通知2后,在方法methodB中,將狀態更新為status=1、openstaus=1或者status=1、openstaus=2,注更新前需要查詢到數據A(wherte acountId=aaa)

注:1、數據A的大概字段:sysNo(主鍵)、accountId(唯一鍵)、status、openStatus、createTime、......

  2、在進行某一項交易后,通知1和通知2會先后到來,但是先后的時間差不能保證,那么存在兩種情況:情況A,通知2來時執行方法methodB時可能通知1的處理方法methodA已經執行完畢,情況B,也可能methodA方法還在執行中。

  為了防止情況B在methodB開始執行的時候,methodA還未執行完畢導致的數據更新不是在最新數據上進行的更新(專業術語:更新丟失或者更新覆蓋),及methodB是直接在原始數據上進行的更新,而不是在methodA更新后的數據上進行更新的。為了防止更新丟失,可以有多種解決辦法:悲觀鎖(for update)方式、樂觀鎖(版本號、時間戳)方式、通過分布式鎖(redis)等。

 

問題:由於公司項目並發不高,所以直接使用悲觀鎖來簡單處理的,但是在處理過程中發現一個奇怪的現象(注該現象是在開發和測試環境中出現),如下圖:

描述:在同一個事務中,第一步通過for update獲取到的排他鎖,但是在進行更新update的時候發現該筆數據的排它鎖卻無法獲取,是不是感覺很奇怪?

於是下面就開始找問題,為什么update獲取不到上面的同一筆數據的鎖?

一、定位數據更新update語句,是不是更新到了索引?檢查了update語句,在update語句中並沒有更新到索引字段。

二、在數據庫中直接執行是否報錯?數據庫中直接執行沒有問題,說明代碼中前后執行不是在同一個事務中(定位問題)。

三、數據庫事務是不是統一管理的?數據庫索引會不會存在一部分spring管理,一部分mybatis管理?確認了數據庫事務統一由Spring管理,所以不是該原因造成的。

四、是否使用主從數據庫導致數據來源不一致?

  描述:公司生產上使用主從數據庫,本地開發和測試環境都是單一數據庫,沒有主從,但是通過ShardingJDBC配置了主從設置

問題原因就在這,雖然測試環境實際上只有一個數據庫,但是ShardingJDBC配置了主從,那么即使只有一個數據庫,也會產生兩條連接,一條用於主、一條用於從,所以在select for update語句的時候默認使用從鏈接的事務中獲取數據,並且將數據加鎖,而表面上看代碼是同一事務的update時,實際上是使用主鏈接的事務進行更新數據,所以select 和 update不在同一個事務中,故代碼層面看是同一事務的先后兩個操作,update時實際獲取不到之前的select的鎖,所以發生等待直至超時報錯。

解決辦法:強制統一使用主鏈接讀取和修改數據,代碼如下:

 

補充:

1、悲觀鎖導致的死鎖

在使用悲觀鎖的過程中,注意加鎖順序,如果methodA方法是更新數據1和數據2,而methodB是更新數據2和數據1,如果兩個線程同時進入到methodA和methodB,分別對數據1和數據2上鎖,然后又分別等待數據2和數據1釋放鎖后自己獲取,那么就會造成死鎖。

  避免死鎖的方法:

  A:統一加鎖順序,例如按照id自然序來進行加鎖操作,這樣事務之間的加鎖操作就不會存在死鎖。

  B:不使用悲觀鎖(樂觀鎖、分布式鎖、Redis鎖)或者在使用悲觀鎖錢在加一個全局鎖,如在redis存儲一個lock標記,當事務獲取到這個lock標記時,才允許進行更新操作,否則等待鎖。

2、ShardingJDBC

  當當網開發的主要用於處理數據分片和讀寫分離的框架。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM