鎖是數據庫區別於文件系統的一個關鍵特性。鎖機制用於管理共享資源的併發訪問。InnoDB除了在表上面進行上鎖之外,在其他層面也會進行上鎖,如操作緩衝池當中的LRU列表,刪除、添加和移動都需要有鎖的介入。本文只討論在InnoDB中的鎖。
一、InnoDB存儲引擎中的鎖
1.1 鎖的類型
1)共享鎖(s lock):允許多個事務讀一行數據。
2)排它鎖(x lock):允許一個事務修改或刪除數據。
鎖的兼容和鎖的不兼容
X | S | |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
例:
T1獲取了行r的共享鎖,T2仍然可以獲取r的共享鎖,這稱爲鎖的兼容。
如果此時T3想要獲取r的排他鎖,則需要等到T1和T2釋放r的共享鎖之後,這稱爲鎖的不兼容。
InnoBD允許行級鎖和表級鎖同時存在,爲了支持在不同粒度上執行加鎖操作,支持了額外的鎖方式,稱之爲意向鎖(Intention Lock)。InnoDB設計的意向鎖比較簡單,因爲其支持行級鎖,所以意向鎖僅設計在表鎖上。其主要目的是爲了揭示下一行將要被請求的鎖類型。
1)意向共享鎖(IS Lock)想要獲得一張表中某幾行的共享鎖。
2)意向排它鎖(IX Lock)想要獲得一張表中某幾行的排它鎖。
意向鎖不會阻塞除全表掃描以外的請求。
在information_schema當中有以下三張表可以讓我們分析鎖的情況:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS。
INNODB_TRX:
首先查看INNODB_TRX中的主要段含義:
字段名 | 說明 |
---|---|
trx_id | 唯一事務id |
trx_state | 事務狀態 |
trx_started | 事務開始時間 |
trx_requested_lock_id | 等待事務的鎖id,如trx_wait爲lock_wait的時候;如果不是trx_wait,則是null |
trx_wait_started | 事務等待開始時間 |
trx_weight | 權重,該值反映修改和鎖住的行數。當發生死鎖需要回滾時,會回滾該值最小的記錄 |
trx_mysql_thread_id | mysql中的線程id |
trx_query | 事務運行的sql語句 |
INNODB_LOCKS:
首先查看INNODB_LOCKS中的主要段含義:
|字段名|說明|
|lock_id|鎖的id|
|lock_trx_id|事務的id|
|lock_mode|鎖模式|
|lock_type|鎖的類型,表鎖和行鎖|
|lock_table|要加鎖的表|
|lock_index|鎖住的索引|
|lock_space|鎖對象的space id|
|lock_page|事務鎖定頁的數量。表鎖該值爲null|
|lock_rec|事務鎖定記錄的數量,表鎖該值爲null|
|lock_data|事務鎖定記錄的主鍵值,表鎖該值爲null|
INNODB_LOCK_WAITS:
首先查看INNODB_LOCK_WAITS中的主要段含義:
字段名 | 說明 |
---|---|
requesting_trx_id | 申請鎖資源的事務id |
requested_lock_id | 申請鎖的id |
blocking_trx_id | 阻塞的事務id |
blocking_lock_id | 阻塞鎖的id |
1.2 一致性非鎖定讀
一致性非鎖定讀是指InnoDB通過多版本併發控制MVCC(Multi-Version Concurrency Control)來讀取當前執行時間數據庫中行的數據。
如果一行或多行數據正在進行update或者delete操作,這時候另外的事務去讀取這些數據並不會等待其釋放鎖,可以直接進行讀取。讀取的是其快照數據。
之所以稱之爲一致性非鎖定讀,因爲不需要等到行上面的X鎖進行釋放即可進行讀取。如下圖所示:
快照數據是指行之前版本的數據,通過undo log實現。
如上圖所示,一個行的版本可能有多個,一般稱之爲多版本技術,由此帶來的併發控制稱之爲多版本併發控制(MVCC)。
在事務隔離級別read commited(oracle默認)和repeatable read(innoDB的默認隔離級別)中,使用非鎖定一致性讀,然而其對於快照讀的定義卻不相同。
隔離級別 | 讀取版本 |
---|---|
rc | 讀取最新版本數據 |
rr | 讀取最開始版本數據 |
下面看個例子:
時間 | 會話1 | 會話2 |
---|---|---|
1 | begin | |
2 | select * from student where id = 1 | |
3 | begin | |
4 | update student set id =3 where id = 1 | |
5 | select * from student where id = 1 | |
6 | commit | |
7 | select * from student where id = 1 | |
8 | commit |
如上表格所示:
在rr級別下,會話1讀取到的結果分別是:1,1,1
在rc級別下,會話1讀取到的結果分別是:1,1,empty
1.3 一致性鎖定讀
在某些特定情況下,用戶需要顯示的對數據庫讀取進行加鎖操作以保證數據的一致性。InnoDB對select支持兩種一致性鎖定讀:
- select * from table where id = X for update 對數據加X鎖,其他事物不能對該行數據加任何鎖。
- select * from table where id = X lock in share model 對數據加S鎖,其他事務可以對其加S鎖,但是加X鎖會被阻塞。
以上加鎖操作必須保證在一個事務當中,一旦事務提交後,鎖即被釋放掉。所以使用時不需使用begin、start transaction、set autocommit = 0。
1.4 自增長和鎖
自增長是一種常見的使用方式,也是數據主鍵的首選。InnoDB對於每一個含有自增長屬性的表都維護一個自增長計數器。會依據該計數器的值自動加1賦予自增長列。這種方式稱作Auto Inc Locking,這是一種特殊的表鎖機制,而這種方式存在一定的性能問題。
在mysql 5.1.22開始,提供了一種輕量級互斥量的自增長方式,這種機制大大提高了自增長值插入的性能。並且提供一個innodb_autoinc_lock_mode來控制自增長,默認爲1。另外還有0和2兩種方式。
0:併發性能不好,在5.1.22版本前使用。
1:默認值。
2:性能最高,併發插入會帶來一定的問題,導致不連續。
二、鎖的算法
2.1 三種行鎖的算法:
1)Record Lock:單條記錄鎖。
2)Gap Lock:區間鎖或間隙鎖,鎖定一個範圍但不包含本身。
3)Next-key Lock:臨鍵,Record Lock + Gap Lock,鎖定範圍區間的同時並且鎖定記錄本身。
如下索引值有1,5,9,11,name在三種鎖的不同區間如下所示:
Next-key Lock算法是結合了record lock 和gap lock,其出現的原因是爲了解決幻讀(snapshot read),利用這種技術,鎖定的不再是單個值,而是一個範圍。
如果查詢的索引含有唯一索引時,Next-key Lock將會降級爲Record lock,僅鎖住索引本身。
如果查詢的索引是輔助索引,使用Next-key Lock進行加鎖。按照上圖給出下面一個sql,key是輔助索引,本例子不寫唯一索引了:
select * from table where key = 5 for update;添加了X鎖
那麼會對5這個值增加(1,5]的前區間,還會使用gap lock對其增加一個後區間(5,9),因此,運行一下sql都會被阻塞:
select * from table where key = 6 lock in share mode;無法對X鎖添加S鎖
insert into table select 2;2在鎖定區間,被阻塞
insert into table select 7;7在鎖定區間,被阻塞
Gap Lock的作用是爲了解決幻讀問題,只在rr級別存在,如果想要關閉可以修改默認隔離界別爲rc,但是這違反了隔離性。
在默認的隔離級別repeatable read下,Innodb使用next-key lock來解決幻讀問題。幻讀問題是指在同一事務的兩次查詢中,可能得到不同的結果,即第二次可能查詢到之前不存在的行。