共計 8926 個字符,預計需要花費 23 分鐘才能閱讀完成。
本篇內(nèi)容主要講解“MYSQL 中鎖的模式與類型有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓丸趣 TV 小編來帶大家學習“MYSQL 中鎖的模式與類型有哪些”吧!
在日常開發(fā)工作中,我們幾乎需要天天與數(shù)據(jù)庫打交道,作為一名只會 CRUD 的 SQL BOY,除了每天用 mybatis-generator 自動生成 DAO 層代碼之外,我們幾乎不用去 care 數(shù)據(jù)庫中如何處理并發(fā)請求,但是突然某一天 MYSQL 數(shù)據(jù)庫告警了,出現(xiàn)了死鎖,我們的內(nèi)心慌的一匹,不禁想問:這不就是個普通查詢嗎,咋還鎖起來了?
為了避免慌亂的表情被主管捕捉到,我們需要提前了解一下數(shù)據(jù)庫中到底有哪些鎖。
在 MySQL 中,其實將鎖分成了兩類:鎖類型(lock_type)和鎖模式(lock_mode)。
鎖類型描述的鎖的粒度,也就是把鎖具體加在什么地方;而鎖模式描述的是到底加的是什么鎖,是讀鎖還是寫鎖。鎖模式通常和鎖類型結(jié)合使用。
按鎖的模式分
讀鎖
讀鎖,又叫共享鎖 / S 鎖 /share locks。
讀鎖是某個事務(wù)(比如事務(wù) A)在進行讀取操作(比如讀一張表或者讀取某一行)時創(chuàng)建出來的鎖,其他的事務(wù)可以并發(fā)地讀取這些數(shù)據(jù)(被加了鎖的),但是不能修改這些數(shù)據(jù)(除非持有鎖的用戶已經(jīng)釋放鎖)。
事務(wù) A 對數(shù)據(jù)加上讀鎖之后,其他事務(wù)依然可以對其添加讀鎖(共享),但是不能添加寫鎖。
在記錄上加讀鎖
InnoDB 支持表鎖和行鎖,在行(也就是記錄)上加鎖,并不是鎖住該條記錄,而是在記錄對應(yīng)的索引上加鎖。如果 where 條件中不走索引,則會對所有的記錄加鎖。
顯式加鎖語句為:
注意:這里所說的讀,是指當前讀,快照讀是無需加鎖的。普通 select 讀一般都是快照讀,除了 select…lock in share mode 這樣的顯式加鎖語句下會變成當前讀,在 InnoDB 引擎的 serializable 級別下,普通 select 讀也會變成快照讀。
另外需要注意,對于行鎖的加鎖過程分析,要根據(jù)事務(wù)隔離級別、是否使用索引(哪種類型的索引)、記錄是否存在等因素結(jié)合分析,才能判斷在哪里加上了鎖。
innodb 引擎中的加讀鎖的幾種情形
普通查詢在隔離級別為 serializable 會給記錄加 S 鎖。但這也取決于場景:非事務(wù)讀(auto-commit)在 Serializable 隔離級別下,無需加鎖;
Serializable 隔離級別時:如果查詢條件為唯一索引且是唯一等值查詢時:是在該條記錄上加 S 鎖;非唯一條件查詢(查詢會掃描到多條記錄時):記錄本身 + 記錄的間隙(需要具體分析間隙的范圍),加 S 鎖;
select … in share mode,會給記錄加 S 鎖,但是根據(jù)隔離級別的不同,加鎖的行為有所不同:
RC 隔離級別:是在記錄上加 S 鎖。RR/Serializable 隔離級別:如果查詢條件為唯一索引且是唯一等值查詢時:是在該條記錄上加 S 鎖;非唯一條件查詢(查詢會掃描到多條記錄時):記錄本身 + 記錄的間隙(需要具體分析間隙的范圍),加 S 鎖;
通常 insert 操作是不加鎖的,但如果在插入或更新記錄時,檢查到 duplicate key(或者有一個被標記刪除的 duplicate key),對于普通的 insert/update,會加 S 鎖,而對于類似 replace into 或者 insert … on duplicate 這樣的 SQL 語句加的是 X 鎖。
insert … select 插入數(shù)據(jù)時,會對 select 的表上掃描到的數(shù)據(jù)加 S 鎖;
外鍵檢查:當我們刪除一條父表上的記錄時,需要去檢查是否有引用約束,這時候會掃描子表上對應(yīng)的記錄,并加上 S 鎖。
在表上加讀鎖
表鎖由 MySQL 服務(wù)器實現(xiàn),無論存儲引擎是什么,都可以使用表鎖。一般在執(zhí)行 DDL 語句時,譬如 ALTER TABLE 時就會對整個表進行加鎖。在執(zhí)行 SQL 語句時,也可以明確對某個表加鎖。
給表顯式加鎖語句為:
在使用 MYISAM 引擎時,通常我們不需要手動加鎖,因為 MYISAM 引擎會針對我們的 sql 語句自動進行加鎖,整個過程不需要用戶干預:
查詢語句(select):會自動給涉及的表加讀鎖;
更新語句(update、delete、insert):會自動給涉及的表加寫鎖。
寫鎖
寫鎖,排他鎖 / X 鎖 /exclusive locks。寫鎖的阻塞性比讀鎖要嚴格的多,一個事務(wù)對數(shù)據(jù)添加寫鎖之后,其他的事務(wù)對該數(shù)據(jù),既不能讀取也不能更改。
與讀鎖加鎖的范圍相同,寫鎖既可以加在記錄上,也可以加在表上。
在記錄上加寫鎖
在記錄上加寫鎖,引擎需要使用 InnoDB。
通常普通的 select 語句是不會加鎖的(隔離級別為 Serializable 除外),想要在查詢時添加排他鎖需要使用以下語句:
查詢時加寫鎖:
與加讀鎖相同,寫鎖也是加在索引上的。
更新時加寫鎖:
在表上加寫鎖
顯式給表加寫鎖的語句為:
當引擎選擇 myisam 時,insert/update/delete 語句,會自動給該表加上排他鎖。
讀寫鎖兼容性:
讀鎖是共享的,它不會阻塞其他讀鎖,但會阻塞其他的寫鎖;
寫鎖是排他的,它會阻塞其他讀鎖和寫鎖;
總結(jié):讀讀不互斥,讀寫互斥,寫寫互斥
意向鎖
意向鎖是一種不與行級鎖沖突的表級鎖,表示表中的記錄所需要的鎖 (S 鎖或 X 鎖) 的類型(其實就是告訴你,這張表中已經(jīng)存在了行鎖(行鎖的類型),所以叫意向鎖)。InnoDB 支持多種粒度的鎖,允許行級鎖和表級鎖的共存。
意向鎖分為:
意向共享鎖(IS 鎖):IS 鎖表示當前事務(wù)意圖在表中的行上設(shè)置共享鎖
下面語句執(zhí)行時會首先獲取 IS 鎖,因為這個操作在獲取 S 鎖:獲取 S 鎖:select … lock in share mode
意向排它鎖(IX 鎖):IX 鎖表示當前事務(wù)意圖在表中的行上設(shè)置排它鎖
下面語句執(zhí)行時會首先獲取 IX 鎖,因為這個操作在獲取 X 鎖:獲取 X 鎖:select … for update
事務(wù)要獲取某個表上的 S 鎖和 X 鎖之前,必須先分別獲取對應(yīng)的 IS 鎖和 IX 鎖。
意向鎖有什么作用呢:
如果另一個事務(wù)試圖在該表級別的共享鎖或排它鎖,則受到由第一個事務(wù)控制的表級別意向鎖的阻塞。第二個事務(wù)在鎖定該表前不必檢查各個頁或行鎖,而只需檢查表上的意向鎖。
示例:表 test_user:
事務(wù) A 獲取了某一行的排他鎖,并未提交;
事務(wù) B 想要獲取 test_user 表的表共享鎖;
因為共享鎖與排他鎖互斥,所以事務(wù) B 在試圖對 test_user 表加共享鎖的時候,必須保證:
當前沒有其他事務(wù)持有 users 表的排他鎖(表排他鎖)。
當前沒有其他事務(wù)持有 users 表中任意一行的排他鎖(行排他鎖)。
為了檢測是否滿足第二個條件,事務(wù) B 必須在確保 test_user 表不存在任何排他鎖的前提下,去檢測表中的每一行是否存在排他鎖。很明顯這是一個效率很差的做法,但是有了意向鎖之后,情況就不一樣了:
因為此時事務(wù) A 獲取了兩把鎖:users 表上的意向排他鎖與 id 為 28 的數(shù)據(jù)行上的排他鎖。
事務(wù) B 想要獲取 test_user 表的共享鎖:
事務(wù) B 只需要檢測事務(wù) A 是否持有 test_user 表的意向排他鎖,就可以得知事務(wù) A 必然持有該表中某些數(shù)據(jù)行的排他鎖,那么事務(wù) B 對 test_users 表的加鎖請求就會被排斥(阻塞),從而無需去檢測表中的每一行數(shù)據(jù)是否存在排他鎖。
事務(wù) C 也想獲取 users 表中某一行的排他鎖:
事務(wù) C 檢測到事務(wù) A 持有 test_user 表的意向排他鎖;
意向鎖之間并不互斥,所以事務(wù) C 獲取到了 test_user 表的意向排他鎖;
因為 id 為 31 的數(shù)據(jù)行上不存在任何排他鎖,最終事務(wù) C 成功獲取到了該數(shù)據(jù)行上的排他鎖。
意向鎖與意向鎖之間是不互斥的,但是意向鎖與其他表鎖之間存在一定的兼容互斥,具體如下:
意向鎖之間的兼容互斥性:
意向鎖與普通的排他 / 共享鎖互斥性:
自增鎖
我們在設(shè)計表結(jié)構(gòu)的時候,通常會把主鍵設(shè)置成自增長(思考一下為什么?)。
在 InnoDB 存儲引擎中,針對每個自增長的字段都設(shè)置了一個自增長的計數(shù)器。我們可以執(zhí)行下面的語句來得到這個計數(shù)器的當前值:
當我們進行插入操作的時候,該操作會根據(jù)這個自增長的計數(shù)器的當前值進行 + 1 操作,并賦予自增長的列,這個操作我們稱之為 auto-inc Locking,也就是自增長鎖,這種鎖其實采用的是特殊的表鎖機制,如果 insert 操作出現(xiàn)在一個事務(wù)中,這個鎖是在 insert 操作完成之后立即釋放,而不是等待事務(wù)提交。
按鎖的類型分
全局鎖
所謂全局鎖,其實就是給整個數(shù)據(jù)庫實例加鎖。
數(shù)據(jù)庫實例與數(shù)據(jù)庫是有所區(qū)別的:
數(shù)據(jù)庫,就是保存數(shù)據(jù)的倉庫,具體到 mysql 中,數(shù)據(jù)庫其實是一系列數(shù)據(jù)文件集合(也就是我們通常所說的 database,比如創(chuàng)建數(shù)據(jù)庫語句就是 create database…)。
數(shù)據(jù)庫實例,是指訪問數(shù)據(jù)庫的應(yīng)用程序,在 Mysql 中,就是 mysqld 進程了。
簡單來理解,數(shù)據(jù)庫實例中包含了你創(chuàng)建的各種數(shù)據(jù)庫。
如果給數(shù)據(jù)庫實例加全局鎖會導致整個庫處于只讀狀態(tài)(這是非常危險的)。
一般來說,全局鎖的典型使用場景是用于全庫備份,即把數(shù)據(jù)庫中所有的表都 select 出來。但是要注意,讓整個庫都處于只讀狀態(tài),會導致一些嚴重的問題:
在主庫上加全局鎖,在加鎖期間,不能執(zhí)行任何更新操作,業(yè)務(wù)基本上很多功能都不可用了;
在從庫上加全局鎖,在加鎖期間,不能執(zhí)行主從同步,會導致主從同步延遲。
全局鎖的加鎖語句是:
解除全局鎖的方法是:
斷開執(zhí)行全局鎖的 session 即可;
執(zhí)行解鎖 sql 語句:unlock tables;
如果需要個數(shù)據(jù)庫備份的話,可以使用官方自帶的邏輯備份工具 mysqldump。
既然已經(jīng)有了 dump 工具,為什么還需要 FTWRL 呢?一致性讀是好,但前提是引擎要支持這個隔離級別。比如,MyISAM 這種不支持事務(wù)的引擎。這時,我們就需要使用 FTWRL 命令了。
FTWRL 前有讀寫的話,F(xiàn)TWRL 都會等待讀寫執(zhí)行完畢后才執(zhí)行。
FTWRL 執(zhí)行的時候要刷臟頁的數(shù)據(jù)到磁盤,因為要保持數(shù)據(jù)的一致性,所以執(zhí)行 FTWRL 時候是所有事務(wù)都提交完畢的時候。
全局鎖的實現(xiàn)還是依賴于元數(shù)據(jù)鎖的。
元數(shù)據(jù)鎖
元數(shù)據(jù)鎖(MetaData Lock),也叫 MDL 鎖,是用來保護元數(shù)據(jù)信息,系統(tǒng)級的鎖無法主動控制。在 MySQL5.5 版本,開始引入 MDL 鎖,主要是為了在并發(fā)環(huán)境下對 DDL、DML 同時操作下保持元數(shù)據(jù)的一致性。比如下面這種情況:
隔離級別:RR
如果沒有元數(shù)據(jù)鎖的保護,那么事務(wù) 2 可以直接執(zhí)行 DDL 操作,導致事務(wù) 1 出錯。MYSQL5.5 版本的時候加入 MDL 鎖,是為了保護這種情況的發(fā)生。由于事務(wù) 1 開啟了查詢,那么獲得了元數(shù)據(jù)鎖,鎖的模式為 MDL 讀鎖,事務(wù) 2 要執(zhí)行 DDL,則需獲得 MDL 寫鎖,由于讀寫鎖互斥,所以事務(wù) 2 需要等待事務(wù) 1 釋放掉讀鎖才能執(zhí)行。
對表中的記錄進行增刪改查(DML 操作)的時候,自動加 MDL 讀鎖;
對表的結(jié)構(gòu)(DDL 操作)進行修改的時候,自動加 MDL 寫鎖。
MDL 鎖的粒度
MDL 鎖是 Mysql 服務(wù)器層面中實現(xiàn)的,而不是在存儲引擎插件中實現(xiàn)。按照鎖定的范圍,MDL 鎖可以分為以下幾類:
MDL 鎖的模式
頁級鎖
MySQL 中鎖定粒度介于行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但沖突多,行級沖突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。不同的存儲引擎支持不同的鎖機制。根據(jù)不同的存儲引擎,MySQL 中鎖的特性可以大致歸納如下:
頁級鎖是 MySQL 中比較獨特的一種鎖定級別,應(yīng)用于 BDB 引擎,并發(fā)度一般,頁級鎖定的特點是鎖定顆粒度介于行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的并發(fā)處理能力也同樣是介于上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發(fā)生死鎖。
鎖定粒度大小比較:表級鎖 頁級鎖 行級鎖
表級鎖
表鎖在上文我們已經(jīng)介紹過,相比于行鎖的細粒度加鎖,表鎖是對整張表加鎖。由于是對整張表加鎖,就沒有行鎖的加鎖方式那么復雜,所以加鎖比行鎖快,而且不會出現(xiàn)死鎖的情況(因為事務(wù)是一次性獲取想要加的表表鎖),但是表鎖也存在一些問題:鎖的范圍過大,在并發(fā)比較高的情況下,會導致?lián)屾i的沖突概率變高,這樣并發(fā)性能就大打折扣了。
表鎖的加鎖方式
引擎選擇 MYISAM 時
MYISAM 引擎只支持表鎖,不支持行鎖。
手動添加表級鎖的語句如下:
在使用 MYISAM 引擎時,通常我們不需要手動加鎖,因為 MYISAM 引擎會針對我們的 sql 語句自動進行加鎖,整個過程不需要用戶干預:
查詢語句(select):會自動給涉及的表加讀鎖;
更新語句(update、delete、insert):會自動給涉及的表加寫鎖
引擎選擇 InnoDB 時
InnoDB 引擎同時支持行級鎖和表級鎖,默認為行級鎖。
給 InnoDB 引擎的表手動加鎖,也同樣使用 lock table {tableName} read/write 語句進行讀 / 寫鎖的添加。
除此之外,innodb 還支持一種表級鎖:意向鎖(上文已經(jīng)介紹過)。
總的來說,InnoDB 引擎的表級鎖包含五種鎖模式:
LOCK_IS:表意向讀鎖
LOCK_IX:表意向?qū)戞i
LOCK_S:表讀鎖
LOCK_X:表寫鎖
LOCK_AUTO_INC:自增鎖
行級鎖
在編寫業(yè)務(wù)代碼的過程中,我們接觸最多的就是行級鎖了(表級鎖由于性能問題,一般不推薦使用)。相比于表級鎖,行級鎖具有明顯的性能優(yōu)勢:
沖突少:多線程中訪問不同的記錄時只存在少量鎖定沖突;
鎖的粒度小:可以長時間鎖定單一的行,對其他的行沒有影響,所以并發(fā)度是最高的;
但是使用行鎖時,一旦稍不注意,是非常容易出現(xiàn)死鎖的(表鎖就不存在死鎖現(xiàn)象),所以使用行鎖需要注意加鎖的順序和鎖定的范圍。
InnoDB 的行鎖是通過對索引項加鎖實現(xiàn)的,這表示只有通過索引查詢記錄時才會使用行鎖,如果不走索引查詢數(shù)據(jù)將使用表鎖,則性能會大打折扣。
需要記住:行鎖也叫記錄鎖,記錄鎖都是加在索引上的。
where 條件指定的是主鍵索引:則在主鍵索引上加鎖;
wehre 條件指定的是二級索引:記錄鎖不僅會加在這個二級索引上,還會加在這個二級索引所對應(yīng)的聚簇索引上;
where 條件如果無法走索引:MySQL 會給整張表所有數(shù)據(jù)行加記錄鎖,存儲引擎層將所有記錄返回由 MySQL 服務(wù)端進行過濾。
記錄鎖:LOCK_REC_NOT_GAP(只鎖記錄)
記錄鎖是最簡單的行鎖。比如在 RR 隔離級別時,執(zhí)行 select * from t_user where id = 1 for update 語句時,實際上是對 id = 1 (這里 id 為主鍵)這條記錄上鎖(鎖加在聚簇索引上)。
記錄鎖永遠都是加在索引上的,就算一個表沒有建索引,數(shù)據(jù)庫也會隱式的創(chuàng)建一個索引。如果 WHERE 條件中指定的列是個二級索引,那么記錄鎖不僅會加在這個二級索引上,還會加在這個二級索引所對應(yīng)的聚簇索引上。
注意,如果 SQL 語句無法使用索引時會走主索引實現(xiàn)全表掃描,這個時候 MySQL 會給整張表的所有數(shù)據(jù)行加記錄鎖。
如果一個 WHERE 條件無法通過索引快速過濾,存儲引擎層面就會將所有記錄加鎖后返回,再由 MySQL Server 層進行過濾。在沒有索引時,不僅會消耗大量的鎖資源,增加數(shù)據(jù)庫的開銷,而且極大的降低了數(shù)據(jù)庫的并發(fā)性能,所以說,更新操作一定要記得走索引(因為更新操作會加 X 鎖)。
行級鎖的幾種類型:
間隙鎖:LOCK_GAP(只鎖間隙)
間隙鎖是一種區(qū)間鎖。鎖加在不存在的空閑空間上,或者兩個索引記錄之間,或者第一個索引記錄,或者最后一個索引之后的空間,用來表示只鎖住一段范圍(一般在進行范圍查詢時且隔離級別在 RR 或 Serializable 隔時)。
一般在 RR 隔離級別下會使用到 GAP 鎖。使用 GAP 鎖,主要是為了防止幻讀產(chǎn)生,在被 GAP 鎖鎖住的區(qū)間,不允許插入數(shù)據(jù)或者更新數(shù)據(jù)。
間隙鎖的產(chǎn)生條件:innodb 的隔離級別為 Repeatable Read 或者 Serializable。
間隙鎖的作用范圍說明:
隔離級別:RR
以 Student 表作為樣例數(shù)據(jù),id 為主鍵,stu_code 為學生編號,添加普通索引。
間隙鎖區(qū)域定義:
根據(jù)檢索條件向左尋找最靠近的值 A,作為左區(qū)間,向右尋找最靠近的值 B,作為右區(qū)間,間隙鎖為(A,B)
向左找不到最近的值 A,也是就無窮小,作為左區(qū)間,向右尋找最靠近的值 B,作為右區(qū)間,間隙鎖為(無窮小,B)
向左找到最近的值 A,作為左區(qū)間,向右尋找不到最近的值 B,也就是無窮大,作為右區(qū)間,間隙鎖為(A,無窮大)
區(qū)間(A,B)示例:
事務(wù) 1:
select * from student where stu_code = 4 for update
事務(wù) 2:
insert into student vaues(2, 2, A);insert into student values(4, 5, B
根據(jù)事務(wù) 1 的 sql 語句分析,間隙鎖的范圍是:stu_code = 4 記錄是存在的,所以左區(qū)間為最近的索引值為 stu_code = 3, 右區(qū)間為最近的索引值為 stu_code =7,所以間隙范圍為:(3,7),因此事務(wù) 2 的兩個 insert 語句,一個在范圍外,一個在范圍內(nèi),在范圍外的能插入,而范圍內(nèi)的則阻塞,所以(2,2, A)能插入成功;(4,5, B)插入阻塞。
區(qū)間(無窮小,B)示例:
事務(wù) 1:
select * from student where stu_code = 1 for update
事務(wù) 2:
insert into student vaues(2, 0, c);insert into student vaues(2, 2, r);insert into student vaues(5, 2, o);
根據(jù)事務(wù) 1 的 sql 語句分析,間隙鎖的范圍是:stu_code = 1 是存在的,左邊最近沒有記錄,所以是左邊的無窮小,右邊最近的索引值為 stu_code = 3,所以間隙鎖范圍為:(無窮小,3)。所以事務(wù) 2 的第一個和第二個 insert sql 語句執(zhí)行被阻塞,是在間隙鎖范圍內(nèi)的。第三個 insert sql 語句能執(zhí)行成功,不在間隙鎖范圍內(nèi)。
區(qū)間(A, 無窮大)示例:
事務(wù) 1:
select * from student where stu_code = 7 for update
事務(wù) 2:
insert into student vaues(2, 2, m);insert into student vaues(20, 22, j);
根據(jù)事務(wù) 1 的 sql 語句分析,間隙鎖的范圍是:stu_code = 7 是存在的,左邊最近的索引值為 stu_code = 4,而右邊是沒有索引值的,所以間隙鎖的范圍為:(4,無窮大),第一個 inset 語句能執(zhí)行成功,不在間隙范圍內(nèi);第二個 insert 語句執(zhí)行被阻塞,是在間隙鎖范圍內(nèi)的。
如果查詢語句在數(shù)據(jù)庫中沒有記錄,那該怎么鎖呢?
以上是查詢是有記錄的,如果查詢語句在數(shù)據(jù)庫中沒有記錄,那該怎么鎖呢?咱們繼續(xù)往下:
事務(wù) 1:
update student set stu_name = 000 where stu_code = 10
事務(wù) 2:
insert into student vaues(2, 2, m);insert into student vaues(20, 22, j);
根據(jù)上面的執(zhí)行語句是找不到記錄的,向左取最近的記錄(10,7,‘小明’)作為左區(qū)間,即間隙鎖的范圍是:(7,無窮大),第一個 insert 語句不在區(qū)間范圍內(nèi),能執(zhí)行成功;第二個 insert 執(zhí)行語句在區(qū)間內(nèi)被阻塞,執(zhí)行失敗。如果事務(wù) 1 的 where 條件是大于 10,也是向左找最近的記錄值作為左區(qū)間,所以間隙鎖的范圍也是:(7,無窮大)
總結(jié):間隙鎖產(chǎn)生的條件
RR/Serializable 隔離級別下:Select … Where…For Update 時:
只使用唯一索引查詢,并且只鎖定一條記錄時,InnoDB 會使用行鎖。
只使用唯一索引查詢,但是檢索條件是范圍檢索,或者是唯一檢索然而檢索結(jié)果不存在(試圖鎖住不存在的數(shù)據(jù))時,會產(chǎn)生 Next-Key Lock。
使用普通索引檢索時,不管是何種查詢,只要加鎖,都會產(chǎn)生間隙鎖。
同時使用唯一索引和普通索引時,由于數(shù)據(jù)行是優(yōu)先根據(jù)普通索引排序,再根據(jù)唯一索引排序,所以也會產(chǎn)生間隙鎖。
下一鍵鎖:LOCK_ORDINARY,也稱 Next-Key Lock
Next-Key 鎖是 record lock + gap lock 的組合。和間隙鎖一樣,在 RC 隔離級別下沒有 Next-key 鎖(除非通過修改配置強制開啟),只有 RR/Serializable 隔離級別才有。
MySQL InnoDB 工作在可重復讀隔離級別(RR)下,并且會以 Next-Key Lock 的方式對數(shù)據(jù)行進行加鎖,這樣可以有效防止幻讀的發(fā)生。Next-Key Lock 是行鎖和間隙鎖的組合,當 InnoDB 掃描索引記錄的時候,會首先對索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖之后,其他事務(wù)就不能在這個間隙修改或者插入記錄。
當查詢的索引含有唯一屬性(唯一索引,主鍵索引)時,Innodb 存儲引擎會對 next-key lock 進行優(yōu)化,將其降為 record lock,即僅鎖住索引本身,而不是范圍。
插入意向鎖:LOCK_INSERT_INTENSION
插入意向鎖,插入記錄時使用,是一種特殊的間隙鎖。這個鎖表示插入的意向,只有在執(zhí)行 insert 語句的時候才會有這個鎖。
假設(shè)有索引記錄的值分別是 id = 1 和 id = 5(1 到 5 之間沒有記錄),單獨的事務(wù)分別嘗試插入 id = 2 和 id = 3,在獲得插入行的排它鎖之前,每個事務(wù)都是用插入意圖鎖來鎖定 1 和 5 之間的空間,但是不會相互阻塞。因為插入意向鎖之間是不會沖突的。
插入意向鎖會跟間隙鎖或者 Next-Key 鎖沖突:間隙鎖的作用是鎖住區(qū)間防止其他事務(wù)插入數(shù)據(jù)導致幻讀。
在上面的場景中,假設(shè)提前有事務(wù) A 獲取了 id 在(1,5)區(qū)間的間隙鎖,那么事務(wù) B 嘗試插入 id = 2 時,會先嘗試獲取插入意向鎖,但是由于插入意向鎖和間隙鎖沖突,導致插入失敗,也就避免了幻讀產(chǎn)生。
到此,相信大家對“MYSQL 中鎖的模式與類型有哪些”有了更深的了解,不妨來實際操作一番吧!這里是丸趣 TV 網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!