帶你一文理清MySQL的各種鎖
一、MySQL的鎖指什么
我們先來(lái)看一下Mysql的基本構(gòu)成,如下圖,以前我們其實(shí)也介紹過(guò),MySQL數(shù)據(jù)庫(kù)總體分為三個(gè)部分:
- Server層:負(fù)責(zé)處理客戶端連接、查詢解析和優(yōu)化、數(shù)據(jù)訪問(wèn)控制、事務(wù)處理、日志、replication和其他管理操作。
- 存儲(chǔ)引擎:負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和檢索等操作。MySQL支持多個(gè)存儲(chǔ)引擎,如InnoDB、MyISAM、MEMORY等。
- 物理磁盤(pán)層:真正存儲(chǔ)數(shù)據(jù)的位置,保存著數(shù)據(jù)庫(kù)數(shù)據(jù)以及各類日志。
我們?nèi)粘Uf(shuō)的MySQL的各種鎖,有的是由存儲(chǔ)引擎提供的,有些鎖是有Server層提供的),它們結(jié)合起來(lái)才形成了完整的鎖的體系
二、排他與共享
鎖從不同角度理解,其實(shí)會(huì)有不同的分類,也就是說(shuō)同一個(gè)東西,在不同的分類下叫不同的鎖,但其實(shí)它們并不沖突。在討論 MySQL 的鎖時(shí),一般我們會(huì)有這樣兩種分類:
- 鎖的粒度
- 是否排他
從鎖的粒度來(lái)看,可以分為全局鎖
、表鎖
和行鎖
,顧名思義,鎖的層級(jí)分別是數(shù)據(jù)庫(kù)、某張表、以及表里的行。
從是否獨(dú)占來(lái)看,可以分為排它鎖
、共享鎖
在innoDB,對(duì)于同一個(gè)資源,是允許設(shè)定 排它鎖(X) 和 共享鎖(S) 兩種的,它們的兼容關(guān)系如下:
X | S | |
---|---|---|
X | 沖突 | 沖突 |
S | 沖突 | 兼容 |
比如事務(wù)A對(duì)某一行上了共享鎖(S),此時(shí)再來(lái)一個(gè)事務(wù)B,如果B需要這個(gè)資源的共享鎖,那么它能立即獲得。如果B需要該資源的排它鎖,事務(wù)B就需要等待了。
而不同粒度的鎖,接下來(lái)我們以一一講解
三、全局鎖(Global Lock)
用于 限制整個(gè)數(shù)據(jù)庫(kù)實(shí)例 的訪問(wèn),當(dāng)執(zhí)行一些需要全局一致性的操作時(shí),例如備份、恢復(fù)等,可以使用全局鎖,如下命令
FLUSH TABLES WITH READ LOCK
這個(gè)命令會(huì)對(duì)所有數(shù)據(jù)庫(kù)的所有表都上一個(gè)讀鎖,加完這個(gè)鎖后,所有的表都會(huì)被鎖定從而無(wú)法插入任何內(nèi)容了(mysql自己的系統(tǒng)日志表不在此列)
四、表鎖(Table Lock)
鎖定整個(gè)表,在執(zhí)行涉及整個(gè)表的操作時(shí),會(huì)對(duì)整個(gè)表進(jìn)行鎖定,避免其他事務(wù)對(duì)該表進(jìn)行并發(fā)操作。它的上鎖及解鎖語(yǔ)法如下
-- 加鎖 LOCK {TABLE | TABLES} tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] ... lock_type: { READ [LOCAL] | WRITE } -- 釋放鎖 UNLOCK {TABLE | TABLES}
一般情況下,不建議使用這種表鎖,除非是對(duì)一組MyISAM的表來(lái)操作,那么提前將它們鎖定會(huì)提高效率
五、意向鎖(Intention Locks)
InnoDB 支持多粒度鎖,允許行鎖和表鎖共存。例如下面的語(yǔ)句就可以在指定的表上使用排他鎖(X鎖):
LOCK TABLES … WRITE
而為了同時(shí)避免遍歷行鎖的困境:比如事務(wù)A獲取了某一行的排它鎖,此時(shí)事務(wù)B想鎖表,就必須遍歷每一行檢查是否有鎖;同時(shí)也為了避免不同粒度間的鎖出現(xiàn)死鎖,所以InnoDB使用了意向鎖。意向鎖是表級(jí)別的鎖,它指示事務(wù)以后對(duì)表中的某一行需要哪種類型的鎖(共享的還是排他的)。有兩種類型的意向鎖:
- 意向共享鎖(IS)表明事務(wù)打算對(duì)表中的單個(gè)行設(shè)置共享鎖。
- 意向排他鎖(IX)表示事務(wù)打算對(duì)表中的單個(gè)行設(shè)置排他鎖。
例如,SELECT…FOR SHARE 設(shè)置IS鎖,SELECT…FOR UPDATE 設(shè)置一個(gè)IX鎖。
在事務(wù)獲得表中某一行的共享鎖之前,它必須首先獲得表上的IS鎖或更強(qiáng)的鎖。
在事務(wù)獲得表中某一行的排他鎖之前,它必須首先獲得該表上的IX鎖
表級(jí)鎖類型兼容性如下表所示:
X | IX | S | IS | |
---|---|---|---|---|
X | 沖突 | 沖突 | 沖突 | 沖突 |
IX | 沖突 | 兼容 | 沖突 | 兼容 |
S | 沖突 | 沖突 | 兼容 | 兼容 |
IS | 沖突 | 兼容 | 兼容 | 兼容 |
這個(gè)表的理解其實(shí)很簡(jiǎn)單:
(1)X 作為表的排他鎖,和其他所有表鎖沖突
(2)IX 代表準(zhǔn)備修改某一行,此時(shí)如果有人持有表,不論是 X 還是 S,為了防止沖突或不一致,所以會(huì)返回沖突
如果鎖與現(xiàn)有鎖兼容,則將鎖授予請(qǐng)求事務(wù),但如果它與現(xiàn)有鎖沖突,則不會(huì)授予。事務(wù)等待,直到?jīng)_突的現(xiàn)有鎖被釋放。如果鎖請(qǐng)求與現(xiàn)有鎖沖突,并且由于會(huì)導(dǎo)致死鎖而無(wú)法授予,則會(huì)發(fā)生錯(cuò)誤。
意向鎖的主要目的是顯示某人正在鎖定表中的一行,或者將要鎖定表中的一行,作為行鎖前置的隱式鎖。也就是說(shuō)若你僅使用表鎖,或僅使用行鎖,意向鎖是不會(huì)讓你阻塞的。只有你在持有行鎖的情況下又使用表鎖,它才能發(fā)揮它的用處
六、行級(jí)鎖(Row Lock)
在InnoDB存儲(chǔ)引擎中,默認(rèn)使用的是行級(jí)鎖。它可以實(shí)現(xiàn)更細(xì)粒度的并發(fā)控制,只鎖定部分行,而不是整個(gè)表,提高了并發(fā)性能。而從實(shí)際表現(xiàn)來(lái)說(shuō),其又分為三種 記錄鎖(Record Locks)
、間隙鎖(Gap Locks)
,以及所謂臨鍵鎖(Next-Key Locks)
。實(shí)際上在源碼中,這三種鎖其實(shí)叫 LOCK_REC_NOT_GAP、LOCK_GAP、LOCK_ORDINARY
/* Precise modes /
/* this flag denotes an ordinary next-key lock in contrast to LOCK_GAP or
LOCK_REC_NOT_GAP /
constexpr uint32_t LOCK_ORDINARY = 0;/* when this bit is set, it means that the lock holds only on the gap before
the record; for instance, an x-lock on the gap does not give permission to
modify the record on which the bit is set; locks of this type are created
when records are removed from the index chain of records /
constexpr uint32_t LOCK_GAP = 512;/* this bit means that the lock is only on the index record and does NOT
block inserts to the gap before the index record; this is used in the case
when we retrieve a record with a unique key, and is also used in locking
plain SELECTs (not part of UPDATE or DELETE) when the user has set the READ
COMMITTED isolation level */
constexpr uint32_t LOCK_REC_NOT_GAP = 1024;
1. 記錄鎖(Record Locks)
記錄鎖是索引記錄上的鎖,例如,
SELECT c1 FROM t WHERE c1=10 For update;
這樣就能阻止任何其他事務(wù)插入、更新或刪除c1是10為的行。
記錄鎖總是鎖定索引,即使定義的表沒(méi)有索引也是如此。對(duì)于這樣的情況,InnoDB創(chuàng)建一個(gè)隱藏的聚集索引,并使用該索引進(jìn)行記錄鎖定
2. 間隙鎖(Gap Locks)
如果我們把隔離級(jí)別設(shè)定為可重復(fù)讀(RR),MySQL就會(huì)為我們解決”幻讀“現(xiàn)象,這里面除了MVCC的作用外,還會(huì)在此時(shí)引入”間隙鎖“的概念,間隙鎖本質(zhì)上是 在索引記錄之間的間隙上的鎖,或者在第一個(gè)索引記錄之前或最后一個(gè)索引記錄之后的間隙上的鎖。比如說(shuō)當(dāng)我們執(zhí)行這樣一個(gè)SQL時(shí)
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 For Update
就會(huì)防止其他事務(wù)將值15插入到列t.c1中,因?yàn)榉秶兴鞋F(xiàn)有值之間的間隙被鎖定。
3. 臨鍵鎖(Next-Key Locks)
next-key鎖是索引記錄上的記錄鎖和索引記錄之前的間隙上的間隙鎖的組合。InnoDB執(zhí)行行級(jí)鎖的方式是,當(dāng)它搜索或掃描一個(gè)表索引時(shí),它會(huì)在遇到的索引記錄上設(shè)置共享鎖或排他鎖。因此,行級(jí)鎖實(shí)際上是索引記錄鎖。索引記錄上的next-key鎖也會(huì)影響該索引記錄之前的“間隙”。也就是說(shuō),next-key鎖是索引記錄鎖加上索引記錄前面的間隙鎖。
假設(shè)索引包含值10、11、13和20。此索引的Next-Key Locks鎖定則覆蓋以下區(qū)間,其中圓括號(hào)表示不包含區(qū)間端點(diǎn),方括號(hào)表示包含端點(diǎn):
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
對(duì)于最后一個(gè)間隔,next-key鎖鎖定索引中最大值以上的間隙,以及值高于索引中任何實(shí)際值的“supremum”偽記錄。”supremum“不是一個(gè)真正的索引記錄,因此,實(shí)際上,這個(gè)next-key鎖只鎖定最大索引值后面的間隙。默認(rèn)情況下,InnoDB運(yùn)行在REPEATABLE READ事務(wù)隔離級(jí)別。在這種情況下,InnoDB使用next-key鎖進(jìn)行搜索和索引掃描,這可以防止幻行。
4. 插入意向鎖(Insert Intention Locks)
學(xué)習(xí)了上面的間隙鎖,我們不難知曉,想要插入新數(shù)據(jù)通常需要先獲得間隙鎖(隔離級(jí)別為可重復(fù)讀及以上)。按照常理來(lái)說(shuō),如果有多個(gè)事務(wù)都想往一個(gè)間隙里插入數(shù)據(jù),它們只要慢慢排隊(duì)就好了,但是,我們還需要意識(shí)到,一旦事務(wù)A插入一條數(shù)據(jù)成功,原先的間隙可能就會(huì)變成兩個(gè)間隙,事務(wù)B可能又被A新誕生的這個(gè)間隙所阻礙。為了優(yōu)化這種插入排隊(duì)的情況,innodb提出了插入意向鎖
的概念:
An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This lock
signals the intent to insert in such a way that multiple transactions inserting into the same index gap
need not wait for each other if they are not inserting at the same position within the gap插入意圖鎖是insert操作在行插入之前設(shè)置的一種間隙鎖。這個(gè)鎖以這樣一種方式表示插入的意圖,即插入到相同索引間隙中的多個(gè)事務(wù)如果不在間隙內(nèi)的相同位置插入,則不需要彼此等待
也就是說(shuō),插入操作在獲取間隙鎖之前,會(huì)先獲取到插入意向鎖,下面的示例演示了一個(gè)事務(wù)在獲得插入記錄的排他鎖之前使用插入意圖鎖。該示例涉及兩個(gè)客戶機(jī)A和b??蛻魴C(jī)A創(chuàng)建一個(gè)包含兩個(gè)索引記錄(90和102)的表,然后啟動(dòng)一個(gè)事務(wù),對(duì)ID大于100的索引記錄設(shè)置排他鎖。排他鎖包括記錄102之前的間隙鎖:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB; mysql> INSERT INTO child (id) values (90),(102); mysql> START TRANSACTION; mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE; +-----+ | id | +-----+ | 102 | +-----+
客戶端B開(kāi)始一個(gè)事務(wù),向缺口間隙插入一條記錄,事務(wù)在等待獲得排他鎖時(shí)接受插入意圖鎖:
mysql> START TRANSACTION; mysql> INSERT INTO child (id) VALUES (101);
在SHOW ENGINE INNODB STATUS和INNODB monitor輸出中,插入意圖鎖的事務(wù)數(shù)據(jù)如下所示:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child` trx id 8731 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000066; asc f;; 1: len 6; hex 000000002215; asc " ;; 2: len 7; hex 9000000172011c; asc r ;;...
5. 行鎖總結(jié)
其實(shí)對(duì)于行鎖,現(xiàn)在解析的文章有很多,我們后續(xù)也可能結(jié)合源碼進(jìn)行更深入的解釋。但在這里我們可以先進(jìn)行總結(jié)(源于Mysql源碼版本8.0.30)
- 鎖基于掃描到的索引,對(duì)于innoDB,哪怕你沒(méi)有顯示建立索引,每一張表也都至少會(huì)有一個(gè)索引(即其聚集索引)
- 間隙鎖(Gap Locks) 只有在隔離級(jí)別為
可重復(fù)讀
以上才會(huì)有 - 隔離級(jí)別為
讀未提交
/讀提交
時(shí),行級(jí)鎖默認(rèn)使用記錄鎖(Record Locks),隔離級(jí)別為可重復(fù)讀
/序列化
時(shí),行級(jí)鎖默認(rèn)使用的是臨鍵鎖(Next-Key Locks) - 間隙鎖(Gap Locks) 的目的是不讓這段間隙內(nèi)插入新的數(shù)據(jù),因此其設(shè)計(jì)與傳統(tǒng)意義上的
共享
/排他
概念不一樣。比如:事務(wù)A在一個(gè)間隙上持有共享間隙鎖(間隙s鎖),而事務(wù)B可以在同一個(gè)間隙上持有排他性間隙鎖(間隙x鎖)
七、自增鎖(AUTO-INC Locks)
AUTO-INC鎖是一種特殊的表級(jí)鎖
,用于在具有AUTO_INCREMENT列的表中插入事務(wù)。
在最簡(jiǎn)單的情況下,如果一個(gè)事務(wù)正在向表中插入值,那么任何其他事務(wù)都必須等待對(duì)該表進(jìn)行自己的插入,以便第一個(gè)事務(wù)插入的行接收連續(xù)的主鍵值。innodb_autoinc_lock_mode變量控制用于自動(dòng)增量鎖定的算法。它允許您選擇如何在可預(yù)測(cè)的自動(dòng)遞增值序列和插入操作的最大并發(fā)性之間進(jìn)行權(quán)衡。
這里有 0、1、2 三種模式可選
0(traditional):這是最常用的模式。在這種模式下,InnoDB使用一個(gè)全局的互斥鎖(AUTO-INC鎖)來(lái)保護(hù)自增主鍵的訪問(wèn)。當(dāng)有一個(gè)事務(wù)插入新記錄時(shí),其他事務(wù)必須等待該事務(wù)釋放AUTO-INC鎖后才能插入新記錄,該鎖通常保持到語(yǔ)句結(jié)束(而不是事務(wù)結(jié)束)。
1(consecutive):在這種模式下,InnoDB使用一個(gè)遞增的互斥鎖(AUTO-INC鎖)來(lái)保護(hù)自增主鍵的訪問(wèn)。如果我們可以預(yù)知插入的數(shù)據(jù)條數(shù),InnoDB會(huì)為每個(gè)事務(wù)分配一個(gè)獨(dú)立的自增值區(qū)間。當(dāng)一個(gè)事務(wù)需要插入自增值時(shí),它就可以在自己的區(qū)間內(nèi)找到一個(gè)可用的自增值,然后將其插入到表中。通過(guò)為每個(gè)事務(wù)分配獨(dú)立的自增值區(qū)間,Consecutive模式可以實(shí)現(xiàn)并行插入,這種模式適用于具有高并發(fā)讀寫(xiě)的應(yīng)用,可以提高性能。
2(interleaved):這種模式是在MySQL 8.0中引入的新模式。在這種模式下,事務(wù)們都不再持有表級(jí)AUTO-INC鎖,該模式可以支持多個(gè)語(yǔ)句可以同時(shí)生成數(shù)字,也就是說(shuō),數(shù)字的分配在多個(gè)語(yǔ)句之間交錯(cuò)進(jìn)行。
需要注意的是,在我們使用它不同種類的插入語(yǔ)句的時(shí)候,consecutive 自增鎖有可能還是會(huì)使用全局互斥鎖的樣子,因?yàn)椴皇敲恳环N insert 都能提前預(yù)知其數(shù)量的,比如下面這樣的插入:
INSERT INTO t1 (c2) SELECT ... from another table ...
總結(jié)
到此這篇關(guān)于MySQL各種鎖的文章就介紹到這了,更多相關(guān)MySQL各種鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MYSQL8 通過(guò)ibd文件恢復(fù)表數(shù)據(jù)的方法
這篇文章主要介紹了MYSQL8 通過(guò)ibd文件恢復(fù)表數(shù)據(jù)的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01mysql存儲(chǔ)過(guò)程與函數(shù)學(xué)習(xí)與實(shí)踐方式
下面小編就為大家分享一篇mysql存儲(chǔ)過(guò)程與函數(shù)學(xué)習(xí)與實(shí)踐方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12淺談MySQL中g(shù)roup_concat()函數(shù)的排序方法
下面小編就為大家?guī)?lái)一篇淺談MySQL中g(shù)roup_concat()函數(shù)的排序方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12如何用SQL命令查看Mysql數(shù)據(jù)庫(kù)大小
本篇文章是對(duì)用SQL命令查看Mysql數(shù)據(jù)庫(kù)大小的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06MySQL5.7中 performance和sys schema中的監(jiān)控參數(shù)解釋(推薦)
在MySQL5.7中,performance schema有很大改進(jìn),包括引入大量新加入的監(jiān)控項(xiàng)、降低占用空間和負(fù)載,以及通過(guò)新的sys schema機(jī)制顯著提升易用性。下面通過(guò)本文給大家介紹 MySQL5.7中 performance和sys schema中的監(jiān)控參數(shù)解釋,需要的朋友可以參考下2017-08-08