MySQL技術內幕-InnoDB存儲引擎-第六章、鎖

MySQL數據庫InnoDB引擎行級鎖鎖定範圍詳解

  • 最大程度利用併發
  • 確保每個用戶能一致的讀和修改數據

一、什麼是鎖?

鎖用於管理對共享資源的併發訪問。InnoDB支持行鎖
InnoDB其他多個地方也使用了鎖,比如操作LRU列表,刪除、添加、移動LRU列表中的元素。
數據庫使用鎖就是爲了支持對共享資源進行併發訪問,提供數據的完整性和一致性。

不同數據庫的鎖的實現可能完全不同。
MyISAM鎖鎖表鎖,併發情況下,讀沒有問題,但是在併發插入的時候性能就差一點。

二、lock和latch

latch(mutex互斥鎖、rwlock讀寫鎖)

latch一般稱爲閂(shuan)鎖,是一種輕量級的鎖,因爲其要求鎖定的時間非常短,如果持續時間長,性能會非常差,沒有死鎖檢查機制。
InnoDB中的latch分類:

  • mutex(互斥鎖)
  • rwlock(讀寫鎖)

lock

lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。

並且lock的對象僅僅在事務commit或者rollback後進行釋放(不同事務隔離級別釋放的時間可能不同)。有死鎖機制。

在這裏插入圖片描述

可以使用show engine innodb mutex。
結果的參數說明:
在這裏插入圖片描述

三、InnoDB存儲引擎中的鎖

1、鎖的類型

兩個標準的行級鎖:

  • 共享鎖:允許事務讀一行數據
  • 排他鎖:允許事務刪除或更新一行數據

共享鎖和排他鎖不能同時有,排他鎖和排他鎖不能同時有。
在這裏插入圖片描述

InnoDB支持多粒度加鎖,這種鎖定允許事務在行和表級別上的鎖同時存在,爲了支持不同粒度上進行加鎖,InnoDB支持意向鎖。
意向鎖將鎖定的對象分爲多個層次,意向鎖意味着事務希望在更細粒度上進行加鎖。

如果要對頁上的記錄進行上X鎖,那麼分別需要對數據庫、表、頁上意向鎖IX,最後對這條記錄上X鎖。其中任何一部分導致等待,那麼該操作需要等待粗粒度鎖的完成,比如需要對錶進行了S表鎖,之後事務需要在該表加IX鎖,那麼事務需要等待表鎖操作的完成。

在這裏插入圖片描述
意向鎖爲表級別的鎖,設計的目的主要是爲了在一個事務中揭示下一行被請求的鎖類型,其支持兩種意向鎖:

  • 意向共享鎖(IS Lock),事務想要獲得一張表中某幾行的共享鎖
  • 意向排他鎖(IX Lock),事務想要獲得一張表某幾行的排他鎖

表級意向鎖和行級鎖的兼容性如下:
在這裏插入圖片描述

可以通過show engine innodb status;命令來查看當前鎖請求的信息。

在這裏插入圖片描述
RECORD LOCKS space id 30 page no 3 bits 72 index ‘PRIMARY’ of table ‘test’.‘t’ trx id 48B89BD lock_mode X locks rec but not gap.表示鎖住的資源,locks rec but not gap表示鎖住的鎖一個索引,不是一個範圍。

在InnoDB1.0之前,使用命令SHOW FULL PROCESSLIST、SHOW ENGINE INNODB STATUS等來查看當前數據庫中鎖的請求,從InnoDB1.0開始,在INFORMATION_SCHEMA架構下添加了表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS,通過這三張表可以更簡單監控當前事務並分析可能存在的鎖問題。

INNODB_TRX的字段定義:
在這裏插入圖片描述
上面的表只能顯示當前運行的InnoDB事務,並不能直接判斷鎖的一些情況,如果需要查看鎖,還需要訪問表INNODB_LOCKS,該字段組成如表6-6所示:
在這裏插入圖片描述在這裏插入圖片描述
lock_data這個值並非是“可信”的值。例如用戶運行一個範圍查找的時候,lock_data可能只返回第一行的主鍵值。

