《MySQL技術內幕四》-InnoDB-各種鎖的東東

《MySQL技術內幕-InnoDB存儲引擎》學習筆記四-各種鎖的東東

2019-07-16 ヾ(◍°∇°◍)ノ゙ 不要攔着我 我還能學一會兒

2019-07-16 ヾ(◍°∇°◍)ノ゙ 不要攔

第6章 鎖的東東

簡單的說說:就是防止數據不一致,同時修改數據的情況存在的鎖,然後就是不同鎖之間的操作限制關係了。挺複雜的,不過了解一下就好了 (:з」∠) 通常也就是查看一下,也沒法做什麼操作的樣子。


關於鎖

鎖嘛,分爲兩種:lock事務所 和 latch閂鎖(輕量級鎖)。

兩種鎖的簡單查看:

-- latch 
show engine innodb mutex;
-- lock 
show engine innodb status;
-- 而關於具體額字段屬性意義,需要的時候請在查詢吧!懶得記錄了╮(╯_╰)╭ 

InnoDB存儲引擎的鎖

鎖類型與兼容模式

InnoDB實現了以下兩種類型的行鎖

  • 共享鎖(s):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。
  • 排他鎖(X):允許獲取排他鎖的事務更新數據,阻止其他事務取得相同的數據集共享讀鎖和排他寫鎖。

另外,爲了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖

  • 意向共享鎖(IS):事務打算給數據行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(IX):事務打算給數據行加排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

簡單的個人理解:S鎖就是讀取,基本都隨意的;X爲刪改操作的鎖,處理的時候啥都不能在東了。而加I 的表示在表級意向而已,只是說明這個表用在操作,具體什麼操作還是要看行級鎖的。

而具體的兼容如下:(兼容爲可以同時操作不衝突,不用等待鎖釋放)

當前鎖模式/是否兼容/請求鎖模式 X IX S IS
X 衝突 衝突 衝突 衝突
IX 衝突 兼容 衝突 兼容
S 衝突 衝突 兼容 兼容
IS 衝突 兼容 兼容 兼容

在操作數據的時候會給對應的行、表加鎖的。如果一個事務請求的鎖模式與當前的鎖兼容,InnoDB就請求的鎖授予該事務;反之,如果兩者兩者不兼容,該事務就要等待鎖釋放。

這裏有查看鎖情況的SQL:

-- 就是這三個 具體字段啥意思 自個查詢吧 
select * from information_schema.innodb_trx
select * from information_schema.innodb_locks
select * from information_schema.innodb_lock_waits
-- 這3個東東的數據是實時的,所以一般情況下是空的
--【更有可能的是,沒有information_schema庫的查看權限,新增的用戶默認是沒有此權限的】

讀取情況的鎖使用

InnoDB的讀取數據的時候,會給讀取的數據加上鎖,而如果讀取到正在修改的數據時,就會衝突啥的,所以爲了併發效率啥的,就有了一致性非鎖定讀這種東東,而對應的一致性鎖定讀 看看就好。

一致性非鎖定讀:這個是InnoDB默認的讀數據模式,就是在讀取數據的時候,如果出現數據已經被別的事務修改的情況,則會去讀取快照數據(使用undo中的回滾數據獲取),已保證讀取不用等待。

在不同事務隔離情況下讀取還有不同,可以瞭解一下:

默認的InnoDB的事務隔離是REPEATABLE READ,這種情況下讀取的的是事務開始是的快照;

而READ COMMITTED 事務情況下,讀取的是最新的快照(算上還在事務中,但已提交的快照);

select @@tx_isolation
-- 查看事務類型
set session tx_isolation = 'XXXXXXXX'

一致性鎖定讀:這東東壓根感覺不會使用的,在讀取數據的時候手動添加數據的X/S鎖,保證數據讀取的時候沒有事務在修改數據,這種是會阻塞其他事務的。

操作的話也記一下好了╮(╯_╰)╭:

select ... for update		 --這個加X鎖
select ... lock in share mode --這個加S鎖
-- 使用的時候,當事務提交,鎖就被釋放了。
-- 所以使用的時候務必加上BEGIN,START TRANSACTION 或者 SET AUTOCOMMIT = 0 ;

