事務 事務併發

2021-06-27 23:20:14 字數 3465 閱讀 5828

最近工作非常鬱悶,天天被領導盯著。主要是系統近來死鎖發生在頻率很高。最終,經過大家的共同努力,我們成功的定位並解決了問題,所以把過程中學習的知識與經驗分享一下

問題背景

系統中有乙個賬戶模組,負責管理和維護會員的各種資金及明細,對外的功能涉及資金的增加與扣減等。通過監控系統發現,當外圍系統併發訪問和呼叫同乙個會員的賬戶功能介面的時候,就會發生死鎖和響應超時的情況,所以問題的分析還得從事務的併發說起。首先,我們先回顧一下事務。

事務是神馬

所謂事務,它是乙個操作序列,這些操作要麼都執行,要麼都不執行,它是乙個不可分割的原子單位。例如,銀行轉帳工作:從乙個帳號扣款並使另乙個帳號增款,這兩個操作要麼都執行,要麼都不執行。

事務的特性

四個特性中,與我們問題相關的應該就是事務的隔離性了,要理解隔離性,就不得不說說事務的併發了。

事務的併發及帶來的影響

事務併發通俗點講就是多個事務同時執行,併發的訪問或更新資料庫中相同的資料。事務併發一般需要相應的隔離措施,否則就會出現各種問題。

事務的併發會造成哪些問題?

知道了事務併發會造成什麼影響,那如何避免問題的發生呢?從資料庫層面來講,是通過封鎖協議再解決的。

資料庫封鎖協議

在說封鎖協議前,先得講講鎖。

資料庫中有兩種鎖:共享鎖(讀鎖s)與排它鎖(寫鎖x)

再來看看封鎖協議

封鎖協議是資料庫內部實現事務併發控制的一種機制,我們無法在程式中使用。在程式開發中,是通過指定事務的

隔離級別來解決併發的各種問題的。

事務的隔離級別

有四種隔離級別,無論哪一種都不會出現

丟失更新,因為四種隔離級別都要求在更新資料物件前先要對資料加x鎖,直到事務結束後才釋放,即一級封鎖協議。

這樣看來,事務隔離級別好像與封鎖協議是一一對應的。讀未提交與一級封鎖協議對應、讀已提交與二級封鎖協議對應、可重複讀與**封鎖協議對應。但經過本人的驗證,這個不完全正確,隔離級別是乙個規範,而封議協議是規範的一種實現方式。比如mysq同時使用資料行版本與鎖的機制來實現隔離級別的,db2單純使用鎖的機制來實現隔離級別。我會重新整理一篇文章講講關於mysql與db2在隔離級別方面的細節與區別。

我們已經知道了相關的知識背景,再來看看我們系統的問題。

系統使用的是db2,所有唯讀事務使用ur隔離級別(相關於讀未提交),其他事務使用rs隔離級別(相關於可重複讀)。會員的資產功能簡單講有增加和扣減操作。

增加操作

start transaction

//第一步,查詢賬戶可用餘額

select ... from account where account_id = ...

//第二步,餘量加上增加量,並賦值給新變數newbalance

set newbalance = dbbalance + changeamount

//第三步,更新會員賬戶記錄

update account set balance = newbalance where account_id = ...

//第四步,其它操作

commit

扣減操作

start transaction

//第一步,查詢賬戶可用餘額

select ... from account where account_id = ...

//第二步,餘量扣去增加量,並賦值給新變數new_balance,如果餘量不足則報錯。

set newbalance = dbbalance - changeamount

if ( newbalance < 0 ) throw new exception("...");

//第三步,更新會員賬戶記錄

update account set balance = newbalance where account_id = ...

//第四步,其它操作

commit

上面使用偽語言描述,可以看出,不論是增加還是扣減操作,都是先查詢出賬戶記錄,根據記錄值作計算,最後再更新記錄。當外圍系統同時訪問同一會員做扣減的時候,死鎖就有可能發生了,比如:

