6. 意向鎖
6.1. 背景
前面提到的S
鎖和X
鎖的語法規(guī)則其實是針對記錄的,也就是行鎖,原因是InnoDB中行鎖用的最多。如果將鎖的粒度和鎖的基本模式排列組合一下,就會出現(xiàn)如下4種情況:
- 行級
S
鎖 - 行級
X
鎖 - 表級
S
鎖 - 表級
X
鎖
那么接下來的描述,也就順理成章了。
如果事務(wù)給一個表添加了表級S
鎖,則:
- 其他事務(wù)可以繼續(xù)獲得該表的
S
鎖,但是無法獲取該表的X
鎖; - 其他事務(wù)可以繼續(xù)獲得該表某些行的
S
鎖,但是無法獲取該表某些行的X
鎖。
如果事務(wù)給一個表添加了表級X
鎖,則:
- 不論是該表的
S
鎖、X
鎖,還是該表某些行的S
鎖、X
鎖,其他事務(wù)都只能干瞪眼兒,啥也獲取不了。
挺好理解的吧,總之就是 S鎖只能和S鎖相容,X鎖和其他任何鎖都互斥 。問題來了,雖然用的不多,但是萬一我真的想給整個表添加一個S
鎖或者X
鎖怎么辦?
假如我要給表user
添加一個S
鎖,那就必須保證user
在表級別上和行級別上都不能有X
鎖,表級別上還好說一點,無非就是1個內(nèi)存結(jié)構(gòu)罷了,但是行X
鎖呢?必須得逐行遍歷是否有行X
鎖嗎?
同理,假如我要給表user
添加一個X
鎖,那就必須保證user
在表級別上和行級別上都不能有任何鎖(S
和X
都不能有),難不成得逐行遍歷是否有S
或X
鎖嗎?
遍歷是不可能遍歷的!這輩子都不可能遍歷的!于是, 意向鎖 (Intension Lock)誕生了。
6.2. 概念
我們要避免遍歷,那最好的辦法就是在給行加鎖時,先在表級別上添加一個標(biāo)識。
- 意向共享鎖(Intension Shared Lock):簡稱
IS
鎖,當(dāng)事務(wù)試圖給行添加S
鎖時,需要先在表級別上添加一個IS
鎖; - 意向排他鎖(Intension Exclusive Lock):簡稱
IX
鎖,當(dāng)事務(wù)試圖給行添加X
鎖時,需要先在表級別上添加一個IX
鎖。
這樣一來:
- 如果想給
user
表添加一個S
鎖(表級鎖),就先看一下user
表有沒有IX
鎖;如果有,就說明user
表的某些行被加了X
鎖(行鎖),需要等到行的X
鎖釋放,隨即IX
鎖被釋放,才可以在user
表中添加S
鎖; - 如果想給
user
表添加一個X
鎖(表級鎖),就先看一下user
有沒有IS
鎖或IX
鎖;如果有,就說明user
表的某些行被加了S
鎖或X
鎖(行鎖),需要等到所有行鎖被釋放,隨即IS
鎖或IX
鎖被釋放,才可以在user
表中添加X
鎖。
需要注意的是,意向鎖和意向鎖之間是不沖突的,意向鎖和行鎖之間也不沖突。
只有在對表添加
S
鎖或X
鎖時才需要判斷當(dāng)前表是否被添加了IS
鎖或IX
鎖,當(dāng)為表添加IS
鎖或IX
鎖時,不需要關(guān)心當(dāng)前表是否已經(jīng)被添加了其他IS
鎖或IX
鎖。
目前為止MySQL鎖的基本模式就介紹完了,接下來回到這片文章的題目,MySQL鎖,鎖住的到底是什么?由于InnoDB的行鎖用的最多,這里的鎖自然指的是行鎖。
7. 行鎖的原理
既然都叫行鎖了,我們姑且猜測一下,行鎖鎖住的是一行數(shù)據(jù)。我們做個實驗。
7.1. 沒有任何索引的表
我們先創(chuàng)建一張沒有任何索引的普通表,語句如下
CREATE TABLE `user_t1` (
`id` int DEFAULT NULL,
`name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
表中數(shù)據(jù)如下:
mysql> SELECT * FROM user_t1;
+------+-------------+
| id | name |
+------+-------------+
| 1 | chanmufeng |
| 2 | wanggangdan |
| 3 | wangshangju |
| 4 | zhaotiechui |
+------+-------------+
接下來我們在兩個session中開啟兩個事務(wù)。
- 事務(wù)1,我們通過
WHERE id = 1
“鎖住”第1行數(shù)據(jù); - 事務(wù)2,我們通過
WHERE id = 2
"鎖住"第2行數(shù)據(jù)。
一件詭異的事情是,第2個加鎖的操作被阻塞了。實際上,T2
中不管我們要給user_t1
中哪行數(shù)據(jù)加鎖,都會失??!
為什么我SELECT
一條數(shù)據(jù),卻給我鎖住了整個表?這個實驗直接推翻了我們的猜測, InnoDB的行鎖并非直接鎖定Record行 。
為什么沒有索引的情況下,給某條語句加鎖會鎖住整個表呢?別急,我們繼續(xù)。
7.2. 有主鍵索引的表
我們再創(chuàng)建一個表user_t2
,語句如下:
CREATE TABLE `user_t2` (
`id` int NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
和user_t1
的不同之處在于為id
創(chuàng)建了一個主鍵索引。表中數(shù)據(jù)依然如下:
mysql> SELECT * FROM user_t2;
+------+-------------+
| id | name |
+------+-------------+
| 1 | chanmufeng |
| 2 | wanggangdan |
| 3 | wangshangju |
| 4 | zhaotiechui |
+------+-------------+
同樣開啟兩個事務(wù):
- 事務(wù)1,通過
WHERE id = 1
“鎖住”第1行數(shù)據(jù); - 事務(wù)2
- 依然使用
WHERE id = 1
嘗試加鎖,加鎖失敗; - 使用
WHERE id = 2
嘗試加鎖,加鎖成功。
- 依然使用
既然鎖的不是Record行,難不成鎖的是id
這一列嗎?
我們再做最后一個實驗。
7.3. 有唯一索引的表
我們再創(chuàng)建一個表user_t3
,語句如下:
CREATE TABLE `user_t3` (
`id` int NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY (`uk_name`) (`name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
和user_t2
的不同之處在于為name
列創(chuàng)建了一個唯一索引。表中數(shù)據(jù)依然如下:
mysql> SELECT * FROM user_t3;
+------+-------------+
| id | name |
+------+-------------+
| 1 | chanmufeng |
| 2 | wanggangdan |
| 3 | wangshangju |
| 4 | zhaotiechui |
+------+-------------+
兩個事務(wù):
- 事務(wù)1,通過
name
字段 “鎖住”name
為“chanmufeng”的數(shù)據(jù); - 事務(wù)2
- 依然使用
WHERE name = “chanmufeng”
嘗試加鎖,可以預(yù)料,加鎖失?。?/li> - 使用
WHERE id = 1
嘗試給同樣的行加鎖,加鎖失敗。
- 依然使用
通過3個實驗我們發(fā)現(xiàn),行鎖鎖住的既不是Record行,也不是Column列,那到底鎖住的是什么?我們對比一下,上文的3張表的不同點在于索引不同,其實 InnoDB的行鎖,就是通過鎖住索引來實現(xiàn)的 。
接下來回答3個問題。
8. 三個問題
8.1. 鎖住索引?沒有索引怎么辦?
你說鎖住索引?如果我不創(chuàng)建索引,MySQL鎖定個啥?
如果我們沒有設(shè)置主鍵,InnoDB會優(yōu)先選取一個不包含NULL值的Unique鍵
作為主鍵,如果表中連Unique鍵
也沒有的話,就會自動為每一條記錄添加一個叫做DB_ROW_ID
的列作為默認(rèn)主鍵,只不過這個主鍵我們看不到罷了。
下圖是數(shù)據(jù)的行格式??床欢脑拸?qiáng)烈推薦看一下我上面給出的兩篇文章,說得非常明白。
行格式
8.2. 為什么第一個實驗會鎖表?
因為SELECT
沒有用到索引,會進(jìn)行全表掃描,然后把DB_ROW_ID
作為默認(rèn)主鍵的聚簇索引都給鎖住了。
8.3. 為什么通過唯一索引給數(shù)據(jù)加鎖,主鍵索引也會被鎖???
不管是Unique
索引還是普通索引,它們的葉子結(jié)點中存儲的數(shù)據(jù)都不完整,其中只是存儲了作為索引并且排序好的列數(shù)據(jù)以及對應(yīng)的主鍵值。
因此我們通過索引查找數(shù)據(jù)數(shù)據(jù)實際上是在索引的B+樹中先找到對應(yīng)的主鍵,然后根據(jù)主鍵再去主鍵索引的B+樹的葉子結(jié)點中找到完整數(shù)據(jù),最后返回。所以雖然是兩個索引樹,但實際上是同一行數(shù)據(jù),必須全部鎖住。
下面給了一張圖,讓不了解索引的朋友大致了解一下。上半部分是name
列創(chuàng)建的唯一索引的B+樹,下半部分是主鍵索引(也叫聚簇索引)。
假如我們通過WHERE name = '王鋼蛋'
對數(shù)據(jù)進(jìn)行查詢,會先用到name
列的唯一索引,最終定位到主鍵值為1
,然后再到主鍵索引中查詢id = 1
的數(shù)據(jù),最終拿到完整的行數(shù)據(jù)。
這兩張圖在我索引文章中都有哦~
MySQL鎖-索引
9. 總結(jié)
至此,我已經(jīng)回答了文章開頭的絕大多數(shù)問題。
MySQL鎖,是解決資源競爭問題的一種手段。有哪些競爭呢?讀—寫/寫—讀,寫—寫中都會出現(xiàn)資源競爭問題,不同的是前者可以通過MVCC的方式來解決,但是某些情況下你也不得不用鎖,因此我也順便解釋了鎖和MVCC的關(guān)系。
然后介紹了MySQL鎖的基本模式,包括共享鎖(S
鎖)和排他鎖(X
鎖),還引入了意向鎖。
最后解釋了鎖到底鎖的是什么的問題。通過3個實驗,最終解釋了InnoDB鎖本質(zhì)上鎖的是索引。
-
計算機(jī)
+關(guān)注
關(guān)注
19文章
7488瀏覽量
87849 -
MySQL
+關(guān)注
關(guān)注
1文章
804瀏覽量
26528 -
MVCC
+關(guān)注
關(guān)注
0文章
13瀏覽量
1465
發(fā)布評論請先 登錄
相關(guān)推薦
評論