1 mysql中的鎖類型:
1) 表鎖
表共享鎖(S
):表級別的讀鎖,表共享鎖之間是兼容的。
表排他鎖(X
): 表級別的寫鎖,表排他鎖和任何鎖(包括表排他鎖)都不兼容(不包括意向鎖)。
意向排他鎖(IX
): 獲取行排他鎖之前必須獲取的意向排他鎖,這個鎖是用了快速指示當前是否存在行排他鎖,而不用在表中遍歷每行數據判斷當前行是否有行鎖。
意向共享鎖(IS
): 獲取行共享鎖之前必須獲取得意向共享鎖,這個鎖是用了快速指示當前是否存在意向共享鎖,而不用在表中遍歷每行數據判斷當前行是否有行排他鎖。
2) 行鎖
行鎖(R
)主要是針對唯一索引(包括主鍵),行鎖也分行共享鎖(S
)、行排他鎖(X
)。
3) 間隙鎖
間隙鎖(GAP
):指鎖定一個范圍區間,主要用來接解決幻讀問題。
插入意向鎖(INSERT_INTENTION
):它不是意向鎖,意向鎖是表鎖,它是一種特殊的間隙鎖,在數據插入的時候需要先獲取插入意向鎖。
4) 臨鍵鎖(NEXT-KEY
) : 它可以看成一種組合鎖,相當于行鎖+間隙鎖
。普通的索引列(非唯一索引)就是加臨鍵鎖。間隙鎖是用來鎖住一個區間的,防止這個區間內插入其他數據,普通索引是可以重復的,需要鎖住自身,所以還需要行鎖,而唯一索引本身是有唯一性的,不能插入重復數據,只需要鎖住間隙就可以了,不需要鎖住自己本身。
2 具體驗證
現在有一個表stu,其中的age字段有索引,現在針對age字段做鎖實驗。stu表中目前有如下這些數據,注意:每次寫數據后都要恢復成下面的初始化數據。
CREATE TABLE `stu` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`age` int DEFAULT NULL,PRIMARY KEY (`id`),KEY `age_index` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=125 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;select * from stu;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | NULL | 10 |
| 3 | 23 | 24 |
| 6 | NULL | 32 |
| 7 | 23 | 45 |
+----+------+------+
1) 間隙鎖with間隙鎖
間隙鎖和間隙鎖之間是兼容的,即可以同時同一個區間加鎖,不會有鎖競爭。
先在會話1中開啟一個事務,更新age=24
的記錄,此時會獲取到[10,32)
這個區間的插入間隙鎖,此時先不提交事務,然后在新會話2中嘗試更新[10,32)
區間的一條數據age=25
的記錄,此時會話2中sql腳本執行正常(盡管此數據不存在)、沒有被阻塞。
// 會話1
COMMIT;
update stu set `name` ='hi' WHERE age=24//會話2
update stu set `name` ='hi' WHERE age=25
2) 插入意向鎖with插入意向鎖
插入意向鎖和插入意向鎖(也是一種間隙鎖)之間是兼容的,只要不是唯一索引(包括主鍵)沖突,可以直接插入,不會有鎖競爭。
先在會話1中開啟一個事務,插入一條age=15
的記錄,此時會獲取到[10,24)
這個區間的插入意向鎖,此時先不提交事務,然后在新會話2中嘗試插入[10,24)
區間的一條數據age=16
的記錄,此時會話2中數據插入成功、沒有被阻塞。
//會話1
begin;
INSERT stu (`name`,age) values ('he',15);//會話2
INSERT stu (`name`,age) values ('he',16);
3) 間隙鎖with插入意向鎖
間隙鎖和插入意向鎖之間有鎖競爭,但只有在先有間隙鎖再申請插入意向鎖時才會不兼容,有鎖競爭;反之在先有插入意向鎖再申請間隙鎖是不會有鎖競爭的,是兼容的。
(1)先在會話1中開啟一個事務,刪除記錄age=25
的記錄(盡管此數據不存在),此時會獲取到[32,45)
這個區間的間隙鎖,此時先不提交事務,然后在新會話2中嘗試插入[32,45)
區間的一條數據age=36
的記錄,此時會話2中數據一直被阻塞,直到會話1的事務提交。這充分證明了先有間隙鎖再申請插入意向鎖,鎖不兼容
.
//會話1
BEGIN;
DELETE from stu WHERE age=35;
//會話2
INSERT stu (`name`,age) values ('he',36); //被阻塞
(2) 其他很多技術貼都說間隙鎖的鎖區間是左開右閉,但我實驗的結果是左閉右開。
先在會話1中開啟一個事務,刪除記錄age=25
的記錄(盡管此數據不存在),此時會獲取到[32,45)
這個區間的間隙鎖,此時先不提交事務,然后在新會話2中嘗試插入[32,45)
區間的一條數據age=32
的記錄,此時插入失敗、被阻塞,然而在新會話3嘗試插入另一條數據age=45
的記錄則成功插入、沒被阻塞。按照那些技術貼的說法,應該age=45
的數據插入失敗被阻塞,age=32
的數據插入成功,但事實卻不是如此。
//會話1
BEGIN;
DELETE from stu WHERE age=35;
//會話2
INSERT stu (`name`,age) values ('he',32); //被阻塞
//會話3
INSERT stu (`name`,age) values ('he',45); //成功插入
(3)先在會話1中開啟一個事務,插入一條記錄age=27
的記錄,此時會獲取到[25,32)
這個區間的插入意向鎖,此時先不提交事務,然后在新會話2中嘗試在[25,32)
區間更新的一條數據age=29
的記錄(盡管此數據不存在),此時會話2中sql腳本正常返回、沒有被阻塞。這充分證明了先有插入意向鎖再申請間隙鎖,鎖兼容
.
//會話1
BEGIN;
INSERT stu (`name`,age) values ('he',32); //會話2
UPDATE stu set `name`='haha' WHERE age=29;//正常返回、不阻塞
3 mvvc
網上對mvvc的解釋太復雜了,我這里簡單說下的行為結果。
mvvc中讀提交
、可重復讀
這兩種隔離級別中有不同的行為。
讀提交
:每次的讀都是當前讀,即每次都讀當前最新的已提交數據。
可重復
:可重復讀是快照讀,第一次讀和當前讀一樣,讀此時的最新的已提交數據,不同點在于之后的第2~n
次讀。第一次之后的第2~n
次讀出的數據不會再變化,這時讀出的數據是第一次讀的快照,它和第一次讀的數據始終是一樣的,不再關心第一次讀之后的已提交數據。每次讀數是一樣的,這就是名副其實的可重復讀
。
在可重復讀隔離級別下, 普通的select
語句是快照讀,而update
、delete
、insert
(如果把update delete insert判斷是否能寫數據的過程看成讀的話) 、select (for update)/(lock on share mode)
都是當前讀。