MySQL鎖機制


本文參考自MySQL官網5.6版本參考手冊的14.5.1,此小節說明MySQL的鎖分類,此外還有14.5.2小節和14.5.3小節詳述事務隔離級別和各SQL語句的加鎖模式,后兩節將單獨寫2篇筆記。
 
第一部分:概述
Myisam的鎖比較容易理解,無論是讀還是寫都只會加表鎖,表鎖又分為read鎖和write鎖,可以使用如下方式手動加鎖:
--加表鎖語句(同樣適用於InnoDB):
lock tables
    tbl_name [[AS] alias] lock_type
    [, tbl_name [[AS] alias] lock_type] ...
lock_type:
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE
或者
flush tables with read lock; #其實加的不是表鎖而是針對所有表的一個全局讀鎖。
--解表鎖語句:
unlock tables;
--如何觀察表的元數據鎖:
show open tables [FROM db_name] [like_or_where]

Myisam的read、write表鎖其實可以看做一種元數據鎖,這種鎖對其他存儲引擎例如innodb表也可以加。

由於Myisam這樣的鎖機制,導致Myisam是一款讀性能較好,並發寫性能較差的存儲引擎,本文主要討論如今的MySQL默認存儲引擎InnoDB的鎖機制。
 
第二部分:InnoDB鎖分類
--如何觀察InnoDB鎖:
set @@global.innodb_status_output_locks=on; 
--這樣show engine innodb status\G可以顯示InnoDB額外的鎖信息(鎖太多時也無法完全顯示),標准情況下只顯示鎖數目,不過如果請求的鎖被阻塞那么標准情況下也會顯示請求的鎖的信息。

InnoDB沒有頁鎖,只有表鎖行鎖