事務t1執行第一步,查詢了會員的賬戶記錄,因為隔離級別是rs,所以會對會員賬戶記錄加s鎖;這時候事務t2也執行了第一步,也對會員的賬戶記錄加了s鎖;之後事務t1執行第三步,在準備更新會員賬戶記錄前需要先對其加x鎖,但發現記錄已經被其它事務(事務t2)加了s鎖,所以事務t1掛起,等待事務t2釋放賬戶記錄的s鎖;接著事務t2也執行到第三步,在準備更新會員賬戶記錄前需要先對其加x鎖,但發現記錄已經被其它事務(事務t1)加了s鎖,所以事務t2也掛起等待事務t1。這樣死鎖就發生了。

知道了死鎖發生的原因,那現在看看如何解決這個問題。死鎖發生的原因主要是第一步查詢的時候對賬戶記錄加了s鎖,所以如何我們把隔離級別降為cs(相當於讀已提交),能否解決問題呢?答案是否,把隔離級底降為cs確實可以避免死鎖的發生,因為查詢操作結束後就會釋放s鎖,但是卻會發生

覆蓋更新的問題,所以這個方案不可行。既然問題出在第一步,那就是第一步出法看看吧,如果查詢賬戶的時候,我們顯示的對記錄加x鎖而不是s鎖,那問題就解決了?看看:

事務t1執行第一步,查詢了會員的賬戶記錄,顯式對會員賬戶記錄加x鎖;這時候事務t2執行到第一步,在查詢會員賬戶記錄嘗試加x鎖時會等待,因為記錄已經被別的事務(事務t1)加了x鎖;之後事務t1執行第三步,順利的更新了記錄,因為它已經占有記錄的x鎖;在事務t1提交之後,事務t2就可以繼續往下執行了,所以死鎖的問題解決了。

優化後的扣減操作

start transaction

//第一步,查詢賬戶可用餘額

select ... from account where account_id = ... for update with rs

//第二步,餘量扣去增加量,並賦值給新變數new_balance,如果餘量不足則報錯。

set newbalance = dbbalance - changeamount

if ( newbalance < 0 ) throw new exception("...");

//第三步,更新會員賬戶記錄

update account set balance = newbalance where account_id = ...

//第四步,其它操作

commit

對於增加操作來講,因為沒有上限限制,所以可以直接更新增加量就可以了。優化後的增加操作

start transaction

//第一步,更新會員賬戶記錄

update account set balance = balance + changeamount where account_id = ...

//第二步,其它操作

commit

經過上面兩個優化,死鎖不再發生了,另外介面的平均響應時間也有不小的提高。

事務併發 事務隔離級別

併發問題可歸納為以下幾類 a.丟失更新 撤銷乙個事務時,把其他事務已提交的更新資料覆蓋 a和b事務併發執行,a事務執行更新後,提交 b事務在a事務更新後,b事務結束前也做了對該行資料的更新操作,然後回滾,則兩次更新操作都丟失了 b.髒讀 乙個事務讀到另乙個事務未提交的更新資料 a和b事務併發執行,b...

事務併發 事務隔離級別

併發問題可歸納為以下幾類 a.丟失更新 撤銷乙個事務時,把其他事務已提交的更新資料覆蓋 a和 b事務併發執行,a事務執行更新後,提交 b事務在 a事務更新後,b事務結束前也做了對該行資料的更新操作,然後回滾,則兩次更新操作都丟失了 b.髒讀 乙個事務讀到另乙個事務未提交的更新資料 a和 b事務併發執...

併發與事務

本部落格只是在開發過程中,對遇到的多執行緒問題的思考,如何在保證資料正確的前提下,提高效能。我覺得併發要考慮兩個問題 在io層次,併發鏈結數過多例如c10k,c10m的問題,是通過reactor 模式解決?例如開源的網路庫都是使用單執行緒io復用 非阻塞的思想解決,最優!還是通過乙個連線對應乙個執行...