這種一致性鎖定讀,會用到的地方是在外鍵使用的時候,數據插入時回會去SELECT父表,爲保證數據一致,這時會給父表加上S鎖,這時如果有處理附表數據是的X鎖事務就會被阻塞了。

感覺也就這裏會用到一致性鎖定讀了,自己平常做的WEB方面的業務都用不到的。。ε=(´ο`*)))唉 。。

鎖的算法(略)

簡單鎖說一下,有三種:(簡單瞭解一下就可以了)

  • Record Lock:單個行記錄上的鎖,【這時最好理解的鎖了】
  • Gap Lock : 間隙鎖,鎖定一個範圍,但不包含記錄本身
  • Next-Key Lock:Gap Lock +Record Lock的模式,鎖定一個範圍,且包含記錄本身。對於行查詢,這個是默認的鎖定算法。

鎖的問題

這應該是InnoDB執行併發事務所帶來的一些的問題。 相對於串行處理來說,併發事務處理能大大增加數據庫資源的利用率,提高數據庫系統的事務吞吐量,從而可以支持可以支持更多的用戶。但併發事務處理也會帶來一些問題,如下;

幻讀【Phantom Problem】

在一個事務下,連續執行兩次相同的SQL語句可能導致不同的結果,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱爲“幻讀”。

解決的方式就是前邊說的,鎖的算法Next-Key Lock。比如一個事務中查詢:select * from table where id >x for update 這個時候會給區間id ->(x,+∞)的數據全部加上X鎖。這個範圍的數據都不允許插入的,避免幻讀。

在正常我們的業務的事務裏面,好像都沒有誰用到了 一致性鎖定讀 這種的寫法。(ˇˍˇ) 想~

髒讀【Dirty Read】

一個事務讀取到了,其他事務中已對數據做的修改,但是沒有提交的數據,也叫髒數據,並據此做進一步的處理,就會產生未提交的數據依賴關係。這就是所謂的髒讀。

這種的問題只會出現在事務爲Read uncommitted的情況,通常是不可能會遇到的。正常默認數據庫的事務級別都比這個高的。╮(╯_╰)╭ 略過了。。。

不可重複讀【Non-Repeatable Reads】

一個事務在讀取某些數據已經發生了改變、或某些記錄已經被刪除了!這種現象叫做“不可重複讀”。

這東東跟幻讀很像,一個是處理的時候別的事務插入數據,這個是別的事務修改數據。

更新丟失【Lost Update】

就是兩個事務同時處理一條數據,最後兩個事務提交的時候其中一個被另一個覆蓋了。

這裏有兩種情況:

第一種:就是標準意義上的更新丟失:【這種只要是有事務的都不會發生】

  • 事務T1更新記錄r爲v1,但事務爲提交 【–預計值vaulev1】
  • 事務T2更新記錄r爲v2,也未提交事務 【–預計值vaulev1v2】
  • 事務T1提交【–結果值vaulev1 】
  • 事務T2提交【 --結果值vaulev1v2 – 丟失更新值vaulev2】

這裏有個前提,這裏的更新是直接使用update 語句進行數據更新操作(包括業務邏輯也在語句中)
比如這裏的語句是update table set vaule = CONCAT(vaule,'v1/v2') where id = r

這種情況出現的T2事務覆蓋T1事務的情況爲標準意義上的更新丟失,但是通才是不可能會出現的,如論什麼級別的事務,在更新的時候都會加鎖,也就是在T2更新的時候會因爲T1的事務而阻塞的。

第二種:常見業務處理上的更新丟失:【這種是會發生的,尤其是在錢包處理上】

  • 事務T1查詢user1錢包金額amount,放入本地回顯或者業務處理;
  • 事務T2查詢user1錢包金額amount,放入本地回顯或者業務處理(這裏可能展示在不同終端上);
  • 事務T1修改金額amount(內存中的值) - 100塊 ,並提交。
  • 事務T2修改金額amount (內存中的值)- 100塊 ,並提交。

這裏會出現的問題是,最終的數據只是 amount -100,其中前一條的數據更新丟失了。

這裏對於這第二種的問題,書中給出的解決方式是:兩個事務都使用select .. for update方式加X鎖。

但是我認爲這個不是很好用,因爲在正常業務的情況下,以上T1事務查詢和修改可能還不是同一個事務。

所以我覺得,應該把修改值直接使用UPDATE的時候限制,如:update wallet set amount = amount - 100 where user = user1 and amount >= 100,這樣就變成第一種的情況了,也就不會發生更新丟失了。

注意:在業務處理的時候需要判斷update語句是否更新了具體的數據,因爲有可能更新數量爲0;爲數據條件邊界的問題。

事務級別

通常有4種隔離級別的事務:

隔離級別/讀數據一致性及允許的併發副作用 讀數據一致性 髒讀 不可重複讀 幻讀
未提交讀(Read uncommitted) 最低級別,只能保證不讀取物理上損壞的數據
已提交度(Read committed) 語句級
可重複讀(Repeatable read) 事務級 【InnoDB默認事務級別】
可序列化(Serializable) 最高級別,事務級

最後要說明的是:各具體數據庫並不一定完全實現了上述4個隔離級別,例如,Oracle只提供Read committed和Serializable兩個標準級別,另外還自己定義的Read only隔離級別:SQL Server除支持上述ISO/ANSI SQL92定義的4個級別外,還支持一個叫做"快照"的隔離級別,但嚴格來說它是一個用MVCC實現的Serializable隔離級別。MySQL支持全部4個隔離級別,但在具體實現時,有一些特點,比如在一些隔離級下是採用MVCC一致性讀,但某些情況又不是。

阻塞

在事務中的鎖衝突,需要等待其他線程釋放資源的情況。默認情況下等待時間50秒,並且超時不會滾的。

-- 查看阻塞超時時間設置  默認50 秒
select @@innodb_lock_wait_timeout
-- 查看鎖超時是否回滾  默認 --OFF:不會滾
show variables like 'innodb_rollback_on_timeout'   

所以這裏如果事務【大的存儲過程同時執行】,出現阻塞超時的情況需要程序判斷處理的是都ROLLBACK。

死鎖

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

MyISAM表鎖是deadlock free的,這是因爲MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼等待,因此不會出現死鎖。但是在InnoDB中,除單個SQL組成的事務外,鎖是逐步獲得的,這就決定了InnoDB發生死鎖是可能的。

發生死鎖後,InnoDB一般都能自動檢測到,並使一個事務釋放鎖並退回,另一個事務獲得鎖,繼續完成事務。

然後,死鎖的發生概率是相當低的,等碰到的再說吧。

注意:如果有使用一致性鎖定讀或者降低了事務隔離模式的情況下才需要注意一下死鎖的問題!

鎖升級

就是行鎖,升級爲頁鎖,或者表鎖,這樣子的東東。

在InnoDB中有兩種情況下會發生這種升級的情況:

  • 單獨的SQL語句在一個對象上持有的鎖數量超過閾值,默認爲5000條。【需要是統一個對象的,不同對象不疊加】
  • 鎖資源佔用的內存超過了激活內存的40%的情況。

小結一下

這個鎖的東東,在瞭解了之後,需要注意幾點即可,爲的是避免一些由於鎖導致的問題:

  • 對於事務上的更新丟失,在代碼業務編寫等地方的時候需要尤其注意一下
  • 對不同程序(包括存儲過程)訪問處理同一組表的時候,應儘量約定以相同的順序訪問各表,對一個表而言,儘可能以固定的順序存取表中的行。這樣可以大減少死鎖的機會。
  • 選擇合理的事務大小,小事務發生鎖衝突的機率也更小。(尤其在使用存儲過程處理大數據業務的時候)

2019-07-17 小杭

鎖這個東東,沒什麼要配置調整的,只是平常使用的時候要注意一下即可!

之後看看事務講解的章節,這個可能會比較麻煩了 (:з」∠)

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