《MySQL技術內幕-InnoDB存儲引擎》整理5-鎖

一、什麼是鎖

鎖機制用於管理對共享文件的併發訪問,並提供數據的完整性和一致性。對於MyISAM引擎,其鎖是表鎖結構,在併發情況下讀沒有問題,但是併發插入時性能較差。而對於Microsoft SQL Server,在樂觀併發下支持行級鎖,但是鎖越多開銷越大,因此會有鎖升級,行鎖會升級到表鎖,導致併發能力回退。InnoDB引擎支持一致性的非鎖定讀,行級鎖支持,且行級鎖沒有額外的開銷。

二、lock與latch

lock鎖是一種輕量級的鎖,其要求鎖定的時間非常短,若時間較長性能會變差。在InnoDB存儲引擎中,latch又分爲互斥量mutex和rwlock讀寫鎖,其目的是爲了保證併發線程操作臨界資源的正確性,並且通常沒有死鎖檢測的機制。

lock的對象是事務,鎖定的是數據庫中的對象,如表、頁、行,並且lock對象在事務commit或rollbck後進行釋放,此外lock具有死鎖機制。用戶可以使用Show Engine InnoDB Status來觀察鎖的信息

三、InnoDB存儲引擎中的鎖

1、鎖的類型

InnoDB存儲引擎實現了兩種標準的行級鎖:共享鎖(S Lock)-允許事務讀一行數據,排他鎖(X Lock)-允許事務刪除或跟更新一行數據。若事務T1獲得了行A的共享鎖,事務T2同樣可以立即獲得行A的共享鎖,這種情況稱爲鎖兼容。若事務T3想獲取行A的排他鎖,則必須等待事務T1、T2釋放行A上的共享鎖。因此只有S鎖與S鎖兼容,其他幾種情況下都是不兼容的

InnoDB存儲引擎支持多粒度的鎖定,這種鎖定允許事務在行級上的鎖和表級上的鎖同時存在,這種不同粒度的鎖稱爲意向鎖(Intention Lock)。例如需要對頁上的記錄M上X鎖,那麼需要分別對數據庫、表、頁上意向鎖,最後對記錄上鎖,若任何一個部分導致等待,那麼該操作需要等待粗粒度的鎖的完成。

InnoDB存儲引擎中的意向鎖爲表級別的鎖,其目的是爲了在一個事務中揭示下一行將被請求的鎖類型,其分爲兩種意向鎖:意向共享鎖(IS Lock)-事務想要獲得一張表中某幾行的共享鎖,意向排他鎖(IX Lock)-事務想要獲得一張表中某幾行的排他鎖。由於InnoDB存儲引擎支持的是行級別的鎖,所以意向鎖不會阻塞除全表掃描以外的任何請求。兼容性如下:

- IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

2、一致性非鎖定讀

一致性非鎖定讀是指InnoDB存儲引擎通過多版本控制的方式來讀取執行時間數據庫中行的數據,如果讀取行的數據在執行一個Update或者Delete操作,讀取行不會等待,而是去讀取一個快照數據。其實現是通過undo段來實現的,undo是用來在事務中進行回滾的,因此快照數據本身是沒有額外開銷的。非鎖定讀是InnoDB存儲引擎在默認設置下的讀取方式,但在不同的隔離級別下,其表現形式是不同的。

在Read Committed和Repeatable Read(默認隔離級別)下,InnoDB存儲引擎使用非鎖定的一致性讀,但對於快照的定義不同。在Read Committed事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據;而在Repeatable Read事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的行版本數據(如兩個事務對同一行進行操作,事務A在讀取時事務B在進行更新操作,事務A讀取到的是更新前的數據)。

3、一致性鎖定讀

在某些情況下,用戶需要顯式地對數據庫讀寫操作進行加鎖以保證數據邏輯的一致性,這就要求數據庫支持加鎖語句,即使是Select只讀操作。InnoDB存儲引擎對於Select語句支持兩種一致性鎖定讀操作:

  • Select...For Update:對讀取的行記錄加一個X鎖,其他事務不能對已鎖定的行加上任何鎖
  • Select...Lock In Share Mode:對讀取的行記錄加一個S鎖,其他事務可以向被鎖定的行加S鎖,但是如果加X鎖,則會被阻塞

