帶你一文理清MySQL的各種鎖
一、MySQL的鎖指什么
我們先來看一下Mysql的基本構成,如下圖,以前我們其實也介紹過,MySQL數據庫總體分為三個部分:
- Server層:負責處理客戶端連接、查詢解析和優(yōu)化、數據訪問控制、事務處理、日志、replication和其他管理操作。
- 存儲引擎:負責數據的存儲和檢索等操作。MySQL支持多個存儲引擎,如InnoDB、MyISAM、MEMORY等。
- 物理磁盤層:真正存儲數據的位置,保存著數據庫數據以及各類日志。
我們日常說的MySQL的各種鎖,有的是由存儲引擎提供的,有些鎖是有Server層提供的),它們結合起來才形成了完整的鎖的體系
二、排他與共享
鎖從不同角度理解,其實會有不同的分類,也就是說同一個東西,在不同的分類下叫不同的鎖,但其實它們并不沖突。在討論 MySQL 的鎖時,一般我們會有這樣兩種分類:
- 鎖的粒度
- 是否排他
從鎖的粒度來看,可以分為全局鎖
、表鎖
和行鎖
,顧名思義,鎖的層級分別是數據庫、某張表、以及表里的行。
從是否獨占來看,可以分為排它鎖
、共享鎖
在innoDB,對于同一個資源,是允許設定 排它鎖(X) 和 共享鎖(S) 兩種的,它們的兼容關系如下:
X | S | |
---|---|---|
X | 沖突 | 沖突 |
S | 沖突 | 兼容 |
比如事務A對某一行上了共享鎖(S),此時再來一個事務B,如果B需要這個資源的共享鎖,那么它能立即獲得。如果B需要該資源的排它鎖,事務B就需要等待了。
而不同粒度的鎖,接下來我們以一一講解
三、全局鎖(Global Lock)
用于 限制整個數據庫實例 的訪問,當執(zhí)行一些需要全局一致性的操作時,例如備份、恢復等,可以使用全局鎖,如下命令
FLUSH TABLES WITH READ LOCK
這個命令會對所有數據庫的所有表都上一個讀鎖,加完這個鎖后,所有的表都會被鎖定從而無法插入任何內容了(mysql自己的系統(tǒng)日志表不在此列)
四、表鎖(Table Lock)
鎖定整個表,在執(zhí)行涉及整個表的操作時,會對整個表進行鎖定,避免其他事務對該表進行并發(fā)操作。它的上鎖及解鎖語法如下
-- 加鎖 LOCK {TABLE | TABLES} tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] ... lock_type: { READ [LOCAL] | WRITE } -- 釋放鎖 UNLOCK {TABLE | TABLES}
一般情況下,不建議使用這種表鎖,除非是對一組MyISAM的表來操作,那么提前將它們鎖定會提高效率
五、意向鎖(Intention Locks)
InnoDB 支持多粒度鎖,允許行鎖和表鎖共存。例如下面的語句就可以在指定的表上使用排他鎖(X鎖):
LOCK TABLES … WRITE
而為了同時避免遍歷行鎖的困境:比如事務A獲取了某一行的排它鎖,此時事務B想鎖表,就必須遍歷每一行檢查是否有鎖;同時也為了避免不同粒度間的鎖出現(xiàn)死鎖,所以InnoDB使用了意向鎖。意向鎖是表級別的鎖,它指示事務以后對表中的某一行需要哪種類型的鎖(共享的還是排他的)。有兩種類型的意向鎖:
- 意向共享鎖(IS)表明事務打算對表中的單個行設置共享鎖。
- 意向排他鎖(IX)表示事務打算對表中的單個行設置排他鎖。
例如,SELECT…FOR SHARE 設置IS鎖,SELECT…FOR UPDATE 設置一個IX鎖。
在事務獲得表中某一行的共享鎖之前,它必須首先獲得表上的IS鎖或更強的鎖。
在事務獲得表中某一行的排他鎖之前,它必須首先獲得該表上的IX鎖
表級鎖類型兼容性如下表所示:
X | IX | S | IS | |
---|---|---|---|---|
X | 沖突 | 沖突 | 沖突 | 沖突 |
IX | 沖突 | 兼容 | 沖突 | 兼容 |
S | 沖突 | 沖突 | 兼容 | 兼容 |
IS | 沖突 | 兼容 | 兼容 | 兼容 |
這個表的理解其實很簡單:
(1)X 作為表的排他鎖,和其他所有表鎖沖突
(2)IX 代表準備修改某一行,此時如果有人持有表,不論是 X 還是 S,為了防止沖突或不一致,所以會返回沖突
如果鎖與現(xiàn)有鎖兼容,則將鎖授予請求事務,但如果它與現(xiàn)有鎖沖突,則不會授予。事務等待,直到沖突的現(xiàn)有鎖被釋放。如果鎖請求與現(xiàn)有鎖沖突,并且由于會導致死鎖而無法授予,則會發(fā)生錯誤。
意向鎖的主要目的是顯示某人正在鎖定表中的一行,或者將要鎖定表中的一行,作為行鎖前置的隱式鎖。也就是說若你僅使用表鎖,或僅使用行鎖,意向鎖是不會讓你阻塞的。只有你在持有行鎖的情況下又使用表鎖,它才能發(fā)揮它的用處
六、行級鎖(Row Lock)
在InnoDB存儲引擎中,默認使用的是行級鎖。它可以實現(xiàn)更細粒度的并發(fā)控制,只鎖定部分行,而不是整個表,提高了并發(fā)性能。而從實際表現(xiàn)來說,其又分為三種 記錄鎖(Record Locks)
、間隙鎖(Gap Locks)
,以及所謂臨鍵鎖(Next-Key Locks)
。實際上在源碼中,這三種鎖其實叫 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;
這樣就能阻止任何其他事務插入、更新或刪除c1是10為的行。
記錄鎖總是鎖定索引,即使定義的表沒有索引也是如此。對于這樣的情況,InnoDB創(chuàng)建一個隱藏的聚集索引,并使用該索引進行記錄鎖定
2. 間隙鎖(Gap Locks)
如果我們把隔離級別設定為可重復讀(RR),MySQL就會為我們解決”幻讀“現(xiàn)象,這里面除了MVCC的作用外,還會在此時引入”間隙鎖“的概念,間隙鎖本質上是 在索引記錄之間的間隙上的鎖,或者在第一個索引記錄之前或最后一個索引記錄之后的間隙上的鎖。比如說當我們執(zhí)行這樣一個SQL時
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 For Update
就會防止其他事務將值15插入到列t.c1中,因為范圍中所有現(xiàn)有值之間的間隙被鎖定。
3. 臨鍵鎖(Next-Key Locks)
next-key鎖是索引記錄上的記錄鎖和索引記錄之前的間隙上的間隙鎖的組合。InnoDB執(zhí)行行級鎖的方式是,當它搜索或掃描一個表索引時,它會在遇到的索引記錄上設置共享鎖或排他鎖。因此,行級鎖實際上是索引記錄鎖。索引記錄上的next-key鎖也會影響該索引記錄之前的“間隙”。也就是說,next-key鎖是索引記錄鎖加上索引記錄前面的間隙鎖。
假設索引包含值10、11、13和20。此索引的Next-Key Locks鎖定則覆蓋以下區(qū)間,其中圓括號表示不包含區(qū)間端點,方括號表示包含端點:
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
對于最后一個間隔,next-key鎖鎖定索引中最大值以上的間隙,以及值高于索引中任何實際值的“supremum”偽記錄。”supremum“不是一個真正的索引記錄,因此,實際上,這個next-key鎖只鎖定最大索引值后面的間隙。默認情況下,InnoDB運行在REPEATABLE READ事務隔離級別。在這種情況下,InnoDB使用next-key鎖進行搜索和索引掃描,這可以防止幻行。
4. 插入意向鎖(Insert Intention Locks)
學習了上面的間隙鎖,我們不難知曉,想要插入新數據通常需要先獲得間隙鎖(隔離級別為可重復讀及以上)。按照常理來說,如果有多個事務都想往一個間隙里插入數據,它們只要慢慢排隊就好了,但是,我們還需要意識到,一旦事務A插入一條數據成功,原先的間隙可能就會變成兩個間隙,事務B可能又被A新誕生的這個間隙所阻礙。為了優(yōu)化這種插入排隊的情況,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操作在行插入之前設置的一種間隙鎖。這個鎖以這樣一種方式表示插入的意圖,即插入到相同索引間隙中的多個事務如果不在間隙內的相同位置插入,則不需要彼此等待
也就是說,插入操作在獲取間隙鎖之前,會先獲取到插入意向鎖,下面的示例演示了一個事務在獲得插入記錄的排他鎖之前使用插入意圖鎖。該示例涉及兩個客戶機A和b。客戶機A創(chuàng)建一個包含兩個索引記錄(90和102)的表,然后啟動一個事務,對ID大于100的索引記錄設置排他鎖。排他鎖包括記錄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開始一個事務,向缺口間隙插入一條記錄,事務在等待獲得排他鎖時接受插入意圖鎖:
mysql> START TRANSACTION; mysql> INSERT INTO child (id) VALUES (101);
在SHOW ENGINE INNODB STATUS和INNODB monitor輸出中,插入意圖鎖的事務數據如下所示:
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. 行鎖總結
其實對于行鎖,現(xiàn)在解析的文章有很多,我們后續(xù)也可能結合源碼進行更深入的解釋。但在這里我們可以先進行總結(源于Mysql源碼版本8.0.30)
- 鎖基于掃描到的索引,對于innoDB,哪怕你沒有顯示建立索引,每一張表也都至少會有一個索引(即其聚集索引)
- 間隙鎖(Gap Locks) 只有在隔離級別為
可重復讀
以上才會有 - 隔離級別為
讀未提交
/讀提交
時,行級鎖默認使用記錄鎖(Record Locks),隔離級別為可重復讀
/序列化
時,行級鎖默認使用的是臨鍵鎖(Next-Key Locks) - 間隙鎖(Gap Locks) 的目的是不讓這段間隙內插入新的數據,因此其設計與傳統(tǒng)意義上的
共享
/排他
概念不一樣。比如:事務A在一個間隙上持有共享間隙鎖(間隙s鎖),而事務B可以在同一個間隙上持有排他性間隙鎖(間隙x鎖)
七、自增鎖(AUTO-INC Locks)
AUTO-INC鎖是一種特殊的表級鎖
,用于在具有AUTO_INCREMENT列的表中插入事務。
在最簡單的情況下,如果一個事務正在向表中插入值,那么任何其他事務都必須等待對該表進行自己的插入,以便第一個事務插入的行接收連續(xù)的主鍵值。innodb_autoinc_lock_mode變量控制用于自動增量鎖定的算法。它允許您選擇如何在可預測的自動遞增值序列和插入操作的最大并發(fā)性之間進行權衡。
這里有 0、1、2 三種模式可選
0(traditional):這是最常用的模式。在這種模式下,InnoDB使用一個全局的互斥鎖(AUTO-INC鎖)來保護自增主鍵的訪問。當有一個事務插入新記錄時,其他事務必須等待該事務釋放AUTO-INC鎖后才能插入新記錄,該鎖通常保持到語句結束(而不是事務結束)。
1(consecutive):在這種模式下,InnoDB使用一個遞增的互斥鎖(AUTO-INC鎖)來保護自增主鍵的訪問。如果我們可以預知插入的數據條數,InnoDB會為每個事務分配一個獨立的自增值區(qū)間。當一個事務需要插入自增值時,它就可以在自己的區(qū)間內找到一個可用的自增值,然后將其插入到表中。通過為每個事務分配獨立的自增值區(qū)間,Consecutive模式可以實現(xiàn)并行插入,這種模式適用于具有高并發(fā)讀寫的應用,可以提高性能。
2(interleaved):這種模式是在MySQL 8.0中引入的新模式。在這種模式下,事務們都不再持有表級AUTO-INC鎖,該模式可以支持多個語句可以同時生成數字,也就是說,數字的分配在多個語句之間交錯進行。
需要注意的是,在我們使用它不同種類的插入語句的時候,consecutive 自增鎖有可能還是會使用全局互斥鎖的樣子,因為不是每一種 insert 都能提前預知其數量的,比如下面這樣的插入:
INSERT INTO t1 (c2) SELECT ... from another table ...
總結
到此這篇關于MySQL各種鎖的文章就介紹到這了,更多相關MySQL各種鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MySQL5.7中 performance和sys schema中的監(jiān)控參數解釋(推薦)
在MySQL5.7中,performance schema有很大改進,包括引入大量新加入的監(jiān)控項、降低占用空間和負載,以及通過新的sys schema機制顯著提升易用性。下面通過本文給大家介紹 MySQL5.7中 performance和sys schema中的監(jiān)控參數解釋,需要的朋友可以參考下2017-08-08