深入淺出Mysql-InnoDB鎖算法

Record Lock

  行鎖,總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定

Gap Lock

  間隙鎖,鎖定一個範圍,但不包含記錄本身(⚠️注意間隙鎖只會存在隔離級別REPEATABLE-READ),如下表中,當鎖定id=3,Gap Lock會鎖定(1,3),(3,5);

id a b
1 2 a
3 4 b
5 6 c
7 8 d

Next-Key Lock

  臨鍵鎖,即Gap Lock+Record Lock,鎖定一個範圍,並且鎖定記錄本身;它是InnoDB默認隔離級別的鎖算法,其設計的目的是爲了解決Phantom Problem(幻讀)。根據上面的表數據可以得出,其鎖定的範圍有(負無窮,1],(1,3],(3,5],(5,7],(7,正無窮],如果插入id爲10,他鎖定範圍會變成(負無窮,1],(1,3],(3,5],(5,7],(7,10],(10,正無窮]。

  這裏有個誤區,當鎖定a=4時,鎖定範圍是不是(2,4],(4,6]呢?其實不然,其鎖定範圍爲(2,4),4,(4,6),這些鎖定範圍((負無窮,1],(1,3],(3,5],(5,7],(7,正無窮])是整張表的鎖定範圍,具體鎖定;

  當查詢的索引是唯一索引時,InnoDB存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock,即僅鎖定索引本身,而不是鎖定範圍了。

當索引是唯一索引的情況下:
條件:
  • t表數據有a = 1,2,5

  • a是唯一索引

  • 隔離級別爲:REPEATABLE-READ

騷操作
時間節點 session1 session2
1 begin;
2 select * from t where a = 5 for update;
3 begin;
4 insert into t (a) values(4);//不會阻塞
5 commit;
commit;
解釋:

  在session1中首先會a=5加X鎖,而且由於a是主鍵且唯一,因此鎖定的只有5這個值,而不是(2,5]這個範圍;在session2中插入值4,是可以成功插入的,即鎖定由Next-Key Lock算法降級爲了Record Lock,從而提高應用的併發性。

當索引是輔助索引的情況下:
創建表:
CREATE TABLE `t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`)
) ENGINE=InnoDB 
-- id爲主鍵
-- a爲輔助索引
insert into t(a,b) values(1,1);
insert into t(a,b) values(3,1);
insert into t(a,b) values(5,1);
insert into t(a,b) values(7,1);
insert into t(a,b) values(9,1);
insert into t(a,b) values(11,1);
騷操作:
時間 session1 session2
1 begin;
2 select * from t where a = 5 for update;
3 begin;
4 insert into t(a,b) values (2,1);//不阻塞🙅‍♂️
5 insert into t(a,b) values (10,1);//不阻塞🙅‍♀️
6 insert into t(a,b) values (4,1);//阻塞
7 insert into t(a,b) values(6,1);//阻塞
8 insert into t(a,b) values(5,1);//阻塞
9 commit;
10 commit;
解釋:

  Next-Key Lock鎖算法中,我們知道它是Gap Lock+Record Lock組成的,根據上表可以知,當鎖定a = 5時,其鎖算法會鎖定哪些數據呢?他會鎖定5,(3,5),(5,7)這些數據;所以插入a=4、a=5和a=6數據都會被阻塞,大家最好自己試一下,但是要注意隔離級別是RR(REPEATABLE-READ)哦!

思考🤔:如果我插入a=3和a=7,大家猜猜會不會阻塞呢

一頓操作猛如虎,直接試一試:
時間 session1 session2
1 begin;
2 select * from t where a = 5 for update;
3 begin;
4 insert into t(a,b) values (3,1);//阻塞
5 insert into t(a,b) values (7,1);//不阻塞🙅‍♀️
6 commit;
7 commit;

震驚!!!

  上面不是說如果鎖定a=5,就會鎖定5,(3,5),(5,7)這些數據嗎?那爲什麼插入a=3時,會阻塞呢

因爲因爲因爲~

  首先,你要先了解一下普通索引是根據索引字段排序,還是根據主鍵排序的?瞭解清楚了,就知道爲什麼了?

來證明以上問題:

  可以肯定的跟你說普通索引中葉子節點他是以主鍵id進行排序的.InnoDB的普通索引樹B+tree中的葉子節點,大概是這樣的存儲方式:

在這裏插入圖片描述

  如果他插入數據爲a=3,id是自增長的主鍵,所以id爲7,他的葉子節點數據變成:
在這裏插入圖片描述

  所以,從這個圖可以引申出另外一個面試題,什麼樣的字段創建索引會更合理?當然是數據不重複最好,因爲重複的數據會導致B+tree裂變,影響性能。

  小結:根據普通索引的B+tree的葉子節點的數據存儲情況,可以得出,當鎖定a=5時,會導致a=3也會被鎖住,這裏有人可能心裏會個想法,那我主鍵搞成不是有序的,保存UUID爲主鍵,不好意思,這種我也試過了,你使用UUID這種無序的字段作爲主鍵索引,InnoDB儲存引擎會默認給你創建一個隱式ID,用於數據的排序。

使用UUID作爲主鍵:
CREATE TABLE `t` (
  `id` varchar(50) NOT NULL,
  `a` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`)
) ENGINE=InnoDB 
-- id爲主鍵
-- a爲輔助索引
insert into t(id,a) values('aadfas',1);
insert into t(id,a) values('badfdas',3);
insert into t(id,a) values('adfdsac',5);
insert into t(id,a) values('rted',7);
insert into t(id,a) values('twertwere',9);
insert into t(id,a) values('rwetrewtref',11);
事務操作過程:
時間 session1 session2
1 begin;
2 select * from t where a = 5 for update;
3 begin;
4 insert into t(id,a) values(‘rtoooooed’,3);//阻塞
5 commit;
6 commit;

如果文章存在問題,或者有疑惑麻煩給我留言,大家一起學習,感謝您~
歡迎關注我的微信公衆號,裏面有很多幹貨,各種面試題
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章