1. 程式人生 > 實用技巧 >MySQL基礎:Innodb引擎鎖總結

MySQL基礎:Innodb引擎鎖總結

1.鎖的目的是什麼?
innodb中利用mvcc(多版本併發控制),可以在不加鎖的情況下提高併發訪問下系統的吞吐量,但有些場景下併發訪問必須要在鎖的保護下進行,比如併發的更新。

2.鎖的分類
一般鎖是指lock,innodb中還有不常用的一種輕量級鎖latch,latch中不存在死鎖檢測機制,適合加鎖時間較短的場景。
按相容性分類,分為S 共享鎖和X 排他鎖。S鎖之間是相容的,但X鎖不與其他鎖相容。
按粒度分類,分為表級鎖、頁鎖(innodb中好像沒有?)、行級鎖。innodb支援多粒度鎖,它允許行級鎖與表級鎖共存。

3.表鎖
mysql的MyISAM使用的是表鎖,而innodb有表鎖和行鎖。
innodb表級鎖包括自增鎖、意向鎖


自增鎖 AUTO-INC Locks,用於AUTO_INCREMENT的自增主鍵。在Innodb儲存引擎的記憶體結構中,對每個含有自增長值的表都有一個自增長計數器。當對錶進行插入操作時,會依據自增長計數器的計數值加一賦予自增長列.為了提升插入的效能,鎖不是事務完成後才釋放,而是完成自增長值插入的sql語句後立即釋放。

意向鎖 Intention Locks,表示事務有意向對錶中的某些行加鎖,分為IS 意向共享鎖和IX 意向排他鎖。IS表示有意向對錶中的某些行加共享鎖,IX表示有意向對錶中的某些行加排他鎖。意向鎖是由引擎自己維護的,使用者無法手動操作,在給資料行加共享/排他鎖之前,Innodb會先給對應的表加意向鎖。
意向鎖之間是相互相容的,IX與IX之間也相互相容。另外,意向鎖也不會與行級的共享/排他鎖互斥


意向鎖的作用是什麼? 意向鎖會與表級的共享/排他鎖互斥,這裡的表級鎖指普通的表級鎖。例如,事務A執行 SELECT * FROM USERS WHERE ID=9 FOR UPDATE, 此時USERS表有兩把鎖,USERS表上的IX鎖和ID=9的資料行上的排他鎖。在事務A未commit的情況下,事務B執行LOCK TABLES USERS READ,此時事務B檢測到事務A持有表的IX鎖,就可以得知A必然持有該表中某些行的排他鎖,因此B對USERS的加鎖請求就會被阻塞,就無需檢測表中每一行的資料是否存在排他鎖。

4.行級鎖
分為Record Lock,Gap Lock和Next-Key Lock。這裡鎖都是針對索引

,通過索引來實現行鎖,而不是通過鎖住記錄。資料庫操作使用主鍵索引時,會鎖住主鍵索引;使用非主鍵索引時,會先鎖住非主鍵索引,再鎖住主鍵索引,這樣就有可能導致死鎖(A先鎖住非主鍵索引再鎖住主鍵索引,而B先鎖住主鍵索引)。
Record Lock,是單個行記錄上的鎖。
Gap Lock,間隙鎖,鎖住一個範圍,但是不包含記錄本身。例如事務A執行SELECT * FROM USERS WHERE ID>5 AND ID<8 FOR UPDATE,此時就會鎖住(5,8)的區間,事務B嘗試插入ID=6的操作就會被阻塞。間隙鎖之間互不衝突,它的唯一作用是防止其他事務的插入,因此加間隙S鎖和間隙X鎖沒有區別。RR隔離級別下特有。
Next-Key Lock,Record Lock+Gap Lock,鎖住記錄本身,並且鎖定一個範圍。它的主要目的是,保證Repeatable Read(RR)的事務隔離級別下不出現Phantom Problem幻讀問題。innodb預設的事務隔離級別是Repeatable Read,在RR中innodb行鎖預設使用Next-key Lock,只有當查詢的索引時唯一索引時,innodb會對next-key lock進行優化,將其降級為Record Lock,即僅鎖住單條索引本身而不是範圍。當查詢的索引為輔助索引時,innodb會使用next-key lock加鎖。

