共計 4907 個字符,預計需要花費 13 分鐘才能閱讀完成。
今天丸趣 TV 小編給大家分享一下 MySQL 知識點之 InnoDB 中的行級鎖是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
行鎖,也稱為記錄鎖,顧名思義就是在記錄上加的鎖。但是要注意,這個記錄指的是通過給索引上的索引項加鎖。InnoDB 這種行鎖實現特點意味著:只有通過索引條件檢索數據,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖。
不論是使用主鍵索引、唯一索引或普通索引,InnoDB 都會使用行鎖來對數據加鎖。
只有執行計劃真正使用了索引,才能使用行鎖:即便在條件中使用了索引字段,但是否使用索引來檢索數據是由 MySQL 通過判斷不同執行計劃的代價來決定的,如果 MySQL 認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。
同時當我們用范圍條件而不是相等條件檢索數據,并請求鎖時,InnoDB 會給符合條件的已有數據記錄的索引項加鎖。
不過即使是行鎖,InnoDB 里也是分成了各種類型的。換句話說即使對同一條記錄加行鎖,如果類型不同,起到的功效也是不同的。
這里我們還是使用前面的 teacher 表,增加一個索引,并插入幾條記錄。
mysql desc teacher;
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| number | int(11) | NO | PRI | NULL | |
| name | varchar(100) | YES | MUL | NULL | |
| domain | varchar(100) | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql select * from teacher;
+--------+------+--------+
| number | name | domain |
+--------+------+--------+
| 1 | T | Java |
| 3 | M | Redis |
| 9 | X | MQ |
| 15 | O | Python |
| 21 | A | Golang |
+--------+------+--------+
5 rows in set (0.00 sec)
我們來看看都有哪些常用的行鎖類型。
Record Locks
也叫記錄鎖,就是僅僅把一條記錄鎖上,官方的類型名稱為:LOCK_REC_NOT_GAP。比方說我們把 number 值為 9 的那條記錄加一個記錄鎖的示意圖如下:
記錄鎖是有 S 鎖和 X 鎖之分的,當一個事務獲取了一條記錄的 S 型記錄鎖后,其他事務也可以繼續獲取該記錄的 S 型記錄鎖,但不可以繼續獲取 X 型記錄鎖;當一個事務獲取了一條記錄的 X 型記錄鎖后,其他事務既不可以繼續獲取該記錄的 S 型記錄鎖,也不可以繼續獲取 X 型記錄鎖。
T1T2begin;
select * from teacher where number=9 for update;
select * from teacher where number=9 for update; # 阻塞 Gap Locks
MySQL 在 REPEATABLE READ 隔離級別下是可以部分解決幻讀問題的,解決方案有兩種,可以使用 MVCC 方案解決,也可以采用加鎖方案解決。但是在使用加鎖方案解決時有問題,就是事務在第一次執行讀取操作時,那些幻影記錄尚不存在,我們無法給這些幻影記錄加上記錄鎖。InnoDB 提出了一種稱之為 Gap Locks 的鎖,官方的類型名稱為:LOCK_GAP,我們也可以簡稱為 gap 鎖。
間隙鎖實質上是對索引前后的間隙上鎖,不對索引本身上鎖。
T1T2begin;
update teacher set domain=‘Redis’where name=‘M’;
insert into teacher value(23,‘B’,‘docker’); # 阻塞
insert into teacher value(23,‘B’,‘docker’); # 阻塞
事務 T1 會對 ([A, 21], [M, 3])、([M, 3], [O, 15]) 之間進行上 gap 鎖,如下圖中所示:
意味著不允許別的事務在這條記錄前后間隙插入新記錄,所以 T2 就不能插入。
但是當 SQL 語句變為:
insert into teacher value(70, P , docker
能插入嗎?當然能,因為(70,‘P’)這條記錄不在被鎖的區間內。
思考題
現在有表,表中有記錄如下:
list = [su liang , hacker , ice]
list.insert(1, kiko)
print(list)
#結果:[su liang , kiko , hacker , ice]
開啟一個事務:
begin;SELECT * FROM test1 WHERE number = 3 FOR UPDATE;
開啟另外一個事務:
INSERT INTO test1 (id, number) VALUES (2, 1); # 阻塞
INSERT INTO test1 (id, number) VALUES (3, 2); # 阻塞
INSERT INTO test1 (id, number) VALUES (6, 8); # 阻塞
INSERT INTO test1 (id, number) VALUES (8, 8); # 正常執行
INSERT INTO test1 (id, number) VALUES (9, 9); # 正常執行
INSERT INTO test1 (id, number) VALUES (10, 12); # 正常執行
UPDATE test1 SET number = 5 WHERE id = 11 AND number = 12; # 阻塞
為什么(6,8)不能執行,(8,8)可以執行?這個間隙鎖的范圍應該是[1,8],最后一個語句為什么不能執行?
解決思路:畫一個 number 的索引數據存放的圖,然后根據間隙鎖的加鎖方式,把鎖加上,就能很快明白答案。
當插入的 number 在 (1,8) 區間都會被阻塞
當插入的 number 等于 1、8,那么 id 在 (1,4]、[6,7) 區間會被阻塞
Next-Key Locks
有時候我們既想鎖住某條記錄,又想阻止其他事務在該記錄前邊的間隙插入新記錄,所以 InnoDB 就提出了一種稱之為 Next-Key Locks 的鎖,官方的類型名稱為:LOCK_ORDINARY,我們也可以簡稱為 next-key 鎖。next-key 鎖的本質就是
一個記錄鎖和一個 gap 鎖的合體。
默認情況下,InnoDB 以 REPEATABLE READ 隔離級別運行。在這種情況下,InnoDB 使用 Next-Key Locks 鎖進行搜索和索引掃描,這可以防止幻讀的發生。
Insert Intention Locks
我們說一個事務在插入一條記錄時需要判斷一下插入位置是不是被別的事務加了所謂的 gap 鎖(next-key 鎖也包含 gap 鎖,后邊就不強調了),如果有的話,插入操作需要等待,直到擁有 gap 鎖的那個事務提交。
但是 InnoDB 規定事務在等待的時候也需要在內存中生成一個鎖結構,表明有事務想在某個間隙中插入新記錄,但是現在處于等待狀態。這種類型的鎖命名為 Insert Intention Locks,官方的類型名稱為:LOCK_INSERT_INTENTION,我們也可以稱為插入意向鎖。
可以理解為插入意向鎖是一種鎖的的等待隊列,讓等鎖的事務在內存中進行排隊等待,當持有鎖的事務完成后,處于等待狀態的事務就可以獲得鎖繼續事務了。
隱式鎖
鎖的的維護是需要成本的,為了節約資源,MySQL 在設計提出了了一個隱式鎖的概念。一般情況下 INSERT 操作是不加鎖的,當然真的有并發沖突的情況下下,還是會導致問題的。
所以 MySQL 中,一個事務對新插入的記錄可以不顯式的加鎖,但是別的事務在對這條記錄加 S 鎖或者 X 鎖時,會去檢查索引記錄中的 trx_id 隱藏列,然后進行各種判斷,會先幫助當前事務生成一個鎖結構,然后自己再生成一個鎖結構后進入等待狀態。但是由于事務 id 的存在,相當于加了一個隱式鎖。
這樣的話,隱式鎖就起到了延遲生成鎖的用處。這個過程,我們無法干預,是由引擎自動處理的,對我們是完全透明的,我們知道下就行了。
鎖的內存結構
所謂的鎖其實是一個內存中的結構,在事務執行前本來是沒有鎖的,也就是說一開始是沒有鎖結構和記錄進行關聯的,當一個事務想對這條記錄做改動時,首先會看看內存中有沒有與這條記錄關聯的鎖結構,當沒有的時候就會在內存中生成一個鎖結構與之關聯。比方說事務 T1 要對記錄做改動,就需要生成一個鎖結構與之關聯。
鎖結構里至少要有兩個比較重要的屬性:
trx 信息:代表這個鎖結構是哪個事務生成的。
is_waiting:代表當前事務是否在等待。
當事務 T1 改動了條記錄后,就生成了一個鎖結構與該記錄關聯,因為之前沒有別的事務為這條記錄加鎖,所以 is_waiting 屬性就是 false,我們把這個場景就稱之為獲取鎖成功,或者加鎖成功,然后就可以繼續執行操作了。
在事務 T1 提交之前,另一個事務 T2 也想對該記錄做改動,那么先去看看有沒有鎖結構與這條記錄關聯,發現有一個鎖結構與之關聯后,然后也生成了一個鎖結構與這條記錄關聯,不過鎖結構的 is_waiting 屬性值為 true,表示當前事務需要等待,我們把這個場景就稱之為獲取鎖失敗,或者加鎖失敗,或者沒有成功的獲取到鎖。
在事務 T1 提交之后,就會把該事務生成的鎖結構釋放掉,然后看看還有沒有別的事務在等待獲取鎖,發現了事務 T2 還在等待獲取鎖,所以把事務 T2 對應的鎖結構的 is_waiting 屬性設置為 false,然后把該事務對應的線程喚醒,讓它繼續執行,此時事務 T2 就算獲取到鎖了。這種實現方式非常像并發編程里 AQS 的等待隊列。
對一條記錄加鎖的本質就是在內存中創建一個鎖結構與之關聯。那么,一個事務對多條記錄加鎖時,是不是就要創建多個鎖結構呢?比如:
SELECT * FROM teacher LOCK IN SHARE MODE;
很顯然,這條語句需要為 teacher 表中的所有記錄進行加鎖。那么,是不是需要為每條記錄都生成一個鎖結構呢?其實理論上創建多個鎖結構沒有問題,反而更容易理解。但是如果一個事務要獲取 10,000 條記錄的鎖,要生成 10,000 個這樣的結構,不管是執行效率還是空間效率來說都是很不劃算的,所以實際上,并不是一個記錄一個鎖結構。
當然鎖結構實際是很復雜的,我們大概了解下里面包含哪些元素。
鎖所在的事務信息:無論是表級鎖還是行級鎖,一個鎖屬于一個事務,這里記載著該鎖對應的事務信息。
索引信息:對于行級鎖來說,需要記錄一下加鎖的記錄屬于哪個索引。
表鎖 / 行鎖信息:表級鎖結構和行級鎖結構在這個位置的內容是不同的。具體表現為表級鎖記載著這是對哪個表加的鎖,還有其他的一些信息;而行級鎖記載了記錄所在的表空間、記錄所在的頁號、區分到底是為哪一條記錄加了鎖的數據結構。
鎖模式:鎖是 IS,IX,S,X 中的哪一種。
鎖類型:表鎖還是行鎖,行鎖的具體類型。
其他:一些和鎖管理相關的數據結構,比如哈希表和鏈表等。
基本上來說,同一個事務里,同一個數據頁面,同一個加鎖類型的鎖會保存在一起。
以上就是“MySQL 知識點之 InnoDB 中的行級鎖是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,丸趣 TV 小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注丸趣 TV 行業資訊頻道。