INNODB_LOCK_WAITS可以直觀反映當前事務的等待。表INNODB_LOCK_WAITS由4個字段組成。
在這裏插入圖片描述在這裏插入圖片描述

從上面可以直觀看到哪個事務阻塞了另外一個事務。

2、一致性非鎖定讀(使用MVCC,當行上正在執行其他操作,可以讀取行的快照,不需等待)

一致性的非鎖定讀指的是InnoDB存儲引擎通過行多版本控制的方法來讀取當前執行時間數據庫中行的數據。

如果讀取的行正在執行DELETE或者UPDATE操作,那麼這個時候讀取操作不回因此去等待行上鎖的釋放,相反的,InnoDB存儲引擎會去讀取行的一個快照數據。
在這裏插入圖片描述
快照數據指的是之前版本的數據,該實現是通過undo段來完成,而undo用來在事務中回滾數據。
非鎖定讀大大提高了數據庫的併發性,快照數據其實就是當前行數據的歷史版本,每行記錄可能有多個版本即多個快照數據,一般稱這種技術爲行多版本技術。由此帶來的併發控制稱爲多版本併發控制。

可重複讀和已提交讀隔離級別下,InnoDB使用非鎖定的一致性讀,但是他們對於快照的定義不同,在READ COMMITTED事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據,而在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的行數據版本。
一個是看到的是最新的版本數據,一個看到的是事務開啓時候的版本數據。

3、一致性鎖定讀(select…for update、select … in share mode)

前面講了select操作使用了一致性非鎖定讀,但是在某些情況下,用戶需要對數據庫讀取操作進行加鎖以保證數據邏輯的一致性。
InnoDB對select語句支持兩種一致性的鎖定讀操作:

  • select … for update(對鎖定的行加X鎖)
  • select … lock in share mode(對鎖定的行加S鎖)

對於一致性非鎖定讀,即使讀取的行已經被執行了select … for update,也是可以進行讀取的,此外select … for update、select … lock in share mode必須在一個事務中,當事務提交,鎖被釋放,因此必須開啓事務,如begin、start transaction或者set autocommit = 0;

4、自增長與鎖

在InnoDB內存結構中,對每個含有自增長值的表都有一個自增長計數器。當含有自增長計數器的表進行插入操作的時候,這個計數器會被初始化,執行如下的語句來得到計數器的值:

  • select MAX(auto_inc_col) from t for update;

插入操作會根據這個自增長的計數器的值加1賦予自增長列,這個實現方式叫做AUTO-INC Locking。它採用的是一種特殊的表鎖機制,爲了提高插入性能,鎖不是在一個事務完成之後才釋放,而是在完成對自增長值插入的SQL語句後立即釋放

雖然AUTO-INC Locking從一定程度提高了併發插入的效率,但是還是存在一些性能問題:

  • 對於自增長列的併發插入性能比較差,事務必須等待前一個插入的完成
  • 對於INSERT…SELECT的大數據量的插入會影響插入的性能,因爲另一個事務中的插入會阻塞

從MySQL5.1.22開始,InnoDB提高了輕量級互斥的自增長實現機制。大大提高了自增長值插入的性能。InnoDB存儲引擎提供了一個參數innodb_autoinc_lock_mode來控制自增長的模式。默認值爲1。
在這裏插入圖片描述
對自增長的分類
在這裏插入圖片描述
在InnoDB存儲引擎中,自增長值必須是索引,同時必須是索引的第一個列。如果不是,MySQL會拋出異常,MyISAM不會有這個問題
在這裏插入圖片描述

5、外鍵和鎖

前面介紹了外鍵,外鍵主要用於引用完整性的約束檢查,在InnoDB存儲引擎中,對於一個外鍵列,如果沒有顯式對這個列加索引,InnoDB存儲引擎會自動對其加1個索引,用戶必須自己手動添加,這也導致了Oracle數據庫中可能產生死鎖。

