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鎖阻塞
結果死鎖