MySQL 锁的相关知识 | lock与latch、锁的类型、简谈MVCC、锁算法、死锁、锁升级
文章目錄
- lock與latch
- 鎖的類型
- MVCC
- 一致性非鎖定讀(快照讀)
- 一致性鎖定讀(當(dāng)前讀)
- 鎖算法
- 死鎖
- 鎖升級
lock與latch
在了解數(shù)據(jù)庫鎖之前,首先就要區(qū)分開 lock 和 latch。在數(shù)據(jù)庫中,lock 和 latch 雖然都是鎖,卻有著截然不同的含義。
-
latch 通常被我們稱為閂鎖(輕量級鎖),因為其要求鎖定的時間必須非常短。在 InnoDB 中,latch 可以分為 mutex(互斥鎖) 和 rwlock(讀寫鎖) ,它的作用是用來保證并發(fā)線程操作臨界資源的正確性,并且通常沒有死鎖檢測機制。
-
lock 的操作對象則是事務(wù),用來鎖定數(shù)據(jù)庫中的對象,如表、頁、行等,一般 lock 的對象僅在事務(wù)提交或者回滾后釋放,lock 有死鎖檢測機制。
關(guān)于數(shù)據(jù)庫頁結(jié)構(gòu)的詳細內(nèi)容可以看這篇博客
鎖的類型
在 InnoDB存儲引擎 中實現(xiàn)了下面兩種標準的行級鎖:
- 共享鎖(S Lock) : 允許事務(wù)讀一行數(shù)據(jù)
- 排他鎖(X Lock) : 允許事務(wù)刪除或者更新一行數(shù)據(jù)。
由于共享鎖并不涉及到數(shù)據(jù)的修改,所以即使一個事務(wù)已經(jīng)獲得了某行的共享鎖,另外的事務(wù)也可以立即獲得該行的共享鎖,這種情況又被稱為鎖兼容。
對于排他鎖又是另一種情況,由于排他鎖涉及到了數(shù)據(jù)的修改,為了保證安全,其他的事務(wù)想要獲得同一行的排他鎖時,必須要等到前一個事務(wù)釋放鎖才行,這種情況又被稱為鎖不兼容。
下面是排他鎖和共享鎖的兼容性:
由于 InnoDB 支持多粒度鎖定,所以允許事務(wù)可以同時存在行鎖和表鎖,為了支持在不同粒度上進行加鎖操作,InnoDB 支持一種額外的鎖方式,即 意向鎖(Intention Lock)。意向鎖即將鎖定的對象分為多個層次,意味著希望事務(wù)在更細粒度上進行加鎖。
如果我們想對下層的對象(如記錄【一行就是一個記錄】)上一個 X鎖 ,就需要先對粒度更粗的上層對象上鎖,需要分別先對數(shù)據(jù)庫、表、頁上 意向排他鎖(IX鎖) ,再對記錄上 X鎖 。(如果沒有意向鎖,我們對一行上了讀鎖之后,假如某個事務(wù)申請了一個表級的寫鎖,此時這個事務(wù)就會對我們上鎖的數(shù)據(jù)進行修改。)
PS:讀/寫鎖 是 共享/排他鎖 的一種。
InnoDB 支持意向鎖設(shè)計比較簡練,其意向鎖即為表級別的鎖,意向鎖主要是為了在一個事務(wù)中揭示下一行將被請求的鎖的類型,兩種意向鎖分別如下:
- 意向共享鎖(IS Lock),事務(wù)要想獲得某一張表中某幾行的共享鎖。
- 意向排他鎖(IX Lock),事務(wù)要想獲得某一張表中某幾行的排他鎖。
由于 InnoDB 支持的是行級別的鎖,因此意向鎖不會阻塞 除全表掃描外 的任何請求,故兼容性如下:
MVCC
MVCC(Multi-Version Concurrency Control)即多版本并發(fā)控制,是數(shù)據(jù)庫并發(fā)控制的一種方法
一致性非鎖定讀(快照讀)
如果我們讀取的行正在執(zhí)行 DELETE 或者 UPDATE 操作,這時就不會去等待鎖釋放后再讀取,而是直接去讀取行的一個快照數(shù)據(jù),如下圖所示:
快照數(shù)據(jù)指的是該行之前版本的數(shù)據(jù),這些數(shù)據(jù)存儲在 undo段 中,由于 undo 用來在事務(wù)中回滾數(shù)據(jù),因此這些快照數(shù)據(jù)本身并沒有額外的開銷。并且我們只是讀操作,并不涉及修改,而且也沒有事務(wù)會去對歷史數(shù)據(jù)進行修改,所以在讀取快照數(shù)據(jù)的時候不需要進行加鎖。
快照讀機制讓讀取操作不再占用和等待表上的鎖,極大的提高了數(shù)據(jù)庫的性能。但由于每個快照都相當(dāng)于是一個歷史版本,行的多版本需要并發(fā)控制—— MVCC 。
需要注意的是,MVCC 只在 READ COMMITTED(讀已提交) 和 REPEATABLE READ(可重復(fù)讀)兩個隔離級別下工作。由于 READ UNCOMMITTED(讀未提交) 總會讀取最新的數(shù)據(jù),而 SERIALIZABLE(可串行化) 會對所有讀取的行加鎖,所以這兩種都不兼容 MVCC 。
- 在 RC 隔離級別下,是每個快照讀都會生成并獲取最新的 Read View,也就是同一個事務(wù)中每次快照讀的結(jié)果都不一樣;
- 在 RR 隔離級別下,則是同一個事務(wù)中的第一個快照讀才會創(chuàng)建 Read View, 之后的快照讀獲取的都是同一個 Read View ,也就是同一個事務(wù)中每次快照讀的結(jié)果都跟第一次快照讀一樣。
當(dāng)前讀,快照讀和MVCC的關(guān)系
- MVCC 多版本并發(fā)控制是 「維持一個數(shù)據(jù)的多個版本,使得讀寫操作沒有沖突」 的概念,只是一個抽象概念,并非實現(xiàn)。
- MVCC 只是一個抽象概念,MySQL 需要提供具體的功能去實現(xiàn)它,「快照讀就是 MySQL 實現(xiàn)了 MVCC 理想模型的其中一個功能——非阻塞讀」。而相對而言,當(dāng)前讀就是悲觀鎖的具體功能實現(xiàn)。
- 要說的再細致一些,快照讀本身也是一個抽象概念,再深入研究。MVCC 模型在 MySQL 中的具體實現(xiàn)則是由 3 個隱式字段、undo 日志 、Read View 等去完成的。
更多MVCC知識可以閱讀這篇博客
一致性鎖定讀(當(dāng)前讀)
一致性鎖定讀又稱為當(dāng)前讀,讀取的是行的最新版本,并且讀取的時候為了防止其他事務(wù)修改當(dāng)前行,還會對當(dāng)前行進行加鎖。
在 InnoDB 中,對于 SELECT語句 支持以下兩種一致性鎖定讀操作:
- SELECT…FOR UPDATE(排他鎖) :會對讀取的行加一個排他鎖,此時其他事務(wù)不能對該行上任何鎖。
- SELECT…LOCK IN SHARE MODE(共享鎖) :會位讀取的行加上一個共享鎖,此時其余的事務(wù)可以對該行加上共享鎖,但是如果想加排他鎖,則會被阻塞。
鎖算法
在 InnoDB 中有三種行鎖的算法:
- Record Lock(記錄鎖): 單個行記錄上的鎖。
- Gap Lock(間隙鎖) : 鎖定一個范圍,但不包含記錄本身。
- Next-Key Lock(下一鍵鎖) : 前兩種鎖的結(jié)合,既鎖定一個范圍,也鎖定記錄本身。
舉例:
有一組數(shù)據(jù),其索引分別為 10、30、60 ,此時使用 SQL 語句 SELECT * FROM t WHERE id = 10 FOR UPDATE ,三種鎖的范圍如下
- Record: 對10單行進行加鎖
- Gap Lock : (-∞, 10)、(10, 30)、(30, 60)、(60, +∞)
- Next-Key Lock: (-∞,10]、(10,30]、(30,60]、(60,+∞)
對于 Record Lockl 來說,其總是會去鎖住索引記錄,即使沒有設(shè)置任何一個索引,它也會使用隱式的索引進行鎖定。
Next-Key Lock 是 結(jié)合了前面所說的兩種鎖算法,既鎖住范圍,也鎖住記錄本身,在 InnoDB 中對于行的查詢都會采用這種算法,而設(shè)計它的目的正是為了解決幻讀問題。
幻讀指 在同一事務(wù)中,用同樣的操作讀取兩次,得到的記錄數(shù)卻不一樣(針對同一個范圍的數(shù)據(jù))。 主要原因就是當(dāng)?shù)谝粋€事務(wù)對表中的所有數(shù)據(jù)行進行修改;同時,第二個事務(wù)向表中插入了一行。這樣也就導(dǎo)致了操作第一個事務(wù)的用戶發(fā)現(xiàn)表中還有沒修改的數(shù)據(jù)行,像發(fā)生了幻覺一樣。
明明在 會話A 的第一次查詢中,大于 2 的數(shù)只有行只有一行,而由于 會話B 插入了新行后,對于 會話A 而言就憑空多出來了一行,像出現(xiàn)了幻覺一樣。
對于以上數(shù)據(jù),Next-Key Locking算法 在 SELECT * FROM t WHERE a > 2 FOR UPDATE 這條語句中,鎖住的不僅僅是 5 這個數(shù)值,而是對直接對[2,+∞) 這個范圍加了排他鎖,所以任何對于這個范圍的插入都不能進行,也就避免了幻讀現(xiàn)象的發(fā)生。
同理,間隙鎖 Gap Lock 也是鎖定某個范圍,所以它也能防止幻讀的出現(xiàn)。
InnoDB 正是借助 鎖(Gap Lock、Next-Key Lock) 以及 MVCC(快照讀) 這兩個機制實現(xiàn)了事務(wù)的隔離性。
死鎖
死鎖指的是兩個或者兩個以上的事務(wù)在執(zhí)行過程中,因為爭搶所資源而導(dǎo)致的一種互相等待的現(xiàn)象。在死鎖的情況下,如果沒有外力作用,事務(wù)將永遠無法推進下去。
在數(shù)據(jù)庫中通常都會使用超時機制來解決死鎖:為事務(wù)設(shè)置超時時間,當(dāng)其中一方超時后立刻進行回滾,另一個事務(wù)就能夠繼續(xù)進行了。
雖然超時機制可以解決這個問題,但是我們并不能掌握回滾的事務(wù)的量級,倘若事務(wù)更新龐大,則回滾就會帶來大量的性能損耗,所以我們通常會采用更加主動的策略,即使用等待圖來進行死鎖檢測:
- 圖中每個節(jié)點即為一個事務(wù)。
- 每條指向其他節(jié)點的線則代表著正在等待該節(jié)點的資源。
- 當(dāng)存在回路時,則代表著事務(wù)互相等待,此時就意味著存在死鎖。
每當(dāng)事務(wù)請求鎖并發(fā)生等待時,都會主動判斷等待圖中是否存在回路,如果存在則代表著有死鎖產(chǎn)生,此時就會主動選擇 undo量最小的事務(wù) 來打破死鎖。在現(xiàn)版本的 InnoDB 中,通常采用 深度優(yōu)先搜索(老版本使用遞歸) 來檢測死鎖的存在。
鎖升級
在數(shù)據(jù)庫為了保證安全,大量的并發(fā)下必定存在著大量的鎖,但鎖是一種稀有資源,為了避免大量鎖的開銷,數(shù)據(jù)庫中存在著鎖升級的機制。
鎖升級指的是將當(dāng)前的鎖升級為更粗粒度的鎖 例如我們可以將多個行鎖升級為一個頁鎖,又或者將多個頁鎖升級為一個表鎖。這種升級減少了鎖的數(shù)量、保護了系統(tǒng)資源,防止系統(tǒng)使用太多內(nèi)存來維護大量的鎖,在一定程度上提高了效率。
在 SQL Server 中,鎖升級是很常見的現(xiàn)象,當(dāng)滿足以下條件中其中一個時則會進行鎖升級:
- 鎖資源占用的內(nèi)存超過了激活內(nèi)存的 40%
- 一條單獨的 SQL語句 在一個對象上持有的鎖數(shù)量超過了閾值,閾值默認為 5000
而在 MySQL 的 InnoDB 中,則不存在鎖升級的問題。其根據(jù)每個事務(wù)訪問的每個頁對鎖進行管理,并且使用位圖來標記,所以一個事務(wù)無論鎖住頁中多少條記錄,開銷都相同。
總結(jié)
以上是生活随笔為你收集整理的MySQL 锁的相关知识 | lock与latch、锁的类型、简谈MVCC、锁算法、死锁、锁升级的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jpa java.util.map_使用
- 下一篇: Java连接mysql出现SQL异常,M