前文提到,對於 InnoDB 來講,隨時均可以加鎖(關於加鎖的 SQL 語句這裏就不說了,忘記的小夥伴能夠翻一下上篇文章),可是並不是隨時均可以解鎖。具體來講,InnoDB 採用的是兩階段鎖定協議(two-phase locking protocol):即在事務執行過程當中,隨時均可以執行加鎖操做,可是只有在事務執行 COMMIT 或者 ROLLBACK 的時候纔會釋放鎖,而且全部的鎖是在同一時刻被釋放。git
而且,行級鎖只在存儲引擎層實現,而對於 InnoDB 存儲引擎來講,行級鎖又分三種,或者說有三種行級鎖算法:算法
下面,咱們來詳細解釋下這三種行鎖算法。sql
顧名思義,記錄鎖就是爲某行記錄加鎖,事實上,它封鎖的是該行的索引記錄。若是表在創建的時候沒有設置任何一個索引,那麼這時 InnoDB 存儲引擎會使用 「隱式的主鍵」 來進行鎖定。數據庫
所謂隱式的主鍵就是指:若是在建表的時候沒有指定主鍵,InnoDB 存儲引擎會將第一列非空的列做爲主鍵;若是沒有的話會自動生成一列爲 6 字節的主鍵。後端
那麼,既然 Record Lock 是基於索引的,那若是咱們的 SQL 語句中的條件致使索引失效(好比使用 or
) 或者說條件根本就不涉及索引或者主鍵,行級鎖就將退化爲表鎖。網絡
先來舉個對索引字段進行查詢的例子,有數據庫以下,id 是主鍵索引:數據結構
CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
初始數據是這樣的:學習
新建兩個事務,先執行事務 T1 的前兩行,也就是不要執行 commit:優化
因爲沒有執行 commit,因此這個時候事務 T1 沒有釋放鎖,而且鎖住了 id = 1 的記錄行,此時再來執行事務 2 申請 id = 2 的記錄行:操作系統
能夠看見,因爲鎖住的是不一樣的記錄行,因此兩個記錄鎖並無相互排斥,來看一下如今表中的數據,因爲事務 1 尚未 commit,因此應該是隻有 id = 2 的 username 被修改了:
nice,果真。再執行下事務 1 的 commit,id = 1 的 username 也就被修改過來啦。
再來看下沒有使用索引的例子:
一樣的,新建兩個事務,先執行事務 T1 的前兩行,也就是不要執行 commit。咱們試圖使用 select ... for update
給 username = "user_three" 的記錄行加上記錄鎖,可是因爲 username 並不是主鍵也並不是索引,因此實際上這裏事務 T1 鎖住的是整張表:
因爲沒有執行 commit,因此這個時候事務 T1 沒有釋放鎖,而且鎖住了整張表。此時再來執行事務 2 試圖申請 id = 5 的記錄鎖,你會發現事務 T2 會卡住,最後超時關閉事務:
這個問題的答案應該很簡單吧,上面咱們強調過,行鎖鎖住的是索引,而不是一條記錄(只不過咱們日常這麼說鎖住了哪條記錄,比較好理解罷了)。因此若是兩個事務分別操做的兩條不一樣記錄擁有相同的索引,某個事務會由於行鎖被另外一個事務佔用而發生等待。
這裏我先簡單提一嘴,下文會詳細解釋:不一樣於 Record Lock 是基於惟一索引的,Gap Lock 和 Next-Key Lock 都是基於非惟一索引的。
而且,不一樣於 Record Lock 鎖定的是某一個索引記錄,Gap Lock 和 Next-Key Lock 鎖定的都是一段範圍內的索引記錄:
select * from test where id between 1 and 10 for update;
對於上述 SQL 語句,全部在(1,10)
區間內(左開右開)的記錄行都會被 Gap Lock 鎖住,全部 id 爲 二、三、四、五、六、七、八、9 的數據行的插入會被阻塞,可是 1 和 10 兩條被操做的索引記錄並不會被鎖住。
注意!這裏指的是鎖住全部的(1,10)區間內的 id,也就是說即便某個 id 目前並不在咱們的表中好比 id = 6 ,若是你想插入一條 id = 6 的新紀錄,那對不起,不行。
Next-Key Lock 是結合了 Gap Lock 和 Record Lock 的一種鎖定算法,其主要目的是爲了解決幻讀問題。
例如一個索引有 10,11,13 和 20 這四個值,分別對這個 4 個索引進行加鎖操做,那麼這四個操做分別對應的 Next-Key Lock 鎖住的區間是:
(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +∞]
細心的同窗應該已經注意到了,和 Gap Lock 的不一樣之處就在於,Next-Key Lock 鎖定的區間是左開右閉的,也就是說它是包含當前被操做的索引記錄的。
在 InnoDB 默認的隔離級別 REPEATABLE-READ 下,行鎖默認使用的算法就是 Next-Key Lock。可是,若是操做的索引是惟一索引或主鍵,InnoDB 會對 Next-Key Lock 進行優化,將其降級爲 Record Lock,即僅鎖住索引自己,而不是範圍。
因爲主鍵也是一種惟一索引,因此咱們能夠這麼說:Record Lock 是基於惟一索引的,而 Next-Key Lock 是基於非惟一索引的。
須要注意的,當操做的索引爲非惟一索引時,InnoDB 會先用 Record Lock 鎖住對應的惟一索引,再用 Next-Key Lock 和 Gap Lock 對這個非惟一索引進行處理,而不只僅是鎖住這個非惟一索引。具體地咱們舉個例子來看下。
假設咱們爲上面 test 表中新增一個字段,並設置爲非惟一索引:
CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `class` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `index_class` (`class`) USING BTREE COMMENT '非惟一索引' ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
插入一些數據:
開啓一個事務 1 執行以下的操做語句:
select * from test where class = 3 for update;
在這種狀況下,InnoDB 事實上會加上三種行鎖(select * ... from update
加的是行級寫鎖即 X 鎖):
1)給主鍵索引 id = 105 加上 Record Lock
2)對於非惟一索引 class = 3,其加上的是 Next-Key Lock,鎖定的範圍是 (1,3]
3)另外,特別須要注意的是,InnoDB 存儲引擎還會對非惟一索引 class 的下一個鍵值加上 Gap Lock(表中 class = 3 的下個鍵值是 6),因此還有一個 class 索引範圍爲 (3,6)
的間隙鎖
總結下 2)和 3),對於這條 SQL 語句,InnoDB 存儲引擎鎖定地 class 索引範圍是 (1, 6)
下面咱們用實踐來驗證理論,再開啓一個事務 2,執行下述的語句:
不出所料,因爲在事務 1 中執行的 SQL 語句已經對主鍵索引中列 a=105 的記錄加上了 X 鎖,因此此處再去獲取 這個記錄的 X 鎖會被阻塞住。
再用一個事務來執行下述 SQL 語句:
主鍵插入 104 沒有任何問題,可是插入的 class 索引值 2 在被鎖定的範圍 (1,6) 中,所以執行一樣會被阻塞住。
通過上面的分析,你們必定可以知道下面的 SQL 語句是能夠正常執行的:
須要注意的是,Next-Key Lock 降級爲 Record Lock 僅存在於操做全部的惟一索引列的狀況。若惟一索引由多個列組成,而操做的僅是多個惟一索引列中的其中一個,那麼 InnoDB 存儲引擎依然使用 Next-Key Lock 進行鎖定。