innoDB的事務,是基於鎖來實現的,用到事務不天然就會用到鎖,而若是對鎖理解的不通透,很容易形成線上問題。數據庫
數據庫加鎖的分析,和事務的引擎,隔離級別,索引,主鍵索引都有關係,spa
若是去考慮引擎和各類隔離級別的話,就會很複雜了,因此下面都是基於innoDB和RR的隔離級別進行分析:.net
表結構:rest
內容:orm
若是根據主鍵來行數blog
事務A索引 |
事務B事務 |
|
update user set name='ce1' where id='1';get |
update user set name='ce3' where Id='3';it |
同時執行,都成功 |
update user set name='ce1' where id='1'; |
update user set name='ce3' where userId='10003'; |
B更新失敗,直至:Lock wait timeout |
結論,若是根據非主鍵來更新,會把整個表進行鎖定,沒法 進行更新操做。
注:只要是根據主鍵索引來更新,哪怕事務A沒命中主鍵,也不會鎖定整個表
事務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' |
都會失敗,若是非索引,直接鎖表 |
修改爲
再更新
事務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' |
都會成功,若是有惟一索引,也是能成功行數,互相不影響 |
把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鎖是用於解決幻讀的存在,演示
把記錄修改爲,如:
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~無窮大 的記錄
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'; |
最簡單的死鎖 |
如,先刪除一條記錄,而後插入一條記錄, 若是記錄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鎖阻塞
結果死鎖
公衆號:
何錦彬 2018.11.21