Mysql併發時經典常見的死鎖原因及解決方法

2021-09-20 20:33:34 字數 4523 閱讀 4010

1.mysql都有什麼鎖

mysql有三種鎖的級別:頁級、表級、行級。

表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。

行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般

演算法:next keylocks鎖,同時鎖住記錄(資料),並且鎖住記錄前面的gap    

gap鎖,不鎖記錄,僅僅記錄前面的gap

recordlock鎖(鎖資料,不鎖gap)

所以其實 next-keylocks=gap鎖+ recordlock鎖

2.什麼情況下會造成死鎖

所謂死鎖: 是指兩個或兩個以上的程序在執行過程中,

因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去.

此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等竺的程序稱為死鎖程序.

表級鎖不會產生死鎖.所以解決死鎖主要還是針對於最常用的innodb.

死鎖的關鍵在於:兩個(或以上)的session加鎖的順序不一致。

那麼對應的解決死鎖問題的關鍵就是:讓不同的session加鎖有次序

3.一些常見的死鎖案例

案例一:

需求:將投資的錢拆成幾份隨機分配給借款人。

起初業務程式思路是這樣的:

投資人投資後,將金額隨機分為幾份,然後隨機從借款人表裡面選幾個,然後通過一條條select for update 去更新借款人表裡面的餘額等。

抽象出來就是乙個session通過for迴圈會有幾條如下的語句:

select * from *** where id='隨機id' for update

基本來說,程式開啟後不一會就死鎖。

這可以是說最經典的死鎖情形了。

例如兩個使用者同時投資,a使用者金額隨機分為2份,分給借款人1,2

b使用者金額隨機分為2份,分給借款人2,1

由於加鎖的順序不一樣,死鎖當然很快就出現了。

對於這個問題的改進很簡單,直接把所有分配到的借款人直接一次鎖住就行了。

select * from *** where id in (xx,xx,xx) for update

在in裡面的列表值mysql是會自動從小到大排序,加鎖也是一條條從小到大加的鎖

例如(以下會話id為主鍵):

session1:

mysql> select * from t3 where id in (8,9) for update;

+----+--------+------+---------------------+

| id | course | name | ctime               |

+----+--------+------+---------------------+

|  8 | wa     | f    | 2016-03-02 11:36:30 |

|  9 | jx     | f    | 2016-03-01 11:36:30 |

+----+--------+------+---------------------+

2 rows in set (0.04 sec)

session2:select * from t3 where id in (10,8,5) for update;

鎖等待中……

其實這個時候id=10這條記錄沒有被鎖住的,但id=5的記錄已經被鎖住了,鎖的等待在id=8的這裡。

不信請看

session3:

mysql> select * from t3 where id=5 for update;

鎖等待中

session4:

mysql> select * from t3 where id=10 for update;

+----+--------+------+---------------------+

| id | course | name | ctime               |

+----+--------+------+---------------------+

| 10 | jb     | g    | 2016-03-10 11:45:05 |

+----+--------+------+---------------------+

1 row in set (0.00 sec)

在其它session中id=5是加不了鎖的,但是id=10是可以加上鎖的。

案例2

在開發中,經常會做這類的判斷需求:根據字段值查詢(有索引),如果不存在,則插入;否則更新。

以id為主鍵為例,目前還沒有id=22的行

session1:select * from t3 where id=22 for update;

empty set (0.00 sec)

session2:select * from t3 where id=23  for update;

empty set (0.00 sec)

session1:insert into t3 values(22,'ac','a',now());

鎖等待中……

session2:insert into t3 values(23,'bc','b',now());

error 1213 (40001): deadlock found when trying to get lock; try restarting transaction

當對存在的行進行鎖的時候(主鍵),mysql就只有行鎖。

當對未存在的行進行鎖的時候(即使條件為主鍵),mysql是會鎖住一段範圍(有gap鎖)

鎖住的範圍為:

(無窮小或小於表中鎖住id的最大值,無窮大或大於表中鎖住id的最小值)

如:如果表中目前有已有的id為(11 , 12)

那麼就鎖住(12,無窮大)

如果表中目前已有的id為(11 , 30)

那麼就鎖住(11,30)

對於這種死鎖的解決辦法是:

insert into t3(xx,xx) on duplicate key update `xx`='xx';

用mysql特有的語法來解決此問題。因為insert語句對於主鍵來說,插入的行不管有沒有存在,都會只有行鎖。

案例3

直接上情景:

mysql> select * from t3 where id=9 for update;

+----+--------+------+---------------------+

| id | course | name | ctime               |

+----+--------+------+---------------------+

|  9 | jx     | f    | 2016-03-01 11:36:30 |

+----+--------+------+---------------------+

1 row in set (0.00 sec)

session2:

mysql> select * from t3 where id<20 for update;

鎖等待中

session1:

mysql> insert into t3 values(7,'ae','a',now());

error 1213 (40001): deadlock found when trying to get lock; try restarting transaction

這個跟案例一其它是差不多的情況,只是session1不按常理出牌了,

session2在等待session1的id=9的鎖,session2又持了1到8的鎖(注意9到19的範圍並沒有被session2鎖住),最後,session1在插入新行時又得等待session2,故死鎖發生了。

這種一般是在業務需求中基本不會出現,因為你鎖住了id=9,卻又想插入id=7的行,這就有點跳了,當然肯定也有解決的方法,那就是重理業務需求,避免這樣的寫法。

附記,推薦兩篇好文章

案例4:

mysql 加鎖處理分析:

Mysql併發時經典常見的死鎖原因及解決方法

1.mysql都有什麼鎖 mysql有三種鎖的級別 頁級 表級 行級。表級鎖 開銷小,加鎖快 不會出現死鎖 鎖定粒度大,發生鎖衝突的概率最高,併發度最低。行級鎖 開銷大,加鎖慢 會出現死鎖 鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。頁面鎖 開銷和加鎖時間界於表鎖和行鎖之間 會出現死鎖 鎖定粒...

Mysql併發時經典常見的死鎖原因及解決方法

mysql有三種鎖的級別 頁級 表級 行級。演算法 next keylocks鎖,同時鎖住記錄 資料 並且鎖住記錄前面的gap gap鎖,不鎖記錄,僅僅記錄前面的gap recordlock鎖 鎖資料,不鎖gap 所以其實 next keylocks gap鎖 recordlock鎖 所謂死鎖dea...

Mysql併發時經典常見的死鎖原因及解決方法

1.mysql都有什麼鎖 mysql有三種鎖的級別 頁級 表級 行級。表級鎖 開銷小,加鎖快 不會出現死鎖 鎖定粒度大,發生鎖衝突的概率最高,併發度最低。行級鎖 開銷大,加鎖慢 會出現死鎖 鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。頁面鎖 開銷和加鎖時間界於表鎖和行鎖之間 會出現死鎖 鎖定粒...