四、鎖的算法

1、行鎖的3種算法(Next-Key Lock在索引唯一時,InnoDB會將Next-Key Lcok降級爲Record Lock)

間隙鎖只會阻止insert語句,record鎖可能select也會阻止。

InnoDB種3種行鎖的算法,分別是:

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

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

Next-Key Lock:結合了Gap Lock+Record Lock,InnoDB對於行的查詢都是採用這種鎖定算法,例如一個索引有10,11,13,和20,那麼該索引可能被Next-Key Locking的區間爲:

  • (-無窮,10]、(10,11]、(11,13]、(13,20]、(20,+無窮)

除了next-key locking,還有previous-key locking技術,同樣上述的索引10,11,13,20,如果使用previous-key locking,那麼可以鎖定的區間爲:

  • (-∞,10)、[10,11)、[11,13)、[13,20)、[20,+∞)

然而,當查詢的索引有唯一屬性的時候,InnoDB會將Next-Key Lcok進行優化,降級爲Record Lock。即鎖住索引本身而不是範圍。如下面,並沒有鎖定(2,5)這個範圍,而是降級爲了Record Lock。
下面是Gap Lock降級爲Record Lock的例子。

在這裏插入圖片描述

如果查詢的是輔助索引
在這裏插入圖片描述

  • select * from z where b = 3 for update;

很明顯,這個SQL語句通過索引列b進行查詢,因此其使用傳統的Next-Key Locking技術加鎖,並且由於有兩個索引,其需要分別進行鎖定,對於聚集索引,僅需要在a=5上加Record Lock,對於輔助索引,加上Next-Key Lock,鎖定的範圍是(1,3),特別注意的是InnoDB還會對下一個鍵值加上gap Lock,即還有一個輔助索引範圍(3,6)的鎖。

對於下面的三條語句:
在這裏插入圖片描述

上面這三條都會被阻塞。
在這裏插入圖片描述
上面的3條不會被阻塞,會立即執行。

Gap Lock的作用是爲了組織多個事務將記錄插入到同一個範圍內,而這個會導致Phantom Problem(幻讀)問題的產生。比如在上面的例子中,會話A用戶鎖定了b=3的記錄,如果沒有Gap Lock鎖定(3,6),那麼用戶可以插入索引b列爲3的記錄,這回導致會話A中的用戶再次執行同樣查詢時會返回不同的記錄,導致幻讀問題。

用戶可以通過下面的方式關閉Gap Lock:

  • 將事務的隔離級別改成已提交讀
  • 將參數innodb_locks_unsafe_for_binlog設置爲1.

在InnoDB中,對於Insert操作,會檢查插入記錄的下一條記錄誰都被鎖定,如果已經被鎖定,不允許插入。比如說鎖定b=3,那麼(1,3)範圍不可以插入會阻塞,因爲下一個記錄是3.

2、解決Phantom Problem(幻讀)

在默認的事務隔離級別下,可重複讀下,InnoDB使用Next-Key Locking機制來避免幻讀。或者使用可串行化隔離級別。
可串行化感覺就是一個開啓兩個事務,如果一個查了,另外一個事務就不能做insert操作,因爲可能會導致幻讀,再比如一個事務只要insert了,另外一個事務就不能查,會阻塞

幻讀是指在同一事務下,連續執行兩次同樣的SQL可能會導致不同的結果,第二次的SQL語句可能會返回之前不存在的行。

