mysql死鎖的問題?

假如,A準備給B轉錢,而B在同時也在給A轉錢。

我的理解是,在資料庫方面,就同時有兩個事務,第一個,是A操作帶來的,他鎖定自己的行信息,修改餘額,然後準備去鎖定B的行信息;第二個,是B帶來的,B先鎖定自己的行信息,修改餘額,然後準備去鎖定A的行信息。

由於同時發生的,那麼他們就死鎖了。

問題是:

1,場景如第一行,是同時互相轉錢,我這樣的後台資料庫操作邏輯對不對?

2,如果是對的,死鎖是否會發生?如果是不對的,該如何設計後台實現?

3,如果死鎖會發生,該如何避免,mysql的事務自動會處理(有人說會自動處理),那我能不能代碼的設計上,避免這種死鎖的情況?

非常感謝

一下是2018年3月16日更新

1,已經得到了我心中認可的方案,@郭靖,@孫前前,@害羞又大鬧 說道的,設定順序執行,可以避免死鎖的發生。同時, 另外一個詢問別人得到的解決方案是,採用redis鎖,關注互相轉賬的情況,先佔鎖再解鎖,具體思路可以自行腦補。

2,問題一開始是兩個問題,第一,如果死鎖,mysql是否會自己處理,第二,我該在開發設計中如何從設計的邏輯和方案選型上,避免死鎖的發生。部分大佬給我解答了,謝謝大佬們


在資料庫的用戶看來,事務是並發的,可以同時發生。可從資料庫內部看來,為了實現隔離性,事務在概念上都是有先後順序的。這個順序,只是針對於事務有衝突(衝突包括1.讀和寫; 2.寫和寫)的情形來說的;若無衝突,順序無關緊要。死鎖發生時,就是違反這個先後順序規則的時候。鎖定的目的,就是為了確保資料庫不會發生不可串列化異常。

從A轉賬到B,就是寫A和B。

兩個事務 T1, T2

T1 write A write B

T2 write B write A

T1,T2並發,

如果調度的序列是這樣的:

T1 write A, T2 write B, T1 write B, T2 write A

T1認為在T1應在T2之前,而T2認為T2應在T1之前,死鎖了,違反鎖定繼續下去就不可串列化了。

如果調度的序列產生一個可串列化的調度(有與之等價串列調度,在語義上等價於T1在T2前,或T2在T1前),那麼就不會發生死鎖。

--------------------回答題主的問題

1.只要在事務中,就沒問題

2.可能會死鎖

3.如果發生了死鎖,MySQL死鎖檢測會檢測到,回滾事務。避免死鎖(理論上應該叫死鎖預防 (Deadlock Prevention),死鎖避免(Deadlock Avoidance)通過一些演算法,如銀行家演算法,動態的去檢測加鎖請求是否會產生死鎖危險,很難應用在資料庫用戶層上),只要打破死鎖發生的條件(死鎖的四個條件)即可。資料庫用戶層面可做的,一般是破壞環路條件,按順序加鎖就不會產生環。拿這個例子來說,不管是從A轉到B,還是從B轉到A,我們都先寫A,後寫B就可以避免死鎖。


1. 你這種情況是有可能導致死鎖的,A在等待B釋放資源,B在等待A釋放資源,相互等待資源,造成死鎖。如果出現死鎖會報ERROR,可在日誌里查詢到,已經出現死鎖的情況,mysql會自動檢測到了兩個會話互相等待鎖的情況,然後把最後一個會話去做回滾操作。

2. 針對這個問題避免死鎖的方法:

① 設置鎖優先順序:提前設置優先順序,如果運行A和B出現死鎖,優先順序低的回滾,優先順序高的先執行,這樣即可解決死鎖問題。

② 以固定順序訪問:設定一個順序,比如先A後B,或者先B後A,保證不管在什麼時候都尊重這個順序(通常是按ID大小的順序),這樣就會減少死鎖發生的概率了。

③ 設置鎖超時時間set lock_timeout:嘗試獲取鎖的時候加一個鎖超時時間,超過這個時間放棄對該鎖請求。比如設置A的超時時間為10毫秒,B為100毫秒,A試了10毫秒以後獲取不到資源,然後會自動斷開,A斷開了,這時B就可以獲取資源了,避免了死鎖。(但是這個方法不太好的地方在於,還需要對A再提交一次,而且timeout時間需要綜合很多其他因素去設置)

④ 對所使用的數據全部加鎖:每一個事務一次就將所有要使用到的數據全部加鎖,否則就不允許執行,比如A在給B轉錢的時候,會使用到A賬戶轉賬前,A相互轉賬後,B賬戶轉賬前,B賬戶轉賬後,所以就算是A給B轉賬,也要把A.B賬戶所有信息都一起加鎖(這樣B想給A轉賬也不行,因為被鎖住了,不過這個還是傻,效率很低,可能又會帶來其他死鎖問題)

