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; |
如果文章存在問題,或者有疑惑麻煩給我留言,大家一起學習,感謝您~
歡迎關注我的微信公衆號,裏面有很多幹貨,各種面試題