mysql原理(九) 鎖,你更新一條記錄真的不會造成死鎖嗎?

鎖是數據庫區別於文件系統的一個關鍵特性。鎖機制用於管理共享資源的併發訪問。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支持兩種一致性鎖定讀:

  1. select * from table where id = X for update 對數據加X鎖,其他事物不能對該行數據加任何鎖。
  2. 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來解決幻讀問題。幻讀問題是指在同一事務的兩次查詢中,可能得到不同的結果,即第二次可能查詢到之前不存在的行。

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