前面兩篇文章介紹了MySQL的全局鎖和表級鎖,今天就介紹一下MySQL的行鎖。html
MySQL的行鎖是各個引擎內部實現的,不是全部的引擎支持行鎖,例如MyISAM就不支持行鎖。mysql
不支持行鎖就意味着在併發操做時,就要使用表鎖,在任意時刻都只能有一個更新操做在執行,這樣會影響業務的併發性。這也是爲何MyISAM會被InnoDB取代的緣由之一。算法
行鎖是鎖裏最小粒度的鎖,InnoDB引擎裏的行鎖的實現算法有三種:sql
InnoDB是使用Next-Key Lock來解決幻讀問題的。數據庫
咱們看一下這個例子,有一個表 t,插入部分數據。session
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);
有三個會話併發執行,Session A在T1,T3,T5時刻分別查詢同一個語句,出現不一樣的結果。其中Q3讀到的id=1這一行的現象,被稱爲幻讀。併發
幻讀,指同一個事務中,兩次相同的查詢操做,獲得的結果行數不同。性能
這裏要對「幻讀」作兩點說明:設計
根據數據可見性規則分析,這三個查詢都加了for update,都是「當前讀」,符合數據可見性規則。3d
這麼看來,好像沒什麼問題,是否是真的沒有問題呢?
不,這裏還真就有問題。
Session A在T1時刻就聲明瞭,「我要把全部d=5的行鎖住,不許別的事務進行讀寫操做」。而實際上,這個語義被破壞了。
上面的例子可能還看不太出來,咱們給Session B和Session C分別加兩個語句,再看看會出現什麼現象。
Session B的第二條語句update t set c = 5 where id=0,語義是「我要把id=0、d=5的這一行的c的值改爲了5」。
因爲在T1時刻,Session A還只是給t=5這一行加了行鎖,並無給id=0這一行加鎖。所以Session B在T2時刻,是能夠執行這條語句的。
同理,Session C對id=1這行的修改,同樣是破壞了Q1的加鎖聲明。
其次是形成數據上不一致。鎖的設計就是爲了保證數據一致性的,這裏的一致性除了內部數據在此刻的一致性外,還包含數據和日誌在邏輯上的一致性。
咱們來分析一下圖3執行完成後,數據庫的數據是什麼:
咱們再來看看binlog的內容:
// session B update t set d=5 where id=0; update t set c=5 where id=0; // session C insert into t values(1,1,5); update t set c=5 where id=1; update t set d=100 where d=5;
按照這個語句序列,這三行的結果變成:(0,5,100),(1,5,100),(5,5,100)。
也就是說id=0和id=1這兩行,發生了數據不一致。這個問題很嚴重,是不行的。
那究竟這個數據不一致是怎樣引入的呢?
假設咱們對掃描到的行都加上行鎖,來看看圖4執行後會出現什麼現象。
id=1這一行仍是出現數據不一致的問題。即便把全部的記錄都加上鎖,仍是阻止不了新插入的記錄。
咱們如今知道產生幻讀的緣由是,行鎖只能鎖住行,可是新插入記錄這個動做,要更新的是記錄之間的「間隙」。所以,爲了解決幻讀問題,InnoDB引入了間隙鎖(Gap Lock)。
前面介紹過,間隙鎖,鎖住某個範圍,但不包括記錄自己。好比前面說到的表t,初始化有6條記錄,這就產生了7個間隙。
當你執行select * from t where d=5 for update的時候,就不止是給數據庫中6個記錄加了行鎖,還同時加了7個間隙鎖。這樣就確保了沒法再插入新的記錄。
也就是說這時候,在一行行掃描的過程當中,不只給行加上行鎖,還給行兩邊的空隙也加上間隙鎖。
咱們回到上面的圖4,再來看看加上間隙鎖後,執行的效果如何。
Session B和Session C都要等待Session A釋放鎖後才能繼續執行,這樣就解決了幻讀的問題。
行鎖保證更新行,間隙鎖保證插入行,而行鎖+間隙鎖=Next-Key Lock,也就是本文開頭說到的,InnoDB是經過Next-Key Lock來解決幻讀問題的。
可是間隙鎖的引入,可能會致使一樣的語句鎖住更大的範圍,這會影響併發度的。好比上面的select * from t where d=5 for update,至關於加了表鎖。