開發多用戶、數據庫驅動的應用時,最大的一個難點是:一方面要最大程度地利用數據庫地併發訪問,另一方面還要確保每一個用戶能以一致地方式讀取和修改數據。算法
鎖機制用於管理對共享資源地併發訪問。InnoDB存儲引擎會在行級別上對錶數據上鎖。數據庫
latch是一種輕量級地鎖,分爲mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證併發線程操做臨界資源地正確性,而且一般沒有死鎖檢測機制。併發
lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。lock的對象僅在事務commit或rollback後進行釋放。另外,lock有死鎖機制。測試
3.1 鎖的類型線程
InnoDB存儲引擎還支持一種額外的鎖方式,即意向鎖(Intension lock)。意向鎖將鎖定的對象分爲多個層次,意向鎖意味着事務但願在更細粒度上進行加鎖。 3d
若將上鎖的對象當作一個樹,那麼對對下層的對象上鎖(最細粒度的對象上鎖),須要首先對粗粒度的對象上鎖。以下圖所示,若是須要對頁上的記錄r上X鎖,須要對數據庫A、表、頁上意向鎖IX,最後對記錄r上X鎖。版本控制
鎖的兼容性以下圖所示。對象
3.2 一致性非鎖定讀blog
一致性非鎖定讀(consistent nonlocking read)指InnoDB存儲引擎經過行多版本控制的方式來讀取當前執行時間數據庫中的行數據。若是讀取的行正在執行DELETE或UPDATE操做,這時,讀取操做不會等待行上鎖的釋放,InnoDB存儲引擎會去讀取行的一個快照數據。非鎖定讀機制能夠極大的提升數據庫的併發性。 索引
讀取的快照數據來自undo段,undo用來在事務中回滾數據,所以快照數據自己沒有額外的開銷。由圖中能夠看到,一個行記錄可能有不止一個快照數據,通常稱這種技術爲行多版本技術。由此帶來的併發控制,稱之爲多版本併發控制(multi version concurrency control MVCC)。
在READ COMMITED 事務隔離級別下,對於快照數據,一致性非鎖定讀老是讀取非鎖定行的最新一份快照數據。在REPEATABLE READ 事務隔離級別下,對於快照數據,一致性非鎖定讀老是讀取事務開始時的行數據版本。
如上表所示執行,在時間點5,兩種隔離模式,獲得的結果同樣,即id = 1; 在時間點7,兩種隔離模式,會獲得不一樣的結果,READ COMMITED 獲得 Empty Set (讀取最新的行數據快照), REPEATABLE READ還是id =1(事務開始時的行數據)。
3.3 一致性鎖定讀
在某些狀況下,用戶須要顯式地對數據庫讀取操做進行加鎖以保證數據邏輯的一致性。InnoDB存儲引擎對於SELECT語句支持兩種一致性地鎖定讀(locking read)操做:
此外,這兩種操做必須在一個事務中,當事務提交了,鎖也就釋放了。所以,在使用上述兩句SELECT鎖定語句時,務必加上BEGIN, START TRANSACTION 或者SET AUTOCOMMIT=0。
3.4 外鍵和鎖
對於外鍵地插入或更新,首先須要查詢父表中的記錄,即SELECT父表。可是對於父表的SELECT操做,不是使用一致性非鎖定讀的方式,由於這樣會發生數據不一致的問題。這時,使用的是SELECT...LOCK IN SHARE MODE方式,即主動對父表加一個S鎖。若是這時父表上已經有了X鎖,子表上的操做會被阻塞,以下表所示。
4.1 行鎖的三種算法
InnoDB存儲引擎有三種行鎖的算法:
InnoDB對行的查詢都是採用Next-Key-Lock算法,該算法能夠解決Phantom Problem,假如一個索引有10,11,13,20這四個值,那麼被索引的區間爲:(-∞, 10], (10, 11], (11, 13], (13, 20], (20, +∞)
當查詢的列是惟一索引時,會降級爲Record Lock,如果輔助索引,狀況會不太同樣,先建立以下測試表z:
如今會話A中執行上面的SQL語句,因爲b列是輔助索引,Next-Key-Lock算法會鎖定(1,3] ,另外,特別須要注意的是,InnoDB存儲引擎還會對輔助索引下個鍵值(即6)加上gap lock,因此鎖定的輔助索引爲1 2 3 4 5,因此運行下面的SQL語句都會被阻塞。
而下面的SQL語句則不會被阻塞:
4.2 解決Phantom Problem
在默認的事務隔離級別下,即REPEATABLE READ下,InnoDB存儲引擎採用Next-Key-Locking機制來避免Phantom Problem(幻像問題)。
Phantom Problem是指在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL語句可能返回以前不存在的行。
假設表由一、二、5三個值組成。若執行以下的SQL語句:
會話A在時間3 和 7 執行的SQL語句會獲得不一樣的結果。爲了不Phantom Problem,對於上述SQL語句,其鎖住的不是5這個值,而是對(2,∞)這個範圍加了X鎖。所以,對於這個範圍的插入都是不被容許的,從而避免了Phantom Problem。
5.1 髒讀
髒數據是指事務對緩衝池中行記錄進行了修改,可是尚未提交的數據。若是讀到了髒數據,即一個事務能夠讀到另一個事務中未提交的數據,則顯然違反了數據庫的隔離性(髒讀)。下表是一個髒讀的例子。
READ UNCOMMITTED能夠應用在一些比較特殊的狀況。例如,replication環境中的slave節點,而且在該slave上的查詢並不須要特別精確的返回值
5.2 不可重複讀
不可重複讀是指在一個事務內屢次讀取同一數據集合。在這個事務尚未結束時,另一個事務也訪問同一數據集合,並作了一些DML操做。所以,在第一個事務中的兩次讀數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的數據多是不同的,即一個事務內兩次讀到的數據是不同的,即不可重複讀。不可重複讀的示例以下表所示。
InnoDB存儲引擎的默認事務隔離級別是READ REPEATABLE,採用Next-Key-Lock算法,避免了不可重複讀的現象。
5.3 丟失更新
一個事務的更新操做會被另外一個事務的更新操做所覆蓋,從而致使數據的不一致。出現下面的狀況時,就會發生丟失更新:
1) 事務T1查詢一行數據,放入本地內存,並顯示給一個終端用戶User1
2) 事務T2也查詢該行數據,並將取得的數據顯示給終端用戶User2
3) User1修改該行記錄,更新數據庫並提交
4) User2修改該行記錄,更新並提交數據
要避免丟失更新發生,須要事務在這種狀況下的操做變成串行化,而不是並行的操做。以下表所示:
由於不一樣鎖之間的兼容性關係,在有些時刻一個事務中的鎖須要等待另外一個事務中的鎖釋放它所佔用的資源,這就是阻塞。
在InnoDB存儲引擎中,參數innodb_lock_wait_timeout用來控制等待的時間(默認50s,動態參數,能夠在運行時調整),innodb_on_timeout(靜態參數,不可在啓動後,修改)用來設定是否在等待超時時,對進行中的事務進行回滾操做(默認是OFF,表明不回滾)。當作默認設置時,可能存在以下問題:
由以上代碼可知,事務B因爲等待事務A釋放a<4的鎖資源發生了超時,雖然沒有進行COMMIT操做,可是數值5仍是插入到了數據庫中。這是十分危險的狀態,用戶必須判斷是否須要COMMIT仍是ROLLBACK,而後再進行下一步操做。
當兩個或兩個以上的事務在執行過程當中,因爭奪鎖資源而形成的一種互相等待的現象。
解決死鎖問題最簡單的方法就是超時回滾(當一個等待時間超過閾值,進行回滾)。
數據庫通常採用wait-for graph(等待圖)的方式來進行死鎖檢測,須要保存如下兩種信息:
在Transaction list 中能夠看到共有四個事務t1, t2, t3, t4.
存在t1 t2的迴路,故而存在死鎖。InnoDB存儲引擎通常選擇回滾undo量最小的事務。
鎖升級是指將當前鎖的粒度下降。即把一個表的1000個行鎖升級爲一個頁鎖,或者將頁鎖升級爲表鎖。
InnoDB不存在鎖升級問題。其根據每一個事務訪問的每一個頁對鎖進行管理,採用位圖的方式。所以,無論一個事務鎖住頁中一個記錄仍是多個記錄,開銷一般是一致的。
假設一張表有3 000 000個數據頁,每一個頁大約有100條記錄,總共有300 000 000條記錄。若一個事務更新全表更新語句,須要對全部記錄加X鎖。若根據每行記錄產生鎖對象,假設每一個鎖10字節,則鎖管理須要3GB內存。
而InnoDB存儲引擎根據頁進行加鎖,每一個頁的鎖信息佔30個字節,則鎖對象僅需90MB內存。