以上是我能想到的解決你提出的這個場景的幾種方法,關於避免死鎖的方法太多了,還有其他建立索引, 設置事務隔離級別,編寫應用程序讓進程持有鎖的時間儘可能短,等等。


我該在開發設計中如何從設計的邏輯和方案選型上,避免死鎖的發生?

樓上各位答主都給出了友好的方法。

保證應用層面的鎖順序,來避免資料庫事務處理過程中的死鎖問題,是可行的;

即@郭華 提出的

------------

如果發生了死鎖,MySQL死鎖檢測會檢測到,回滾事務。避免死鎖(理論上應該叫死鎖預防 (Deadlock Prevention),死鎖避免(Deadlock Avoidance)通過一些演算法,如銀行家演算法,動態的去檢測加鎖請求是否會產生死鎖危險,很難應用在資料庫用戶層上),只要打破死鎖發生的條件(死鎖的四個條件)即可。資料庫用戶層面可做的,一般是破壞環路條件,按順序加鎖就不會產生環。拿這個例子來說,不管是從A轉到B,還是從B轉到A,我們都先寫A,後寫B就可以避免死鎖。

--------------

順序加鎖思想是一致的;

至於題主提出的redis加鎖,如果在不保證加鎖順序的情況下,仍然可能出現死鎖,仍然需要redis的請求鎖超時來打斷邏輯事務的處理;

-------

新的問題:我們怎麼確定加鎖的順序才是合理的呢?

如果一個業務邏輯比較複雜,牽涉的表和邏輯比較多,一個邏輯事務可能牽涉到很多數據的獲取的更新;有沒有一種更輕便的方法來控制加鎖順序呢?

其實將各種記錄鎖的順序進行排序(其實就是表名 +記錄key的鎖粒度),排序的規則即邏輯中的規則,一旦定下就不允許兩個事務中採用不同的鎖順序,這裡的順序包括不同表名之間的順序,同表名間記錄key的順序(表名的順序一般都是從邏輯上考慮,記錄key的順序從小到大即可);

-----

更自動的方法是我們可以使用類似Java中的Reentrant的讀寫鎖來實現鎖定記錄;

甚至我們可以在加鎖中自動檢測事務中的所順序是否符合我們的定義規則;

這些都是比較自動的做法來實現該機制。

-------------

如果牽涉到分散式的問題,就要使用題主的redis來實現了

------

此外:我認為在邏輯層面避免死鎖問題優於讓死鎖回滾事務的操作


死鎖了就會有一個回滾,只要你transaction和index都對了,資料庫不會發生問題。


首先,你需要了解什麼是事物,然後了解事物之間的隔離級別

然後,在看看mvcc,看看事物是如何控制的,A轉賬到B必須在一個事物中,反之B轉A也是一個事物,鎖的等級和互斥就在這裡起作用了

最後,你創建個表,然後開2個窗口,自己開啟2個事物,執行update,test一下,然後看看對應的系統視圖,就都明白了


使用 select for update 鎖定行

按一定的順序獲取鎖,賬戶的話就按ID

兩個賬戶記錄都獲取到才繼續進行下去


你理解的對,死鎖就是這麼發生的。所以你寫的SQL效率一定要高,然後在符合業務邏輯的前提下儘早提交,目的就是縮短事務時間,減少兩個事務操作同一行的可能性。主要也不是避免死鎖,這並不容易發生,主要避免鎖等待。

當然如果業務要求事務很長,那麼一般會加個樂觀鎖來避免並發。

在Oracle資料庫中,如果事務檢測到死鎖,他會回滾。


先說處理,關係型資料庫,對於死鎖都有一個殺死優先順序低的或者最優殺死的進程的方式。這樣另外一個就能進行完畢。你這個裡面問題在於並發引起的事務死鎖,處理這種問題一般我們採用的方式就是針對事務進行排序,這樣對於交易類型的數據按照提交賬戶進行事務排序。當然對於簡單的操作就是通過索引等方式加快事務處理的速度,這樣可以儘可能減少死鎖的可能性。


redis應該是解決並發的問題感覺 其實感覺上也是利用分散式鎖對兩個線程進行了防並發 根本上也是排序避免了死鎖了吧。


鎖了就重來,關鍵知道什麼時候死鎖了。


推薦閱讀:

JSONP是個什麼鬼?
MySQL 字元轉化以及亂碼原因
雲資料庫UDB的三重境界
簡析關係型資料庫和非關係型資料庫的比較(下)
MySQL恢復delete的數據

TAG:資料庫 | MySQL | 死鎖 | 資料庫事務 |