需要注意的是上述兩種操作都必須在一個事務中,當事務提交後,鎖會被釋放

4、自增長與鎖

在InnoDB存儲引擎的內存結構中,對每個含有自增長值的表都有一個自增長計數器,當對含有自增長計數器的表進行插入操作時,計數器會被初始化。插入數據會依據自增長的計數器的值加1,這個實現方式稱爲Auto-Inc Locking。這種鎖採用了一種表鎖機制,它會在自增長值插入SQL語句後立即釋放。

Auto-Inc Locking從一定程度上提高了併發插入的效率,但是對於自增長列的併發插入性能較差,需要等待一個事務完成之後再進行下一個插入。MySQL5.1.22版本開始,InnoDB存儲引擎提供了一種輕量級互斥量的自增長實現機制,大大提高了自增長值插入的性能,其通過innodb_autoinc_lock_mode來控制自增長的模式

5、外鍵和鎖

外鍵主要用於完整性的約束檢查,在InnoDB存儲引擎中,對於一個外鍵列,如果存儲引擎沒有顯式地對這個列加索引,InnoDB存儲引擎會自動地對其加一個索引,避免表鎖。對於外鍵的插入或更新,首先要查詢父表中的記錄,此時使用的是一致性讀寫中的Select...Lock In Share Mode操作,子表上的操作會被阻塞,以避免父表與子表數據不一致的情況

四、鎖的算法

1、行鎖的三種算法

InnoDB存儲引擎有3中行鎖的算法,其分別是:

  • Record Lock:單個行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身
  • Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍,並且鎖定記錄本身

Record Lock總是會鎖住索引記錄,如果InnoDB存儲引擎在建立時沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。

Next-Key Lock算法下,InnoDB對於行的查詢都是採用這種鎖定算法,但是當查詢的索引含有唯一屬性時,InnoDB存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock。若唯一索引由多個列組成,而查詢的是多個唯一索引列中的一個,那麼查詢是Range類型的查詢而非Point類型,InnoDB存儲引擎依舊會使用Next-Key Lock。

Gap Lock的作用是爲了阻止多個事務將記錄插入到同一個範圍內,但這會導致Phantom Problem(幻像問題)的產生。用戶可以將隔離級別設置爲Read Committed或者將參數innodb_locks_unsafe_for_binlog設置爲1來顯式地關閉Gap Lock,但這樣會破壞事務的隔離性,對於主從架構會導致數據的不一致。

2、解決Phantom Problem

Phantom Problem是指在同一事務下,連續執行兩次同樣的SQL語句可能會導致不同的結果,第二次的SQL語句可能會返回之前不存在的行。InnoDB存儲引擎默認使用Repeatable Read隔離級別,其使用Next-Key Locking機制來避免幻象問題。而在Read Committed隔離級別下,其僅採用Record Lock

五、鎖問題

1、髒讀

髒數據是指未提交的數據,如果讀到了髒數據,即一個事務可以讀到另一個事務中未提交的數據,則違反了數據庫的隔離性。髒讀指的就是在不同的事務下,當前事務可以讀到另外事務未提交的數據,即讀到了髒數據。髒讀只有在事務隔離級別爲Read UnCommitted時會發生,目前主流的數據庫如MSSQL和Oracle的默認隔離級別都是Read Committed,Mysql的隔離級別是Read Repeatable,因此在實際生產環境中很少發生

2、不可重複讀

不可重複讀是指在同一事務內多次讀取同一數據集合,由於事務未結束而另一事務的DML操作對該數據集合進行了變化,導致同一事務內讀取到的數據不一致。其與髒讀的區別是,髒讀是讀到了未提交的數據,不可重複讀是讀到了已提交的數據,不可重複讀違反了數據一致性的原則。默認情況下的Read Committed是允許不可重複讀的現象的。Mysql將不可重複讀問題定義爲幻象問題(注意不是幻讀-即下面的丟失更新),InnoDB存儲引擎的隔離級別是Read Repeatable,使用Next-Key Lock算法來避免不可重複讀