一、InnoDB表鎖有以下幾種:
InnoDB也可以使用lock tables ... read/write來添加元數據表鎖。(其實是取決於innodb_table_locks參數,默認為1表示innodb可以感知mysql層的表鎖
InnoDB支持的事務表鎖有:
S :即lock tables ... read添加的S鎖。
X :即lock tables ... write添加的X鎖。
IS:表級意向共享鎖,即表示事務有向底層資源加共享行鎖的意向。如select ... lock in share mode語句,在加行鎖之前會在表上現加IS鎖,這樣可以提高鎖沖突檢測的效率,同時也可以避免事務在表級添加會使其他事務行鎖失效的表級鎖。
IX:表級意向獨占鎖,即表示事務有向底層資源加獨占行鎖的意向。一般來說delete、update語句和select ... for update語句都會在加行鎖之前先加表級IX鎖,除非未用到索引(此時直接加表級X鎖)。
表鎖的兼容性圖:
此外表級鎖還有一種比較特殊的鎖:AUTO-INC Locks
這種鎖只在向自增主鍵中插入記錄時出現,由於自增主鍵在MySQL中較為常見,因此也算是經常會遇到的鎖,這種鎖是為自增主鍵設計的,無需和以上4鍾鎖檢測沖突。
AUTO-INC Locks的鎖機制:
在向自增主鍵中插入記錄時,其他insert事務都需要等待直到本事務的插入完成才能繼續插入自增記錄,注意是插入完成而不是本事務完成。這很好理解,因為需要保證自增主鍵的連貫性。但是如果你有超高的插入並發,那么肯定會帶來性能問題。
因此InnoDB也提供了折中的方案,innodb_autoinc_lock_mode參數可以控制你是否使用這種鎖,如果你的自增主鍵不需要嚴格連貫而且需要更高的insert並發,那么可以禁用掉這種鎖。
但是如果你做了主從復制,而且使用的是statement模式的binlog,那么禁用innodb_autoinc_lock_mode后可能造成主從自增主鍵不一致,尤其是遇到insert ... select ... from table_name;這種語句。此時需要改為row模式或mixed模式的binlog主從復制,因為row模式對SQL執行順序不敏感,而mixed模式也會將可能影響主從復制的statement改為row模式傳輸。
那么最后還有個問題就是既需要超高插入並發又需要連貫自增,那該怎么辦?
涼拌~
 
二、InnoDB行鎖有以下四種:
Ps:四種行鎖在show engine innodb status\G里的顯示分別是:
Record lock:RECORD LOCKS <rec物理位置和事務id等信息> lock_mode <鎖模式:XorS> locks rec but not gap [waiting]  --如果處於等待狀態就會有waiting后綴
Gap lock:RECORD LOCKS <rec物理位置和事務id等信息> lock mode <鎖模式:XorS> locks gap before rec
Next-key lock:RECORD LOCKS <rec物理位置和事務id等信息> lock_mode <鎖模式:XorS> [waiting]
插入意向鎖(Insert Intention Locks):RECORD LOCKS <rec具體位置> lock_mode <鎖模式:X/S> locks gap before rec insert intention [waiting]

 示例里帶waiting是因為innodb_status_output_locks參數未開啟時這些鎖都只在請求被阻塞時會顯示,正好我是用的一個有大量鎖等待的庫來示例的,所以帶了waiting.

1.Record lock
即在索引記錄上加的行鎖,lock_mode分為S和X兩種模式。
例如:Start transaction;SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;就會c1列的索引上添加X類型的Record lock。 
-- 請注意默認事務隔離級別下select for update雖然是鎖定讀,但是會自動提交導致鎖立馬釋放,所以必須開啟事務或者設置@@session_autocommit=0才能看到行鎖。
Record lock一定是加在索引記錄上的,即便是一個沒有定義主鍵的表,InnoDB也會創建一個隱式的聚集索引GEN_CLUST_INDEX,在用到此主鍵索引時加Record lock。
2.Gap lock
即間隙鎖,官方定義是:Gap lock用於鎖定2個索引記錄之間、或第一個索引記錄之前、或最后一個索引記錄之后的范圍,即:鎖定不存在的索引記錄,也因此如果要查詢的索引是單列的唯一索引,那么是不會出現gap鎖的(多列唯一索引除非查詢條件包含了所有索引列,否則依然可能出現gap鎖)。
通常我們會把Record lock和其之前的Gap lock合起來稱為Next-key lock,這點在Next-key lock部分解釋。Gap鎖很不容易被觀察到,基本都是在某些死鎖情況下才能看到,一般阻塞時都是以next-key的形式出現。
之所以設計Gap lock主要是為了解決幻讀問題的,參考SQL Server的鍵范圍鎖,所謂幻讀就是防止同一個事務內兩次讀到的結果集數目不一樣,其避免幻讀的原理就是通過gap鎖阻止在指定范圍內新插入記錄。
Gap鎖是可以禁用的,你可以將數據庫的全局隔離級別設置為read committed或者將innodb_locks_unsafe_for_binlog(未來版本會棄用)參數設置為1來禁用Gap lock,只是這樣就會出現幻讀,不過幻讀一般並不是什么大問題,比如Oracle數據庫的默認隔離級別下就無法避免幻讀。如果你想禁用gap鎖,請參考官網關於事務隔離級別的頁面以便厘清其優劣。
另外必須要說的是:
同一個gap上的Gap lock的S和X模式效果完全一樣的,就算你加了一個X模式的gap lock,其他事務也能在同一個gap上再加一個X模式的gap lock而不會阻塞,因為gap鎖被設計的唯一目(only purpose)就是為了防止其他事務向gap插入數據的,或者說其唯一目的就是為了防止幻讀。
mysql允許在gap上存在互斥鎖的原因主要是當gap依賴的索引記錄被刪除時這些gap鎖會被合並(這句的官網原文為:The reason conflicting gap locks are allowed is that if a record is purged from an index, the gap locks held on the record by different transactions must be merged)。
3.Next-key lock
即Record lock和Gap lock的合體,不過next-key中的gap必須是索引記錄前的gap,記錄之后的gap不能與這個gap之前的key合並為next-key。一個特例是如果鎖定的gap范圍沒有上限那么會用一個名為supremum的偽上限代表無窮大,那么此虛擬key值的行鎖和小於此值的gap也可以組合為一個next-key鎖。所以在內存中看到supremum就要想到是這種next-key鎖。
一個示例:
假設有如下表,此表無主鍵i列上有索引:
mysql> select * from t;
+------+
| i    |
+------+
|    1 |
|    4 |
|    5 |
|    6 |
|    9 |
+------+
mysql> set @@session.autocommit=0;
mysql> set @@global.innodb_status_output_locks=on; 
mysql> select * from t where i between 6 and 11 for update;
# show engine innodb status\G 的事務部分顯示的加鎖模式如下:
---TRANSACTION 598438, ACTIVE 5 sec
3 lock struct(s), heap size 1136, 5 row lock(s) --顯示一共有5個行鎖(lock strcuts的含義我至今不明白,先放着)
MySQL thread id 163, OS thread handle 140513269520128, query id 538 localhost root
TABLE LOCK table `test`.`t` trx id 598438 lock mode IX  --一個表級意向鎖,沒啥問題
RECORD LOCKS space id 686 page no 4 n bits 80 index IX_1 of table `test`.`t` trx id 598438 lock_mode X  --某位置有如下3個next-key鎖
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;  -- 這是一個包含了上限supremum的next-key鎖

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000006; asc     ;;
 1: len 6; hex 000000000825; asc      %;;

Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000009; asc     ;;
 1: len 6; hex 000000000826; asc      &;;

RECORD LOCKS space id 686 page no 3 n bits 80 index GEN_CLUST_INDEX of table `test`.`t` trx id 598438 lock_mode X locks rec but not gap  --這里有2個GEN_CLUST_INDEX的非gap行鎖
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 6; hex 000000000826; asc      &;;
 1: len 6; hex 00000009219d; asc     ! ;;
 2: len 7; hex ee000001430110; asc     C  ;;
 3: len 4; hex 80000009; asc     ;;

Record lock, heap no 6 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 6; hex 000000000825; asc      %;;
 1: len 6; hex 00000009219c; asc     ! ;;
 2: len 7; hex ed0000016b0110; asc     k  ;;
 3: len 4; hex 80000006; asc     ;;

 上述5個行鎖中,前3個鎖定的范圍是IX_1索引上的:(5,6]、(6,9]、(9,supremum],后2個鎖定的范圍是GEN_CLUST_INDEX的6和9兩個記錄對應的主鍵。

4.插入意向鎖(Insert Intention Locks
這個鎖也是一個InnoDB的奇葩例子,不知道大家發現沒InnoDB在談IX IS還有行鎖這些鎖的時候基本不用insert語句來舉例,這點如果是熟悉Oracle和SQL Server的人就會很困惑,因為增刪改全都是DML語句,大家加鎖機制基本相似的,無非就是表級意向鎖+頁級or行級鎖的套路,但是InnoDB不是這樣!!!insert語句和delete、update完全不是一路人!!關於Insert語句的加鎖模式可以參考http://www.cnblogs.com/leohahah/p/8863422.html中的INSERT說明部分。
這個鎖用於表明:只要不是插入相同的index record,多個事務向同一個gap插入記錄是不會沖突的。雖然插入意向鎖之間不會互相阻塞,但是插入意向鎖與涉及本區間的行鎖們可是不兼容的,會互相阻塞,這也是造成死鎖的一大主因。
Insert語句的基本加鎖模式為:表級IX鎖--行級插入意向鎖--行級鎖(行級插入意向鎖釋放)。
插入意向鎖其實是行級別的一種意向gap鎖(從show engine innodb status的輸出也可以看出),既然有意向兩字那么可以認定就是用於檢測鎖沖突的,是為在行級別獲取X模式的record lock鎖提前做檢測。
用一個例子來解釋更為明了:
--會話A執行:
CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
INSERT INTO child (id) values (90),(102);
START TRANSACTION;
SELECT * FROM child WHERE id > 100 FOR UPDATE;
--會話B執行:
INSERT INTO child (id) VALUES (101);
可以看到會話B被阻塞了,而show engine innodb status\G看到的鎖等待如下:
即insert語句想在(90,102)的gap上加個lock_mode=X的gap鎖,也就是Insert Intention Lock,但是會話A的select for update語句已經在(90,102]的gap上添加了X模式的next-key鎖,要獲取的插入意向鎖與此鎖不兼容,於是被阻塞無法獲取。
 
三、Innodb內存鎖
除以上事務鎖之外,innodb還有內存鎖,可以分為兩大類:Mutex和rw-locks,統稱為latches。
  • An s-lock provides read access to a common resource.

  • An x-lock provides write access to a common resource while not permitting inconsistent reads by other threads.

  • An sx-lock provides write access to a common resource while permitting inconsistent reads by other threads. sx-locks were introduced in MySQL 5.7 to optimize concurrency and improve scalability for read-write workloads.

rw-lock在5.6之前只包含S和X兩種模式,5.7之后新增了SX模式,其兼容機制如下:
 
四、總結
MySQL的鎖機制基本就如上所示了,但是了解InnoDB鎖只是初步的,還必須結合事務隔離級別的概念去判斷各種SQL的具體加鎖機制,因為事務隔離級別會影響SQL的默認加鎖模式。
MySQL的事務隔離級別定義也是遵循ANSI SQL92標准的,不過但凡是家數據庫廠商都會說自己遵循SQL92標准,而事實是早已加料加的面目全非。當然這全都是為了能夠提供更好的並發性能。例如Oracle也說自己遵循SQL92標准,結果四大隔離級別只支持2個,SQL Server也說自己支持,結果又多造出來2個事務隔離級別。
同樣的MySQL也提供了4大基本的事務隔離級別,不同的隔離級別下加鎖機制區別很大,參考:http://www.cnblogs.com/leohahah/p/8857124.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM