1. 程式人生 > >1-2 【包子mysql系列】, 對mysql的innoDB加鎖分析

1-2 【包子mysql系列】, 對mysql的innoDB加鎖分析

innoDB的事務,是基於鎖來實現的,用到事務不自然就會用到鎖,而如果對鎖理解的不通透,很容易造成線上問題。

資料庫加鎖的分析,和事務的引擎,隔離級別,索引,主鍵索引都有關係,

如果去考慮引擎和各種隔離級別的話,就會很複雜了,所以下面都是基於innoDB和RR的隔離級別進行分析:

 

表結構:

內容:

 

 

1 , 根據主鍵更新

 

如果根據主鍵來行數

 

事務A

事務B

 

update user set name='ce1' where id='1';

update user set name='ce3' where Id='3';

同時執行,都成功

update user set name='ce1' where id='1';

update user set name='ce3' where userId='10003';

B更新失敗,直至:Lock wait timeout

 

結論,如果根據非主鍵來更新,會把整個表進行鎖定,無法 進行更新操作。

 

注:只要是根據主鍵索引來更新,哪怕事務A沒命中主鍵,也不會鎖定整個表

 

 

2,根據非索引非主鍵更新

 

事務A

事務B

 

update user set name='ce1' where userId='10001';

update user set name='ce3' where Id='3';

或者

update user set name='ce3' where userId='10003'

都會失敗,如果非索引,直接鎖表

 

3, 如果在userId 列上加入普通唯一索引

 

修改成

再更新

事務A

事務B

 

update user set name='ce1' where userId='10001';

update user set name='ce3' where Id='3';

或者

update user set name='ce3' where userId='10003'

都會成功,如果有唯一索引,也是能成功行數,互相不影響

 

4, 如果在userId 列上加入普通非唯一索引 (重點探討)

把userId改成非唯一索引:

 

記錄內容如下:

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

| id | userId | name |

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

| 1 | 10001 | ce1 |

| 2 | 10002 | ce2 |

| 3 | 10001 | ce3 |

| 4 | 10004 | ce4 |

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

再相同操作

事務A

事務B

 

update user set name='ce1' where userId='10001';

update user set name='ce3' where Id='3';

B失誤執行失敗,顯然id=3的這行也被鎖住了

其實最終還是按主鍵鎖住的記錄 id=1和id=3的記錄

非唯一索引與普通索引,更一步的區別是GAP鎖

gap鎖是用於解決幻讀的存在,演示

把記錄修改成,如:

id為pk. userId為Normal key

A事務

B事務

結果

begin;

 

update user set name='ce22' where userId='100020';

   
 

insert into user (userId,name) values('100021','tttt');

直至事務失敗超時

1, 首先GAP鎖針對的是insert操作

2, 當更新userId='100020'時,會鎖住兩邊的記錄區間,防止幻讀的存在。

3, 鎖是作用在普通索引上,但由於索引是由B+樹儲存,那麼鎖住的是兩邊的區間,防止insert

GAP鎖為什麼不是鎖住一條記錄,而是鎖住一個區間呢? 

附上疑問: https://www.oschina.net/question/867417_2289606

其實:

GAP鎖是解決幻讀存在的,如當 delete時就必須鎖住區間了

A事務

B事務

 

begin;

 

delete from user where userId='888888';

   
 

insert into user (userId,name) values('100021','tttt');

OK, 可以插入

 

insert into user (userId,name) values('100041','tttt');

插入超時

可見,這個GAP鎖,鎖住的是100040~無窮大 的記錄

死鎖的產生分析

1, 兩條語句產生的死鎖

id = pk, userId= key

最簡單的。兩條語句互相更新等待

 

begin;

 

update user set name='ce1' where userId='100010';

begin;

 

update user set name='ce2' where userId='100020';

update user set name='ce2' where userId='100020';

update user set name='ce1' where userId='100010';

 

最簡單的死鎖

 

2, 由於gap鎖,刪除一臺不存在的記錄

 

如,先刪除一條記錄,然後插入一條記錄, 如果記錄GAP鎖衝突,兩個事務容易互為死鎖。如:

 

A事務

B事務

begin;

delete from user where userId='100020';

(Query OK, 1 row affected)

begin;

 

 

delete from user where userId='565656';

insert into user (userId,name) values('100041','tttt');

 

 

insert into user (userId,name) values('100019','tttt');

 

結果直接丟擲:

 

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 

 

分析:

 

A事務 delete 加入gap鎖【100010,100020】, 第二段【100020,100030】

B事務 delete加入gap 鎖【100040,無窮大】

然後A事務插入,獲取插入意向鎖時B事務的GAP鎖被阻塞

B事務插入,獲取插入意向鎖時時被A事務的GAP鎖阻塞

結果死鎖