Innodb中的鎖

Innodb中的鎖

  雖然比較擅長的是Oracle,但是公司使用的是MySQL數據庫,所以不得不對MySQL數據庫多研究一下。今天就談一下MySQL中的鎖。

  談鎖之前,要明白爲什麼會有鎖這種東西。之所以有鎖,大部分情況下是爲了實現事務(transaction)之間的隔離,那麼事務之間有幾種隔離方式,各種隔離方式又是爲了達到什麼效果呢?先來說一下各種讀現象。

  1. 髒讀:即一個事務A讀取了事務B還沒有提交過的數據;

  2. 不可重複讀:即事務A在第一次按照某條SQL語句讀取後跟第二次按照相同的SQL語句讀取數據庫,這段時間內,事務B對這條SQL語句覆蓋的數據行進行了已提交的修改(updatedelete),導致A前後兩次讀取數據不一樣;

  3. 幻讀:即事務A在第一次按照某條SQL語句讀取後跟第二次按照相同的SQL語句讀取數據庫,這段時間內,事務B插入了滿足SQL語句的數據行並提交了(insert),導致A前後兩次讀取數據不一樣;

針對以上幾種讀現象,就有了read-uncommittedread-committedrepeatable read

serializable read,這幾種事務隔離級別,下面是各種隔離級別可以防止的讀現象:

是否防止

髒讀

不可重複讀

幻讀

read-uncommitted

read-committed

repeatable read

serializable read

        

下面通過例子的形式介紹,切記以begin顯式開啓事務。雖然mysql使用的是repeatableread的隔離級別,但是爲了在一定程度上防止幻讀,使用了next key算法(下面將介紹)進行上鎖。


  例如使用如下SQL語句建表

   createtable ktest (id int primary key auto_increment, col1 int, col2 int, key(col1));

  其中id列包含主鍵索引的列,col1爲包含非唯一索引的列,col2爲不包含任何索引的列。使用如下sql語句對數據表插入數據

   insertinto ktest (col1, col2) values (1,2),(2,3),(2,4),(4,5),(8,9);

  以下是col1上索引的大體情況:

wKiom1hmKsSjAEwRAAA8uoeNuwU625.png-wh_50

(空白部分表示還沒有插入)

  在利用非唯一鍵進行唯一匹配更新或刪除的時候,例如此時事務A執行updatektest set col2 = 10 where col1 = 2;

  那麼除了兩個col1=2的地方需要上鎖之外,爲了確保在事務執行過程中不會出現幻讀,即不能說剛把這兩行數據修改完成但還沒有提交事務的情況下,其他事務就能能插入col1=2(例如此時事務B執行insert into ktest values (10,3,5);)的數據行,那麼需要在事務A執行update語句之前,在col12(右邊的)到col14之間,加一個虛擬鎖,保證這段時間內不會有col1=2的數據插入進來,但是這樣帶來了一個弊端,即col1=3的數據行也不能在A執行的期間插入。這種在兩個索引值之間加的虛擬鎖就是GAP鎖,加上原本就應該加上鎖的兩個col1=2的鎖,合起來就叫next key鎖。

  當然如果第一個col1不是1,而是0的話也要在col10col12(左邊的)之間,加上類似的“虛擬鎖”。這樣也會造成col1=1的數據行無法插入。

  以下是事務A(左)執行上面sql語句時,事務B(右)試圖插入col1=3的截圖

wKiom1hmKvGzGoPTAAE2U5pj2m4899.png-wh_50

圖一

相反,對於id這樣的唯一列,在這上面試圖用唯一匹配的方式更新一行數據的時候,由於id已經是唯一列了,因此不需要通過鎖的方式防止有相同的id插入,所以只會對匹配的行上鎖。例如在上述表中沒有其他事務時執行完insert into ktest values (10, 3, 5);

此時如果不採取默認提交(即用begin顯示開啓事務),同時在兩個事務中分別執行

transaction A:update ktest set col2 = 10 where id = 10;

transaction B:insert into ktest values (9, 1, 1);

兩個事務之間不會產生任何鎖定,截圖如下

wKioL1hmKwWwKdkWAACiAb0xSkw893.jpg-wh_50

圖二

雖然這種情況下不會產生因爲GAP而導致的事務阻塞,但是不代表使用唯一鍵就不會有GAP鎖。例如在兩個事務中分別執行

transaction A:update ktest set col2 = 10 where id < 10;

transaction B:insert into ktest values (7, 1, 1);

事務A不僅會跟已有的id1,2,3,4,5,9)上鎖,59之間也會有GAP鎖,而導致事務B被堵住。當然如果執行insertinto ktest values (17, 1, 1);是沒有問題的,因爲id=17的情況不會被這種鎖機制包含;同理執行insert into ktest values (-17, 1, 1);仍然會被堵住。

當然由於innodb中鎖是基於索引實現的,如果在沒有索引的col2上執行如下語句,將會鎖住所有行

update ktest setcol2 = 10 where col2 =10

 

雖然GAP鎖可以在一定程度上防止幻讀,但是有些時候也會引發一些莫名其妙的錯誤,例如下面表格表示的兩個事務,比如在上面的例子中,即使transaction B 多次全表掃描,但是仍然看不到id7的數據行,可是用戶就是沒辦法在transaction B中插入id7的數據行。

 

當通過修改innodb_locks_unsafe_for_binlog參數使其等於1的時候,重啓mysql服務,可以禁用GAP鎖,下面的兩個事務也不會產生堵塞。(先左後右)

wKioL1hmKxWDhB4WAAC2XZ_3FxE328.jpg-wh_50

圖三

 

總結:

由此可見GAP鎖雖然可以控制事務的隔離級別,但是無可避免的降低了事務的併發量,在生產中可以考慮使用read-committed的事務隔離級別(類似Oralce),或者將innodb_locks_unsafe_for_binlog參數設置爲1,取消可重複讀隔離級別下的GAP鎖。

如果對索引結構不太熟悉可以參考之前我寫過的關於索引的文章

http://9305967.blog.51cto.com/9295967/1885949

相關圖片已經打包上傳,大家看不清可以下載

  最後提前祝大家元旦快樂。

                        2016.12.30 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章