3、丟失更新

丟失更新簡單來說就是一個事務的更新操作會被另一個事務的更新操作覆蓋,導致數據的不一致性。在數據庫理論層面是不會出現該現象的,這個因爲DML操作需要對行或其他粗粒度級別的對象加鎖,後一個事務不能對某數據進行更新直到前一個事務提交,但是在實際應用場景是存在的,比如將數據展現給多個用戶後數據發生了變化。避免丟失更新發生需要讓事務在這種情況下的操作串行化,而不是並行。

六、阻塞

由於不同鎖之間的兼容性,有時一個事務的鎖需要等待另一個事務中的鎖釋放它所佔用的資源,這就是阻塞。在InnoDB存儲引擎中,參數innodb_lock_timeout用來控制等待的時間(默認爲50s),參數innodb_rollback_on_timeout用來設定是否在等待超時時對進行中的事務進行回滾操作(默認爲Off)

默認情況下,InnoDB存儲引擎不會回滾超時引發的錯誤異常,用戶必須判斷是否需要Commit還是RollBack以避免在等待過程中的變更的數據帶來的影響。

七、死鎖

1、死鎖的概念

死鎖指的是兩個或兩個以上的事務在執行過程中,因爭奪鎖資源而造成的一種互相等待的現象。解決死鎖最簡單的方式是不需要等待,將任何的等待都轉化爲回滾,並且事務重新開始,但這會導致併發性能的下降,甚至任何一個事務都不能進行。另一種方式是設定超時時間,當一個等待超過設置的閾值時,當前事務回滾,另一個事務可以繼續執行,這樣的缺點是僅按照時間權重來決定,可能會導致事務更新操作較多,即佔用了較多的undo log的事務回滾,浪費系統資源

目前數據庫普遍採用wait-for graph(等待圖)的方式來進行死鎖的檢測,它是一種主動的死鎖檢測方式,InnoDB存儲引擎採用的也是這種方式。wait-for graph要求數據庫保存鎖的信息鏈表以及事務等待鏈表,通過這兩種信息構造一張圖,若圖出現迴路即代表存在死鎖。圖中節點代表事務,事務T1指向事務T2邊的定義爲:①事務T1等待事務T2所佔用的資源;②事務T1最終等待T2所佔用的資源,即事務之間存在等待的資源且T1發生在T2之後。每個事務請求鎖併發生等待時都會判斷是否存在迴路,若存在死鎖,InnoDB存儲引擎通常會選擇回滾undo量最小的事務。wait-for graph通常採用深度優先算法

2、死鎖的概率

  • 系統中事務的數量n,數量越大發生死鎖的概率越大;
  • 每個事務操作的數量r,每個事務操作的數量越多,發生死鎖的概率越大;
  • 操作數據的集合R,越小則發生死鎖的概率越大

八、鎖升級

鎖升級是指將當前鎖的粒度降低,即將行鎖升級爲頁鎖,或者將頁鎖升級爲表鎖。MSSQL中將鎖認定爲一種稀有的資源,在合適的時候自動地將行、鍵或者分頁鎖升級爲更粗粒度的表級鎖,這種升級保護了系統資源,防止系統使用過多的內存來維護鎖,在一定程度上提高了效率,到那時會降低系統的併發性能。

MSSQL在以下情況發生時可能發生鎖的升級:

  • 由一句單獨的SQL語句在一個對象上持有的鎖的數量超過了閾值,默認值爲5000
  • 鎖資源佔用的內存超過了激活內存的40%

InnoDB存儲引擎不存在鎖升級的問題,它是根據每個事務訪問每個頁對鎖進行管理的,採用的是位圖的方式,因此不管事務是鎖住頁中的一個記錄還是多個記錄,其開銷通常是一致的

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