5.mvcc
多版本併發控制,不加鎖的情況下支援併發查詢提高併發度。在Read Committed和Repeatable Read隔離級別下有不同的表現。Innodb中每一行有三個隱藏列,DATA_TRX_ID表示最近修改該行資料的事務id,DATA_ROLL_PTR表示指向該行undolog段段指標,DELETED_BIT標記該列是否被刪除,mvcc就是通過這三個隱藏列實現。
Read Committed隔離級別下,每次查詢得到的是當前已經提交的資料。Repeatable Read級別下,查詢得到的是事務開始前的資料。
怎麼實現的? 核心內容:事務版本號,表的隱藏列,undo log和read view。通過讀取undo log的歷史版本資料,來實現不同事務擁有自己獨立的快照資料版本。
主要過程:獲得事務版本號;獲取一個read view;查詢到資料,與read view事務版本號進行批評;不符合read view規則的從undolog裡獲取歷史版本資料;返回符合規則的資料。
Read View, 每個sql執行前,都會得到一個read_view,主要儲存了當前資料庫系統中正處於活躍(沒有commit)的事務ID號。不同隔離級別下,根據不同的條件匹配read view。RC隔離級別下,同一個事務裡的每一次查詢都會獲得一個新的read view副本,這樣既可能造成同一個事務裡前後讀取資料可能不一致問題(重複讀)。RR級別下,一個事務裡只會獲取一次read view副本,從而保證每次查詢的資料都是不一致的。Read Uncommitted級別下,事務不會獲取read view副本。
事務版本號, 事務開始前都會從資料庫獲取一個自增長的事務ID,可以根據事務ID判斷事務的執行先後順序。

5.phantom problem幻讀問題
解決:mvcc+next-key lock?為什麼mvcc不夠?
什麼是phantom problem?
RR隔離級別下,全部使用快照讀不會存在幻讀問題,但是快照讀和當前讀混用時會存在幻讀問題,因此需要間隙鎖機制。
例項如下:

a. 事務A執行SELECT * FROM T WHERE ID>3 返回ID=3和4兩條記錄;
b. 事務B插入了一條ID=5的資料並commit;
c.事務A執行SELECT * FROM T WHERE ID>3 得到3、4兩條記錄;
d. 事務A執行UPDATE T SET NAME='..' WHERE ID>3 更新三條記錄;
e. 事務A再次執行SELECT * FROM T WHERE ID>3 得到3、4、5三條記錄。

a和c兩步均為快照讀,因此都只能讀取到兩條記錄,且不對資料加鎖。b中插入ID=5的資料,在d中是可見的,因為UPDATE中使用當前讀讀取最新commit的資料,如果不使用當前讀會有資料覆蓋問題。因為事務A在d中對ID=5的資料進行了更新,因此A中快照進行了更新,e中可以查詢到5這條記錄。
事務A中快照讀和當前讀混用,導致了幻讀問題。如果都使用當前讀即a中使用SELECT FOR UPDATE,則b中插入操作會阻塞,A中的update和select都始終只會影響兩條記錄。當前讀使用了間隙鎖,因此會阻塞b中操作。

快照讀和當前讀 快照讀是讀取資料時,不讀取最新版本資料,而是基於歷史版本讀取的一個快照資訊(innodb基於undo log歷史版本),快照讀可以使用普通的SELECT讀取資料不用對錶資料進行加鎖。當前讀是讀取最新的資料,需要對資料加鎖。update、delete、insert、select ... lock in share mode和select ... for update 為當前讀。RC隔離級別下,只加record lock,而在RR下加Record Lock。

6.參考
《MySQL內幕:Innodb儲存引擎》大名鼎鼎,但是很多東西講的不清楚,遺憾。
意向鎖
各種鎖,幻讀