show create table t5;
+-------+---------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                            |
+-------+---------------------------------------------------------------------------------------------------------+
| t5    | CREATE TABLE `t5` (
  `id` int(11) DEFAULT NULL,
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+----------------------------------------------------------------

幻讀例子:

時間 Session A Session B 備註
1 beigin;select id from t5 where id>4; //結果輸出一行,id爲7
2 begin;insert into t5 values(8);
3 select id from t5 where id>4; // 結果輸出一行,id爲7 這裏事務Session B沒有提交,不管是Read COMMITTED還是REPEATABLE READ都是不可見的
4 commit
5 select id from t5 where id>4; //結果輸出一行,id爲7 當前的事務隔離級別是REOEATABLE,InnoDB通過MVCC機制提供一致性非鎖定讀,故當前事務仍然不可見Session B事務
6 update t5 set id=10 where id=8; 詭異現象出現了,這裏update的語句竟然可以看到Session B的提交
7 select id from t5 where id>4; //會看到兩行。 Session A再次查詢的時候就可以查看新的update的值,當然這裏是合理的,因爲在一個事務中,應該能看到本事務的修改。
8 commit

在已提交讀隔離級別下,採用的是Record
Lock的方式加鎖,而在可重複讀下使用的是Next-Key Lock方式加鎖。

可串行化如果開啓兩個事務,一個事務比如想插入一個數據,必須等待另外一個事務commit或者rollback。這樣的化,併發量很低。在可重複讀下,使用for update也是使用的是Next-Key Lock但是隻是會鎖住一部分Gap,不會鎖住全部,而可串行化會鎖住全部。

五、鎖問題

通過鎖機制可以實現事務的隔離性要求,使得事務可以併發工作。但是卻帶來了潛在的問題,鎖智慧帶來三種問題,如果解決三種情況的發生,就不會產生併發異常。

1、髒讀

髒數據:事務對緩衝池中行記錄的修改,並沒有被提交。

如果讀到了髒數據,即一個事務可以讀到另外一個事務未提交的數據,違反了數據庫的隔離性。

在這裏插入圖片描述
髒讀發生的事務隔離級別是READ UNCOMMITTED,目前大部分數據庫都至少設置爲READ COMMITTED。InnoDB默認的隔離級別是READ REPEATABLE。

2、不可重複讀

不可重複讀和髒讀的區別是:髒讀是讀到未提交的數據,而不可重複讀讀到的卻是已經提交的數據,但是違反了數據庫事務一致性的要求。

在這裏插入圖片描述
一般來說,不可重複讀是可以接受的,因爲其讀到的是已經提交的數據,本身不會帶來什麼問題。

在InnoDB存儲引中,使用Next-Key Lock算法來避免不可重複讀的問題即幻讀問題。在Next-Key Lock下,對於索引的掃描,不僅是鎖住了掃描到的索引,而且鎖住了索引之間的範圍。因此在這個範圍內的插入都是不允許的。

3、丟失更新

簡單的說就是一個事務的更新操作會被另一個事務的更新操作覆蓋。從而導致數據的不一致。如:

  • 事務T1將行記錄r更新爲v1,但是事務T1沒有提交
  • 此時事務T2將行記錄r更新爲v2,事務T2未提交
  • 事務T1提交
  • 事務T2提交

在當前數據庫的任何隔離級別下,都不會出現丟失更新問題,這是因爲,即使READ UNCOMMITTED的事務隔離級別,對於行的DML操作,需要對行或者其他粗粒度級別的對象加鎖,因此上面的第二個步驟中,事務T2並不難對行記錄r進行更新操作,其會被阻塞,直到事務T1提交。

下面的情況會出現丟失更新:

  • 事務T1查詢一行數據,放入本地內存,並顯示給一個終端用戶User1。
  • 事務T2也查詢該行數據,並將取得的數據顯示給終端用戶User2。
  • User1修改這行數據,更新數據庫並提交
  • User2修改這行數據,更新數據庫並提交

如果用戶User1的修改更新操作丟失了,可能導致嚴重問題。設想銀行發生丟失更新現在,如果一個用戶賬號中有10000元人民幣,第一次轉9000,第二次轉1元,第一次轉的9000因爲網絡和數據的原因,需要等待,如果兩筆操作都成功了,用戶的賬號餘款是9999元,第一次轉的9000沒得到更新。但是在轉賬的另外一個賬戶收到了9000.

將操作變成串行化,對用戶讀取的記錄加上排他X鎖。
在這裏插入圖片描述

六、阻塞

因爲不同鎖之間的兼容性問題,有些時刻一個事務中的鎖要等待另外一個事務中的鎖釋放他佔用的資源,這就是阻塞。

  • 在InnoDB中,參數innodb_lock_wait_timeout用來控制等待的時間,默認50s

  • innodb_rollback_on_timeout用來設定是否在等待超時時對進行中的事務進行回滾操作。

  • innodb_lock_wait_timeout是動態的,可以在MySQL運行的時候對其進行調整

  • set @@innodb_lock_wait_timeout=60

innodb_lock_wait_timeout是靜態的,不可以在啓動的時候進行修改。

  • set @@innodb_rollback_on_timeout=on;

需要牢記的是,在默認情況下InnoDB存儲引擎不會回滾超時引發的錯誤異常,其實InnoDB存儲引擎在大部分情況下都不會對異常進行回滾,如在一個會話中執行如下語句:
在這裏插入圖片描述
在這裏插入圖片描述

會話B中插入記錄5是可以的,但是插入3的時候,因爲會話A中Next-Key Lock算法的關係,需要等待會話中事務釋放這個資源,所以等待後產生了超時,但是再進行select的時候會發現,5這個記錄依然存在,但是事務B沒進行commit和rollback操作,這是非常危險的情況,因此用戶必須判斷是佛呀需要commit還是rollback。

七、死鎖

1、死鎖的概念(解決:先超時的先回滾、)

死鎖指的是兩個或者兩個以上的事務在執行過程中,因爭奪鎖資源而造成的一種互相等待的現象。

解決死鎖問題最簡單的一種方法是超時,當兩個事務互相等待的時候,當一個等待時間超過設置的某一個閾值時,其中一個事務進行回滾,另外一個等待的事務就能繼續進行。

  • innodb_lock_wait_timeout用來設置超時的時間。

如果使用超時,那麼是先來的事務先回滾,但是如果超時的事務權重比較大。如事務操作更新了很多行,佔用了很多的undo log,這時採用FIFO的方式就不是很合適,因爲回滾這個事務的時間相對另外一個事務所佔的時間可能會很多。

因此,除了使用超時,當前數據庫普遍使用了wait-for-graph的方式來進行死鎖檢查。wait-for-graph要求數據庫保存下面兩種信息:

  • 鎖的信息鏈表
  • 事務等待鏈表

通過上述鏈表可以構造一張圖,而在這個圖中如果存在迴路,就代表存在死鎖,因此資源之間發生相互等待。
在這裏插入圖片描述

在這裏插入圖片描述
可以看到t1、t2發信啊了迴路,因此存在死鎖,通過上面的介紹,可以看出wait-for graph是一種較爲主動的死鎖檢測機制,在每個事務請求鎖併發生等待時都會判斷是否存在迴路,如果存在則有死鎖,通常來說InnoDB存儲引擎選擇回滾undo量最少的事務

2、死鎖概率

3、死鎖的例子

死鎖只存在於併發的情況下。死鎖的經典情況A等待B、B等待A。
在這裏插入圖片描述
InnoDB存儲引擎並不會回滾大部分的錯誤異常,但是死鎖除外。發現死鎖後,InnoDB存儲引擎會馬上回滾一個事務。

八、鎖升級

鎖升級指的是將當前鎖的粒度降低,舉例來說,數據庫可以把一個表的1000個行鎖升級爲一個頁鎖,或者將頁鎖升級爲表鎖。

一下情況會發生鎖升級:

  • 由一句單獨的SQL語句在一個對象上持有的鎖的數量超過了閾值,默認這個閾值爲5000。
  • 鎖資源佔用的內存超過了激活內存的40%就會發生鎖升級。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章