MySQL InnoDB儲存引擎之鎖,mysqlinnodb
概念:
鎖是用來管理對共用檔案的並發訪問。innodb會在行層級上對資料庫上鎖。不過innodb儲存引擎會在資料庫內部其他多個地方使用鎖,從而允許對不同資源提供並發訪問。例如操作緩衝池中的LRU列表,刪除,添加,移動LRU列表中的元素,為了保證一致性,必須有鎖的介入。MyISAM引擎是表鎖,而InnoDB提供一致性的非鎖定讀、行級鎖,且行級鎖沒有相關額外的開銷。
鎖
table-level locking(表級鎖)
整個表被客戶鎖定。根據鎖定的類型,其他客戶不能向表中插入記錄,甚至從中讀資料也受到限制MyISAM、MEMORY預設鎖層級,個別時候,InnoDB也會升級為表級鎖
row-level locking(行級鎖)
只有線程當前使用的行被鎖定,其他行對於其他線程都是可用的InnoDB預設行級鎖。是基於索引資料結構來實現的,而不是像ORACLE的鎖,是基於block的。InnoDB也會升級為表級鎖,全表/全索引更新,請求autoinc鎖等
page-level locking(頁級鎖)
鎖定表中某些行集合(稱做頁),被鎖定的行只對鎖定最初的線程是可行。如果另外一個線程想要向這些行寫資料,它必須等到鎖被釋放。不過其他頁的行仍然可以使用BDB預設頁級鎖
lock與latch
latch稱為閂鎖(輕量級的鎖),因為其要求鎖定的時間必須非常短。若持續的時間長,則應用的效能會非常差。在InnoDB儲存引擎中,又可以分為mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證並發線程操作臨界資源的正確性,並且通常沒有死結檢測的機制。latch可以通過命令show engine innodb mutex來進行查看。
由可以看出列Type顯示的總是InnoDB,列Name顯示latch的資訊以及所在源碼的行數,列Status中顯示的os_waits表示作業系統等待的次數。
lock的對象是事務,用來鎖定的是資料庫中的對象,如表、頁、行。並且一般lock的對象僅在事務commit或者rollback後釋放(不同交易隔離等級釋放的時間可能不一樣)。有死結機制。二則的區別如下:
特點:
InnoDB是通過對索引上的索引項目加鎖來實現行鎖。這種特點也就意味著,只有通過索引條件檢索資料,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。
鎖的類型:
有兩種標準的行級鎖:
共用鎖定(S lock):允許一個事務去讀一行,阻止其他事務獲得相同資料集的獨佔鎖定.SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排它鎖(X lock):允許獲得獨佔鎖定的事務更新資料,阻止其他事務取得相同資料集的共用讀鎖和獨佔鎖定.SELECT * FROM table_name WHERE ... FOR UPDATE
InnoDB儲存引擎支援意圖鎖定且設計比較簡練,分為兩種內部使用的意圖鎖定(Intention Locks),這兩種意圖鎖定都是表鎖。(意圖鎖定是InnoDB自動加的)
意圖共用鎖(IS):事務打算給資料行加行共用鎖定,事務在給一個資料行加共用鎖定前必須先取得該表的IS鎖.
意向獨佔鎖(IX):事務打算給資料行加行獨佔鎖定,事務在給一個資料行加獨佔鎖定前必須先取得該表的IX鎖.
表級意圖鎖定與行級鎖的相容情況如:
鎖的查看
在InnoDB1.0版本之前只能通過show engine innodb status(transactions行中查看) 或者 show full processlist來查看當前庫中鎖的請求。但是在這之後在information_schema架構下新增innodb_trx、innodb_locks和innodb_lock_waits三張表記錄當前庫中鎖的情況。
三個表的欄位說明如
一致性非鎖定讀(consistent nonlocking read)
一致性的非鎖定讀是指InnoDB儲存引擎通過行多版本控制(multi_versioning)的方式來讀取當前執行時間資料庫中行的資料。如果讀取的行正在執行delete或者update操作,這時讀取操作不會因此去等待行上鎖的釋放。相反地,InnoDB儲存引擎會去讀取行的一個快照資料(當前行資料的曆史版本)。快照資料是指該行的之前版本的資料,該實現是通過undo段來完成。而undo用來在交易回復資料,因此快照資料本身是沒有額外開銷。而且,讀取快照資料是不需要上鎖的。一致性非鎖定讀是InnoDB儲存引擎的預設讀取方式(在讀取不會佔用和等待表上的鎖)。但是在不同交易隔離等級下,讀取的方式不同,並不是在每個交易隔離等級下都是採用非鎖定的一致性讀。即使都是使用非鎖定的一致性讀,但是對於快照資料的定義格式也各不相同。在交易隔離等級READ COMMITTED(RC)和REPEATABLE READ(RR,InnoDB儲存引擎的預設交易隔離等級)下,InnoDB儲存引擎使用非鎖定的一致性讀。然而,對於快照資料的定義去不相同。在RC交易隔離等級下,對於快照資料,非一致性讀總是讀取被鎖定行的最新一份快照資料。而在RR事務隔離解除綁定下,對於快照資料,非一致性讀總是讀取事務開始時的行資料版本。
一致性鎖定讀
有上文知道,預設的交易隔離等級(RR)模式下,InnoDB儲存引擎的select操作使用一致性非鎖定讀。但是在某些情況下,使用者需要顯式地對資料庫讀操作加鎖以保證資料邏輯的一致性。InnoDB儲存引擎對於select語句支援兩種一致性的鎖定讀操作:
select ... for update:對讀取的行記錄加X鎖,其他事物不能對該行加任何鎖。
select ... lock in share mode:對讀取的行記錄加S鎖,其他事物可以對該行加S鎖,但是如果加X鎖,則會被阻塞。
自增長與鎖
在InnoDB儲存引擎的記憶體結構中,對每個含有自增長值的表都有一個自增長計數器(auto-increment counter)。當對含有自增長的計數器的表進行插入操作是,這個計數器會被初始化,執行如下的語句來得到計數器的值:select max(auto_inc_col) from t for update。插入操作會依據這個自增長的計數器值加1賦予自增長列。這個實現方式稱為AUTO-INC Locking,這是一種特殊的表鎖機制,為了提高插入的效能,鎖不是在一個事務完成之後才釋放,而是在完成對自增長值插入的SQL語句後會立即釋放。AUTO-INC Locking在一定程度上提高了並發插入的效率,但是還存在一些效能上的問題。首先,對於有自增長值的列的並發插入效能較差,事務必須等待前一個插入的完成(不用等待事務的完成)。其次,對於insert ... select的大資料量的插入會影響插入的效能,因為另一個事務中的插入會被阻塞。從MySQL5.1.22版本開始,InnoDB儲存引擎提供了一種輕量級互斥量的自增長實現機制,這種機制大大提高了自增長值插入的效能。通過參數innodb_autoinc_lock_mode來控制自增長的模式(預設為1)。自增長的插入進行分類:
innodb_autoinc_lock_mode的參數值及其對自增長的影響如:
MyISAM儲存引擎是表鎖,自增長不用考慮並發插入的問題。需要注意的是:在InnoDB儲存引擎中,自增長值的列必須是索引,同時必須是索引的第一個列,如果不是第一個列,MySQL是會拋出異常的。異常
外鍵與鎖
外鍵主要用於完整性的約束檢查。在InnoDB儲存引擎中,對於一個外鍵列,如果沒有顯式地對這個列加索引,InnoDB儲存引擎會自動對其加一個索引,避免表鎖。對於外鍵值的插入或者更新,首先需要查詢父表中的記錄,對於父表的select操作,不是使用的一致性非鎖定讀的方式,因為這樣會發生資料不一致的問題,所以這時使用的是select ... lock in share mode方式,即主動給父表加一個S鎖。
鎖的問題
dirty read 髒讀
髒讀就是讀取到髒資料(未提交的資料)。一個事務(A)讀取到另一個事務(B)中修改後但尚未提交的資料,並在這個資料的基礎上操作。這時,如果B交易回復,那麼A事務讀到的資料是無效的。不符合一致性。
首先事務的隔離等級有預設的RR改為RU,由上述例子可以看出會話B中兩次select操作取得了不同的結果,並且這2條記錄是會話A中並未提交的資料,這就產生了髒讀。由此可以得出結論:髒讀發生的條件是事務的隔離等級為RU。
unrepeatable read 不可重複讀取
事務(A)讀取到了另一個事務(B)已經提交的更改資料,不符合隔離性。不可重複讀取和髒讀的區別是:髒讀是讀到未提交的資料,而不可重複讀取則讀到的是已經提交的資料。首先將交易隔離等級調整為RC,然後操作下邊的例子:
phantom read 幻讀
事務(A)讀取到了另一個事務(B)提交的新增資料,不符合隔離性。
鎖的範圍(鎖的演算法):
1.Record Lock :單個記錄上的鎖,總是會去鎖住索引記錄,如果InnoDB儲存引擎表在建立的時候沒有設定任何一個索引,那麼這時InnoDB儲存引擎會使用隱式的主鍵來進行鎖定。
2.Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身。
3.Next-key Lock: 鎖定一個範圍和本身 Record Lock + Gap Lock,防止幻讀。
主鍵索引和唯一輔助索引 = record lock
非唯一輔助索引 = next-key lock
阻塞
不同鎖之間的相容性關係,在有些時刻一個事務中的鎖需要等待另外一個事務中的鎖釋放它所佔用的資源,這就是阻塞。阻塞並不是一件壞事,其是為了確保事務可以並發且正常地運行。在InnoDB儲存引擎中,參數innodb_lock_wait_timeout用來動態控制等待的時間(預設50秒),innodb_rollback_on_timeout用來靜態設定釋放在等待逾時時對進行的事務進行復原操作(預設OFF,代表不復原)。
死結
死結是指兩個或者兩個以上的事務在執行過程中,因爭奪資源而造成的一種相互等待的現象。解決死結最簡單的一種方式是逾時,即當兩個事務相互等待是,當一個等待時間超過設定的某一閥值時,其中一個事務進行復原,另外一個等待的事務就能繼續進行。在InnoDB儲存引擎中,參數innodb_lock_wait_timeout用來設定逾時時間。但若逾時的事務所佔權重比較大,如果事務操作更新了很多行,佔用了較多的undo log,這時採用FIFO的方式就不合適啦,因為復原這個事務的時間相對於另一個事務所佔用的時間可能會很多。因此,除了逾時機制,當前資料庫都普遍採用wait-for graph(等待圖)的方式來進行死結檢測。要求資料庫報錯以下兩種資訊:a.鎖的資訊鏈表;b.事務等待鏈表。通過上述鏈表可以構造一張圖,而在這個圖中若存在迴路,就代表存在死結。在wait-for graph中,事務為圖中的節點。
可以發現存在迴路(1,2),因此存在死結,這時InnoDB儲存引擎選擇復原undo量最小的事務。wait-for graph的死結檢測通常採用深度優先的演算法實現。
注意:
1.S X IS IX,表示的是,本鎖和其他鎖共存的方式,是互斥還是相容
2.RECORD LOCK、GAP LOCK、NEXT-KEY LOCK,表示的是,這些鎖要載入的範圍,是行記錄本身,還是行記錄+間隙,甚至更大的範圍
重要的結論:
1、任何輔助索引上的鎖,或者非索引列上的鎖,最終都要回溯到主鍵上,在主鍵上也要加一把鎖
2、任何葉子節點上的S或X鎖之前,都會在根節點加一個IS或IX鎖,也就是表層級的IS、IX鎖
3、主鍵索引上的鎖,都是record lock
4、唯一索引輔助索引上的鎖,也都是record lock
5、非唯一索引輔助索引上的鎖,則是next-key lock
6、不會有單獨的gap lock出現,只會伴隨著record lock出現,依附於它