MySQL 中的鎖理解
鎖的型別
MySQL 找那個根據加鎖的範圍,大致可以分成全域性鎖,表級鎖和行級鎖。
全域性鎖
全域性鎖,就是對整個資料庫加鎖。
加鎖
flush tables with read lock
解鎖
unlock tables
全域性鎖會讓整個庫處於只讀狀態,之後所有的更新操作都會被阻塞:
-
資料更新語句(資料的增刪改);
-
資料定義語句(包括建表、修改表結構等)和更新類事務的提交語句。
缺點
如果對主庫加鎖,那麼執行期間就不能執行更新,業務基本上就停擺了;
如果對從庫加鎖,那麼執行期間,從庫就不能執行主庫同步過來的 binlog,會導致主從延遲。
適用範圍
全域性鎖的典型使用場景是,做全庫邏輯備份。也就是把整庫每個表都select出來存成文字。
不過為什麼要在備份的時候加鎖,不加鎖的話,備份系統備份的得到的庫不是一個邏輯時間點,這個檢視是邏輯不一致的。
官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用引數 –single-transaction
的時候,導資料之前就會啟動一個事務,來確保拿到一致性檢視。而由於 MVCC 的支援,這個過程中資料是可以正常更新的。
對於 MyISAM 這種不支援事務的引擎,mysqldump 工具就不能用了,所以 全域性鎖 雖然缺點很多,但是還是有存在的必要。
表級鎖
MySQL 中的表級別的鎖包括:表鎖,後設資料鎖(meta data lock,MDL)。
比如 InnoDB 中的意向鎖和自增鎖(AUTO-INC Locks)也都是表級別的鎖。
下面來一一分析下
表鎖
表鎖,就是會鎖定整張表,它是 MySQL 中最基本的鎖策略,並不依賴於儲存引擎,被大部分的 MySQL 引擎支援,MyISAM 和 InnoDB 都支援表級鎖,由於表級鎖一次會將整個表鎖定,所以可以很好的避免死鎖問題。當然,鎖的粒度大所帶來最大的負面影響就是出現鎖資源爭用的機率也會最高,導致併發率大打折扣。
表級別的鎖,和全域性鎖一樣可以使用 unlock tables
主動釋放鎖,也可以在客戶端斷開的時候自動釋放鎖。
加鎖
// 表級別的共享鎖,也就是讀鎖
lock tables t1 read
// 表級別的排它鎖,也就是寫鎖
lock tables t1 write
釋放鎖
unlock tables
表鎖除了會限制其它執行緒的讀寫,還會限制當前執行緒接下來的操作。
如果一個執行緒加了表級別的讀鎖,其他執行緒對該表的寫操作,都會被阻塞,同時當前執行緒接下來對該表的寫入操作也不能執行,會報錯當前表有一個讀鎖,直到表鎖的讀鎖被釋放。
後設資料鎖
MDL(metadata lock) 後設資料也是表級別的鎖。
MDL 鎖主要使用來維護表後設資料的資料一致性,MDL 不需要顯式使用,在訪問一個表的時候會被自動加上,避免在進行讀取或者寫入的時候,其它執行緒對資料表做出修改,造成寫入或者讀取的結果異常。
因此,在 MySQL 5.5
版本中引入了MDL,當對一個表做增刪改查操作的時候,加MDL讀鎖;當要對錶做結構變更操作的時候,加 MDL 寫鎖。
-
讀鎖之間不互斥,因此你可以有多個執行緒同時對一張表增刪改查;
-
讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。因此,如果有兩個執行緒要同時給一個表加欄位,其中一個要等另一個執行完才能開始執行。
給一個表加欄位會可能會導致整個庫崩掉,為什麼呢?
來分析下上面的栗子
1、session A
首先啟動事務,使用了一個查詢,因為是查詢,所以對 user 表會加一個 MDL 讀鎖;
2、session B
因為也是查詢,所以也是加 MDL 讀鎖,讀讀不互斥,所以改該查詢就正常進行了;
3、session C
是一個對錶新增欄位的操作,會加 MDL 寫鎖,因為 session A
中的讀鎖還沒有提交,讀寫互斥,該執行緒就會被阻塞了;
4、session D
也是一個讀鎖,不過因為 session C
加了一個寫鎖,這時候 session D
的讀鎖會被 session C
阻塞,因為申請 MDL 鎖的操作會形成一個佇列,佇列中寫鎖獲取優先順序高於讀鎖,一旦出現 MDL 寫鎖等待,會阻塞後續該表的所有 CRUD 操作。這樣後面申請 MDL 讀鎖都會被阻塞,因為對錶的增刪改查操作都需要先申請MDL讀鎖,基本上這個表就完全不能讀寫了。
如果某個表上的查詢語句頻繁,而且客戶端有重試機制,也就是說超時後會再起一個新 session 再請求的話,這個庫的執行緒很快就會爆滿。
MDL 鎖會需要等到事務提交的時候才會被釋放,這樣在給表加欄位的時候遇到一個長事務,就可能會導致資料表崩掉。
如何安全的對資料表新增欄位呢?
設定 alter table
語句中的等待時間,如果在指定時間沒有拿到 MDL 寫鎖,直接退出,不至於長時間阻塞後面的業務操作。失敗,就後面多嘗試幾次。
意向鎖
InnoDB 儲存引擎支援多粒度鎖,這種鎖定允許事務在行級別上的鎖和表級別上的鎖同時存在。這種鎖就是意向鎖。
意向鎖有兩種:
1、意向共享鎖:在使用 InnoDB 引擎的表裡對某些記錄加上「共享鎖」之前,需要先在表級別加上一個「意向共享鎖」;
2、意向獨佔鎖:在使用 InnoDB 引擎的表裡對某些紀錄加上「獨佔鎖」之前,需要先在表級別加上一個「意向獨佔鎖」;
意向鎖的作用?
意向鎖是放置在資源層次結構的一個級別上的鎖,以保護較低階別資源上的共享鎖或排它鎖。
意向共享鎖和意向獨佔鎖是表級鎖,不會和行級的共享鎖和獨佔鎖發生衝突,同時意向鎖之間也不會發生衝突。只會和表級別的共享鎖和表級獨佔鎖發生衝突。
意向鎖是 InnoDB 自動加的,不需要使用者干預。這樣一個事務在表中一些記錄加獨佔鎖,InnoDB 就會自動加表級別的意向鎖獨佔鎖,這樣其他的事務如果對該表加表級別的獨佔鎖,就不用遍歷表裡面的記錄,透過表級別的意向鎖直接就能判斷當前事務是夠會被阻塞了。
簡單的講就是意向鎖是為了快速判斷,表裡面是否有記錄被加鎖。
自增鎖
自增鎖(AUTO-INC)是一種表級鎖,專門針對插入 AUTO_INCREMENT 型別的列。可以該列的值,資料庫會自動賦值自增的值,這主要就是透過自增鎖實現的。一般會在主鍵中設定 AUTO_INCREMENT。
自增鎖(AUTO-INC)採用的是一種特殊的表鎖機制,為了提高插入的效能,鎖不是在一個事務完成後才釋放,而是在完成是自增長值插入的 SQL 語句後立即釋放。
不過自增鎖(AUTO-INC)在進行大量插入的時候,另一個事務中的插入會被阻塞。從 MySQL 5.1.22 版本開始,InnoDB 提供了一種輕量級互斥的自增實現機制,大大提高了自增插入的效能。
透過 innodb_autoinc_lock_mode 來控制鎖的型別。
innodb_autoinc_lock_mode | 說明 |
---|---|
0 | 採用 AUTO-INC 鎖,執行語句結束後釋放鎖,這種方式鎖粒度大,比較重 |
1 | 針對批次插入採用 AUTO-INC 鎖,針對簡單插入採用輕量級的互斥鎖,如果當前有事務進行批次的資料插入,後面的簡單插入需要等待前面 AUTO-INC 鎖的釋放才可以插入,這種方式可以保證同一 insert 語句插入的自增 ID 都是連續的 |
2 | 所有的插入操作都使用輕量的互斥鎖,鎖的粒度小,多條語句插入存在競爭,自增長的值可能不連續 |
不過當 innodb_autoinc_lock_mode = 2
搭配 binlog 的日誌格式是 statement 一起使用的時候,在主從複製的場景
中會發生資料不一致的問題。下面來分析下
首先來看下 binlog 的格式
binlog 有三種格式:
1、Statement(Statement-Based Replication,SBR):每一條會修改資料的 SQL 都會記錄在 binlog 中,裡面記錄的是執行的 SQL;
Statement 模式只記錄執行的 SQL,不需要記錄每一行資料的變化,因此極大的減少了 binlog 的日誌量,避免了大量的 IO 操作,提升了系統的效能。
正是由於 Statement 模式只記錄 SQL,而如果一些 SQL 中包含了函式,那麼可能會出現執行結果不一致的情況。
比如說 uuid() 函式,每次執行的時候都會生成一個隨機字串,在 master 中記錄了 uuid,當同步到 slave 之後,再次執行,就獲取到另外一個結果了。
所以使用 Statement 格式會出現一些資料一致性問題。
2、Row(Row-Based Replication,RBR):不記錄 SQL 語句上下文資訊,僅僅只需要記錄某一條記錄被修改成什麼樣子;
Row 格式的日誌內容會非常清楚的記錄下每一行資料修改的細節,這樣就不會出現 Statement 中存在的那種資料無法被正常複製的情況。
比如一個修改,滿足條件的資料有 100 行,則會把這 100 行資料詳細記錄在 binlog 中。當然此時,binlog 檔案的內容要比第一種多很多。
不過 Row 格式也有一個很大的問題,那就是日誌量太大了,特別是批次 update、整表 delete、alter 表等操作,由於要記錄每一行資料的變化,此時會產生大量的日誌,大量的日誌也會帶來 IO 效能問題。
3、Mixed(Mixed-Based Replication,MBR):Statement 和 Row 的混合體。
在 Mixed 模式下,系統會自動判斷該用 Statement 還是 Row:一般的語句修改使用 Statement 格式儲存 binlog;對於一些 Statement 無法準確完成主從複製的操作,則採用 Row 格式儲存 binlog。
下面分析下 當 innodb_autoinc_lock_mode = 2
搭配 binlog 的日誌格式是 statement 一起使用的時候,在主從複製的場景
中為什麼會發生資料不一致。
CREATE TABLE `t` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;
session A | session B |
---|---|
insert into t values(null,1,1) insert into t values(null,2,2) insert into t values(null,3,3) insert into t values(null,4,4) |
|
create table t2 like t; | |
insert into t2 values(null,5,5) | insert into t2(c,d) select c,d from t; |
分析下上面語句的執行
首先 session A
先寫入了4條資料,然後 session B
建立了相同的 t2 表。
接下來 session A
和 session B
在相同的時刻寫入資料到表 t 中。
因為 innodb_autoinc_lock_mode = 2
插入語句在申請萬自增主鍵之後就會馬上釋放自增鎖,不需要等待插入語句執行完成。
那麼就可能出現下面的情況
1、session B
首先插入語句 (1,1,1),(2,2,2),(3,3,3);
2、session A
申請到了自增的 id = 4,插入資料 (4,5,5);
3、session B
繼續執行插入資料 (5,4,4)。
這樣看下來沒什麼影響,表 t 中的資料也都插入到了 t2 中,只是主鍵 ID 有點不同。
當 binlog_format=statement
的時候在來看下 binlog 是如何同步從庫的資料。
因為兩個 session 是同時插入資料的,binlog 對錶 t2 的更新日誌只會有兩種情況,先記錄 session A
的或者先記錄 session B
的,同時 binlog 在從庫中的資料執行,也都是順序性的,生成的id都是連續的,不會出現主庫中,兩個 session 並行間隙插入的情況,這樣就會出現從庫和主庫資料不一致的情況。
如何解決呢?可以設定 binlog 的型別為 row,這樣 binlog 會把插入資料的操作都如實記錄進來,到備庫執行的時候,不再依賴於自增主鍵去生成,同時 innodb_autoinc_lock_mode 設定為 2。
對於普通的 insert 語句裡面包含多個 value 值,即使 innodb_autoinc_lock_mode 設定為 1,也不會等語句執行完成才釋放鎖。因為這類語句在申請自增 id 的時候,是可以精確計算出需要多少個 id 的,然後一次性申請,申請完成後鎖就可以釋放了。
對於批次的資料插入,類似 insert … select、replace … select
和 load data
語句。這種是不能這樣操作的,因為不知道預先要申請多少個 ID。
批次的資料插入,如果一個個的申請 id,不僅速度很慢,同時也會影響插入的效能,這肯定是不合適的。
因此,對於批次插入資料的語句,MySQL有一個批次申請自增id的策略:
1、語句執行過程中,第一次申請自增id,會分配1個;
2、1個用完以後,這個語句第二次申請自增id,會分配2個;
3、2個用完以後,還是這個語句,第三次申請自增id,會分配4個;
4、依此類推,同一個語句去申請自增id,每次申請到的自增id個數都是上一次的兩倍。
insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);
create table t2 like t;
insert into t2(c,d) select c,d from t;
insert into t2 values(null, 5,5);
insert…select
,實際上往表 t2 中插入了 4 行資料。但是,這四行資料是分三次申請的自增 id,第一次申請到了 id=1
,第二次被分配了 id=2
和 id=3
, 第三次被分配到 id=4
到 id=7
。
由於這條語句實際只用上了 4 個 id,所以 id=5
到 id=7
就被浪費掉了。之後,再執行 insert into t2 values(null, 5,5)
,實際上插入的資料就是(8,5,5)
。
所以總結下來資料插入,主鍵 ID 不連續的情況大概有下面幾種:
1、事務回滾,事務在執行過程中出錯,主鍵衝突,或者主動發生回滾,會導致已經申請的自增 ID 被棄用;
2、批次資料插入的插入最佳化,批次資料插入 MySQL 會有一個批次的預申請自增 ID 的策略。
行鎖
MySQL 的行鎖是在引擎層由各個引擎自己實現的,但並不是所有的引擎都支援行鎖的,比如 MyISAM 引擎就不支援行鎖,InnoDB 是支援行鎖的,這也是 MyISAM 被 InnoDB 替代的重要原因之一。
下面主要來介紹下 InnoDB 中的行鎖。
行鎖主要有下面三類:
1、Record Lock,記錄鎖,也就是僅僅把一條記錄鎖上;
2、Gap Lock,間隙鎖,鎖定一個範圍,但是不包含記錄本身;
3、Next-Key Lock:Record Lock + Gap Lock 的組合,鎖定一個範圍,並且鎖定記錄本身。
Record Lock
Record Lock
記錄鎖,這個很好理解,比如事務 A 更新了一行,而這時候事務 B 也更新了同一行,則必須等待事務 A 的操作完成才能更新。
在 InnoDB 事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放。
所以當事務中需要鎖多個行,要把最可能造成鎖衝突。最可能影響併發度的鎖儘量往後放。
同時記錄鎖還分成讀鎖和寫鎖:
共享鎖(S鎖)也叫讀鎖,滿足讀讀共享,讀寫互斥。
獨佔鎖(X鎖)也叫寫鎖,滿足寫寫互斥、讀寫互斥。
如果一個事務 A 對一條資料加了讀鎖,那麼另外一個事務 B 如果同樣也加了讀鎖,這兩個事務不會互斥,都能正常讀取,如果事務 B 加的是寫鎖,那麼事務 B 就需要等待事務 A 的讀鎖釋放之後,才能操作加鎖。
如果一個事務 A 對一條資料加了寫鎖,那麼其他的事務對這條資料加鎖,無論是讀鎖還是寫鎖都需要等待事務 A 釋放才能繼續加鎖。
如何加鎖
//對讀取的記錄加共享鎖
select ... lock in share mode;
//對讀取的記錄加獨佔鎖
select ... for update;
不過需要注意的是當一條記錄被加了記錄鎖,其它事務如果只是簡單的查詢,沒有使用當前讀,那麼是不會被阻塞的,因為會透過 MVCC 找到當前可讀取的版本資料,直接返回資料即可。
Gap Lock
間隙鎖(Gap Lock)是 Innodb 在可重複讀提交下為了解決幻讀問題時引入的鎖機制。
幻讀的問題存在是因為新增或者更新操作,這時如果進行範圍查詢的時候(加鎖查詢),會出現不一致的問題,這時使用普通的行鎖已經沒有辦法滿足要求,需要對一定範圍內的資料進行加鎖,間隙鎖就是解決這類問題的。
如上圖,上面加了 id 範圍為 (4,6)
的間隙鎖 ,那麼其他事務進行 id 為 5 的主鍵資料插入就會被阻塞,直到間隙鎖被釋放。
間隙鎖之間是相容的,兩個事務之間可以共同持有包含相同間隙的間隙鎖,同時也不存在互斥關係,間隙鎖的目的只是為了解決幻讀問題而提出的。
Next-Key Lock
Next-Key Lock
,是 Record Lock + Gap Lock
的組合,鎖定一個範圍,並且鎖定記錄本身。
如果對 id 範圍為 (4,6]
加了 Next-Key Lock
,除了 id 為 5 的主鍵資料插入就會被阻塞,同時 id 為 5 的主鍵資料修改也會被阻塞。
插入意向鎖
插入意向鎖是一種間隙鎖形式的意向鎖,在真正執行 INSERT 操作之前設定。
一個事務在資料插入的是時候會判斷插入的位置是否加了被其它的事務加了間隙鎖或臨鍵鎖。如果有的話,就會阻塞,直到間隙鎖或臨鍵鎖被釋放才能執行後面的插入。
因為插入意向鎖也是一種意向鎖,意向鎖只是表示一種意向,所以插入意向鎖之間不會互相沖突,多個插入操作同時插入同一個 gap 時,無需互相等待。
加鎖的原則
分析完了 MySQL 中幾個主要的鎖,再來看下這幾個鎖在 MySQL 中的使用。
這裡引用下丁奇大佬在極客時間專欄中的總結
MySQL 後面的版本可能會改變加鎖策略,這個原則是在下面版本中總結的,5.x系列<=5.7.24,8.0系列 <=8.0.13
。
加鎖原則:
1、加鎖的物件是索引,加鎖的基本單位是 next-key lock
,是前開後閉區間;
2、只有語句查詢過程中訪問到的物件才會加鎖;
加鎖最佳化:
1、索引中的等值查詢,如果加鎖的物件是唯一索引,這時候鎖就從 next-key lock
,變成行鎖了,因為只需要鎖住對應的一行就行了;
2、索引中的等值查詢,索引是唯一索引,查詢的值沒有找到,或者索引是普通索引 就會鎖住一個範圍,向右遍歷最後一個不滿足條件的值,並將其鎖住,這時候next-key lock
,就會變成 Gap Lock
了,這個條件是確定加鎖的右邊範圍;
同時,唯一索引上的範圍查詢會訪問到不滿足條件的第一個值為止。
下面來幾個栗子來具體的分析下
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
1、主鍵等值查詢
session A | session B | session C |
---|---|---|
begin update t set d=d+1 where id=7 |
||
insert into t values(8,8,8) (blocked) |
||
update t set d=d+1 where id=10 |
可以看到 session B
中的插入操作被阻塞了,下面來分析下
session A
:
1、加鎖的物件是索引,加鎖的基本單位是 next-key lock
,首先會加 next-key lock
(5,10];
2、又因為 id = 7
這行資料,資料庫中不存在,索引中的查詢,如果沒有找到對應的資料,就會鎖住滿足條件的範圍資料,向右遍歷最後一個不滿足條件的值,並將其鎖住,這時候next-key lock
,就會變成 Gap Lock
了;
所以 session A
首先會加一把 (5,10)
的間隙鎖。
session B
:
因為是資料插入操作,會加插入意向鎖,因為 (5,10)
已經被 session A
鎖住了,所以 id = 8
的資料就不能插入了,會被阻塞。
session C
:
session C
因為 id = 10
這行資料是存在的,並且 id 為主鍵索引。根據上面的加鎖原則,首先加鎖的基本單位是 next-key lock
,索引中的等值查詢,如果加鎖的物件是唯一索引,這時候鎖就從 next-key lock
,變成行鎖了。
2、非唯一索引等值查詢
session A | session B | session C | session D |
---|---|---|---|
begin select id from t where c=5 lock in share mode |
|||
update t set d=d+1 where id=5 | |||
insert into t values(7,7,7) (blocked) |
insert into t values(2,2,2) (blocked) |
可以看到 session C
被阻塞了,下面來分析下
session A
:
首先 session A
加了一個 id = 5
的讀鎖。
1、加鎖的基本單位是 next-key lock
,首先會加 next-key lock
(0,5]
;
2、索引中的等值查詢,索引是唯一索引,查詢的值沒有找到,或者索引是普通索引 就會鎖住一個範圍,向右遍歷最後一個不滿足條件的值,並將其鎖住,這時候next-key lock
,就會變成 Gap Lock
了, (5,10)
同樣也會被鎖住;
這樣加鎖的範圍就是 (0,10)
session B
:
只有訪問到的物件才會加鎖,session A
的查詢使用覆蓋索引,並不需要訪問主鍵索引,所以主鍵索引上上的查詢不會被阻塞。
session C
和 session B=D
:
兩個插入操作,都會加插入意向鎖,因為間隙 (0,10)
被 session A
鎖住了,所以插入操作就會被阻塞了。
3、主鍵索引範圍鎖
例如下面的栗子
select * from t where id=10 for update;
select * from t where id>=10 and id<11 for update;
上面這兩個查詢,看起來是等價的,其實他們加鎖的方式是不同的,下面來分析下
session A | session B | session C |
---|---|---|
begin select * from t where id>=10 and id <11 for update |
||
insert into t values(8,8,8) (Query OK) insert into t values(13,13,13) (blocked) |
||
update t set d=d+1 where id=15 (blocked) |
可以看到上面插入的 id=13 的資料會被阻塞,還有 id=15 的資料修改也會被阻塞,下面來分析下
session A
首先 id>=10 這個條件,按照加鎖的基本物件首先加 next-key lock
(5,10]
,因為 id 是主鍵,等值查詢會退化成行鎖,所以 next-key lock
(5,10]
就會退化成 id=10
的行鎖;
同時因為是範圍查詢,向右查詢,所以右邊界會找到 15,會加 next-key lock
(10,15]
;
所以加的鎖就是 (10,15]
的 next-key lock
還有 id=10
的行鎖。
4、非唯一索引範圍查詢
session A | session B | session C |
---|---|---|
begin select * from t where c>=10 and c <11 for update |
||
insert into t values(8,8,8) (blocked) |
||
update t set d=d+1 where id=15 (blocked) |
可以看到上面插入的 id=13 的資料會被阻塞,還有 id=15 的資料修改也會被阻塞,下面來分析下
session A
按照加鎖的基本物件首先加 c 的 next-key lock
(5,10]
,因為 c 是普通索引,等值查詢不會退化成行鎖;
同時因為是範圍查詢,向右查詢,所以右邊界會找到 15,會加 c 的 next-key lock
(10,15]
;
所以 session A
會加索引 c 上的 (5,10]
和 (10,15]
這兩個 next-key lock
。
5、非唯一索引等值查詢
假定目前表中的資料見下文,有兩條 c=10
的資料。
來看下下面的查詢栗子
session A | session B | session C |
---|---|---|
begin select * from t where c=10 for update |
||
insert into t values(12,12,12) (blocked) |
||
update t set d=d+1 where id=15 |
可以看到上面 session B
中 id=12
的資料插入被阻塞了,來分析下原因
session A
查詢的條件是 c=10
,上面的圖示可以看到 c=10
的書有兩條;
首先加鎖的基本單位是 next-key lock
,所以會加一個 (c=5,id=5)
到 (c=10,id=10)
的 next-key lock
;
因為這是個等值查詢的索引,索引中的等值查詢,索引是唯一索引,查詢的值沒有找到,或者索引是普通索引 就會鎖住一個範圍,向右遍歷最後一個不滿足條件的值,並將其鎖住,這時候next-key lock
,就會變成 Gap Lock
了,所以 (c=10,id=10)
到 (c=15,id=15)
也會被加一個間隙鎖。
所以 session A
中的鎖就是 (c=5,id=5)
到 (c=15,id=15)
的間隙鎖。
6、limit 語句加鎖
session A | session B |
---|---|
begin select * from t where c=10 limit 2 for update |
|
insert into t values(12,12,12) |
session A
中加入了 limit 查詢,還是栗子5 中的插入語句,這時候 session B
的插入就不會被阻塞了。
因為有 limit 2
的限制,因此在遍歷到 (c=10, id=13)
這一行之後,滿足條件的語句已經有兩條,迴圈就結束了。
因此,索引c上的加鎖範圍就變成了從(c=5,id=5)
到 (c=10,id=13)
這個前開後閉區間。
所以業務中如果加入 limit 條件,能夠減小鎖的範圍。
總結
鎖大概分成三類 全域性鎖,表級鎖和行鎖。
加鎖原則:
1、加鎖的物件是索引,加鎖的基本單位是 next-key lock,是前開後閉區間;
2、只有語句查詢過程中訪問到的物件才會加鎖;
加鎖最佳化:
1、索引中的等值查詢,如果加鎖的物件是唯一索引,這時候鎖就從 next-key lock,變成行鎖了,因為只需要鎖住對應的一行就行了;
2、索引中的等值查詢,索引是唯一索引,查詢的值沒有找到,或者索引是普通索引 就會鎖住一個範圍,向右遍歷最後一個不滿足條件的值,並將其鎖住,這時候 next-key lock,就會變成 Gap Lock 了,這個條件是確定加鎖的右邊範圍;
同時,唯一索引上的範圍查詢會訪問到不滿足條件的第一個值為止。
參考
【高效能MySQL(第3版)】https://book.douban.com/subject/23008813/
【MySQL 實戰 45 講】https://time.geekbang.org/column/100020801
【MySQL技術內幕】https://book.douban.com/subject/24708143/
【MySQL學習筆記】https://github.com/boilingfrog/Go-POINT/tree/master/mysql