深入理解MySQL 鎖之從全局鎖到死鎖檢測(cè)的問(wèn)題
MySQL 的鎖并不是單一的概念。本文從三個(gè)維度對(duì) InnoDB 鎖體系進(jìn)行系統(tǒng)梳理,幫你在實(shí)戰(zhàn)和面試中建立清晰的知識(shí)框架。
鎖的全景圖
理解 MySQL 鎖,可以從三個(gè)維度切入:
| 維度 | 鎖的類(lèi)型 | 核心目的 |
|---|---|---|
| 粒度劃分 | 全局鎖、表級(jí)鎖、行級(jí)鎖 | 控制鎖定的數(shù)據(jù)范圍(InnoDB 以行鎖為主) |
| 兼容性劃分 | 共享鎖 (S)、排他鎖 (X) | 決定多個(gè)事務(wù)能否同時(shí)操作同一塊數(shù)據(jù) |
| 鎖的算法 | 記錄鎖、間隙鎖、臨鍵鎖 | 決定在 B+ 樹(shù)的哪個(gè)位置加鎖 |
第一層:鎖的粒度——你在哪里加鎖?
| 鎖級(jí)別 | 類(lèi)比 | 對(duì)應(yīng) SQL 場(chǎng)景 | 影響 |
|---|---|---|---|
| 全局鎖 | 封鎖整棟大樓,不準(zhǔn)進(jìn)出 | FLUSH TABLES WITH READ LOCK(全庫(kù)備份) | 業(yè)務(wù)全線(xiàn)停擺,極少手動(dòng)使用 |
| 表級(jí)鎖 | 包下整個(gè)樓層搞裝修 | LOCK TABLES 或元數(shù)據(jù)鎖 (MDL) | 影響整張表,并發(fā)能力差 |
| 行級(jí)鎖 | 鎖住具體的房間 | UPDATE ... WHERE id = 1 | InnoDB 的看家本領(lǐng),并發(fā)最高 |
第二層:鎖的兼容性——S 鎖與 X 鎖
這是所有鎖的基礎(chǔ)邏輯:
- 共享鎖(S 鎖 / 讀鎖):我能讀,你也能讀,但大家都別想改。觸發(fā)方式:
SELECT ... FOR SHARE - 排他鎖(X 鎖 / 寫(xiě)鎖):我要改,誰(shuí)也別想碰。觸發(fā)方式:
UPDATE/DELETE/SELECT ... FOR UPDATE
需要注意的是,普通的 SELECT 是不加鎖的(快照讀)。只有顯式加鎖語(yǔ)句才會(huì)觸發(fā)。
意向鎖:表級(jí)別的"打招呼"
當(dāng)你準(zhǔn)備給某一行加 X 鎖時(shí),MySQL 會(huì)先在表級(jí)別加一個(gè)意向排他鎖(IX)。
為什么需要它?想象一張 1.6 億行的表,如果另一個(gè)事務(wù)想加表鎖,沒(méi)有意向鎖的話(huà),數(shù)據(jù)庫(kù)得遍歷 1.6 億行去檢查有沒(méi)有行鎖。有了意向鎖,只需要看一眼表頭就知道了——這是一種空間換時(shí)間的策略。
關(guān)鍵特性:意向鎖之間是兼容的,意向鎖和行鎖之間也是兼容的。它唯一不兼容的是表級(jí)鎖。
第三層:行鎖的三種算法——InnoDB 的精華
這是基于 B+ 樹(shù)的物理鎖,也是面試最高頻的考點(diǎn)。
記錄鎖(Record Lock)
精確鎖住索引樹(shù)上的某一個(gè)葉子節(jié)點(diǎn)。
-- 精準(zhǔn)打擊,只鎖 pk_id = 10 這一行 SELECT * FROM t_mqtt_log WHERE pk_id = 10 FOR UPDATE;
間隙鎖(Gap Lock)
鎖住兩個(gè)索引記錄之間的"縫隙",不包括記錄本身。目的是防止幻讀——阻止其他事務(wù)往這個(gè)縫隙里插入新數(shù)據(jù)。
類(lèi)比:在 101 房間和 105 房間之間的走廊上放一個(gè)"施工中"的牌子,別人想在 102 插入一個(gè)新房間?對(duì)不起,走廊被封了。
臨鍵鎖(Next-Key Lock)
記錄鎖 + 間隙鎖的組合,鎖住一個(gè)范圍并且包含記錄本身。這是 InnoDB 在 REPEATABLE READ 隔離級(jí)別下的默認(rèn)行鎖算法。
鎖與索引的核心關(guān)系
記住一個(gè)準(zhǔn)則:MySQL 的鎖是加在"索引"上的,而不是加在"行"上的。
即使你建表時(shí)沒(méi)有主鍵也沒(méi)有唯一索引,InnoDB 也會(huì)在后臺(tái)自動(dòng)創(chuàng)建一個(gè)隱藏列 RowID 作為聚簇索引。
這意味著:
- 查詢(xún)命中主鍵索引 → 精準(zhǔn)的記錄鎖
- 沒(méi)命中索引導(dǎo)致全表掃描 → 鎖升級(jí)為覆蓋全表的 Next-Key Lock
REPEATABLE READ下間隙鎖防止幻讀;READ COMMITTED下基本沒(méi)有間隙鎖
實(shí)操:三步看透鎖
不需要背書(shū),開(kāi)兩個(gè)終端窗口就能直觀感受。
第一步:制造"對(duì)峙"
窗口 A——開(kāi)啟事務(wù)并加鎖:
BEGIN; SELECT * FROM t_mqtt_log WHERE pk_id = 10 FOR UPDATE;
窗口 B——嘗試修改同一行(會(huì)被阻塞):
UPDATE t_mqtt_log SET type = 1 WHERE pk_id = 10; -- 此時(shí) B 會(huì)卡住,等待 A 釋放鎖
第二步:查看"案發(fā)現(xiàn)場(chǎng)"
在第三個(gè)窗口執(zhí)行:
SELECT * FROM performance_schema.data_locks;
你會(huì)清晰地看到:哪個(gè)線(xiàn)程持有鎖,鎖的是哪棵 B+ 樹(shù),是 X 鎖還是 GAP 鎖,甚至能看到鎖的具體 pk_id。
第三步:分析死鎖
SHOW ENGINE INNODB STATUS;
在 LATEST DETECTED DEADLOCK 一欄,你會(huì)看到完整的死鎖現(xiàn)場(chǎng):誰(shuí)在等誰(shuí),誰(shuí)最后被 MySQL 犧牲掉了。
死鎖:原理與破局
死鎖的發(fā)生需要滿(mǎn)足四個(gè)必要條件(Coffman 條件):
- 互斥:資源在一段時(shí)間內(nèi)只能被一個(gè)事務(wù)鎖定
- 占有且等待:事務(wù)已持有鎖,同時(shí)又在請(qǐng)求另一個(gè)被其他事務(wù)持有的鎖
- 不可搶占:事務(wù)持有的鎖在事務(wù)完成前不能被強(qiáng)制釋放
- 循環(huán)等待:A 等 B,B 等 A,形成環(huán)
經(jīng)典死鎖場(chǎng)景
| 時(shí)間點(diǎn) | 事務(wù) A | 事務(wù) B |
|---|---|---|
| T1 | UPDATE users SET name='A' WHERE id=1;(拿到 ID=1 的鎖) | |
| T2 | UPDATE users SET name='B' WHERE id=2;(拿到 ID=2 的鎖) | |
| T3 | UPDATE users SET name='A2' WHERE id=2;(阻塞,等待 B 釋放 ID=2) | |
| T4 | UPDATE users SET name='B2' WHERE id=1;(死鎖!B 試圖拿 ID=1,但 A 持有) |
InnoDB 的死鎖檢測(cè)機(jī)制
InnoDB 通過(guò) Wait-for Graph(等待關(guān)系圖) 主動(dòng)檢測(cè)死鎖:
- 維護(hù)一張鎖等待關(guān)系圖,一旦發(fā)現(xiàn)環(huán),立即判定死鎖
- 選擇代價(jià)最小的事務(wù)進(jìn)行回滾(通常是更新行數(shù)最少、Undo Log 最少的那個(gè))
- 被回滾的事務(wù)釋放所有鎖,另一個(gè)事務(wù)繼續(xù)執(zhí)行
- 應(yīng)用程序收到經(jīng)典報(bào)錯(cuò):
Deadlock found when trying to get lock; try restarting transaction
代碼層面的預(yù)防策略
無(wú)法完全杜絕死鎖,但以下策略能極大降低概率:
- 固定訪問(wèn)順序:所有業(yè)務(wù)邏輯都按相同順序操作資源(先 ID=1 再 ID=2),從根源上打破循環(huán)等待
- 大事務(wù)拆小:事務(wù)執(zhí)行時(shí)間越長(zhǎng),持有鎖的時(shí)間越長(zhǎng),撞車(chē)概率越大
- 善用索引:沒(méi)命中索引時(shí)鎖會(huì)升級(jí)(Next-Key Lock 甚至鎖全表),鎖范圍越大,死鎖概率越高
- 降低隔離級(jí)別:如果業(yè)務(wù)允許,從 RR 降到 RC 可以消滅大部分間隙鎖,減少死鎖
高頻問(wèn)題補(bǔ)充
元數(shù)據(jù)鎖(MDL)——線(xiàn)上事故高發(fā)區(qū)
MDL 不是加在數(shù)據(jù)上的,而是加在表結(jié)構(gòu)上的。一個(gè)典型的災(zāi)難場(chǎng)景:
- 事務(wù) A 執(zhí)行了一個(gè)慢查詢(xún)
SELECT(未結(jié)束),持有 MDL 讀鎖 - 你執(zhí)行
ALTER TABLE加字段,申請(qǐng) MDL 寫(xiě)鎖,被 A 阻塞 - 之后所有新的
SELECT都會(huì)被這個(gè)ALTER TABLE堵住 - 連接池瞬間爆炸,服務(wù)雪崩
結(jié)論:永遠(yuǎn)不要在線(xiàn)上高峰期對(duì)大表做 ALTER TABLE,除非你明確評(píng)估了 MDL 的影響。
自增鎖(AUTO-INC Locks)
對(duì)于高并發(fā)插入場(chǎng)景(比如 t_mqtt_log 每秒插入幾千次),所有事務(wù)都要搶下一個(gè)自增 ID。InnoDB 通過(guò)自增鎖來(lái)協(xié)調(diào),關(guān)鍵調(diào)優(yōu)參數(shù)是 innodb_autoinc_lock_mode:
0(傳統(tǒng)模式):語(yǔ)句級(jí)鎖,并發(fā)最差1(連續(xù)模式):簡(jiǎn)單插入用輕量鎖,批量插入用語(yǔ)句級(jí)鎖(MySQL 5.x 默認(rèn))2(交錯(cuò)模式):完全并發(fā),性能最好,但批量插入的自增值可能不連續(xù)(MySQL 8.0 默認(rèn))
悲觀鎖 vs 樂(lè)觀鎖
這是業(yè)務(wù)邏輯層面的鎖策略選擇:
- 悲觀鎖:
SELECT ... FOR UPDATE,先占位再干活,適合寫(xiě)沖突頻繁的場(chǎng)景 - 樂(lè)觀鎖:通過(guò)版本號(hào)控制,
UPDATE ... SET version = version + 1 WHERE id = 1 AND version = ?,不鎖行,提交時(shí)檢查是否被修改過(guò),適合讀多寫(xiě)少的場(chǎng)景
總結(jié)
| 知識(shí)點(diǎn) | 核心要點(diǎn) |
|---|---|
| 鎖粒度 | 全局鎖 > 表級(jí)鎖 > 行級(jí)鎖,InnoDB 以行鎖為主 |
| S 鎖 / X 鎖 | 共享鎖允許并發(fā)讀,排他鎖獨(dú)占讀寫(xiě) |
| 意向鎖 | 表級(jí)標(biāo)記,避免加表鎖時(shí)逐行檢查 |
| 記錄鎖 | 精確鎖住索引上的一條記錄 |
| 間隙鎖 | 鎖住記錄間的縫隙,防止幻讀 |
| 臨鍵鎖 | 記錄鎖 + 間隙鎖,InnoDB 默認(rèn)行鎖算法 |
| 鎖加在索引上 | 沒(méi)命中索引 → 鎖升級(jí),范圍越大死鎖概率越高 |
| 死鎖檢測(cè) | Wait-for Graph 檢測(cè)環(huán),回滾代價(jià)最小的事務(wù) |
| MDL | 表結(jié)構(gòu)鎖,高峰期 ALTER TABLE 可致服務(wù)雪崩 |
到此這篇關(guān)于深入理解 MySQL 鎖:從全局鎖到死鎖檢測(cè)的文章就介紹到這了,更多相關(guān)mysql全局鎖到死鎖檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL數(shù)據(jù)庫(kù)表修復(fù) MyISAM
這篇文章主要介紹了MySQL數(shù)據(jù)庫(kù)表修復(fù) MyISAM ,需要的朋友可以參考下2014-06-06
小型Drupal數(shù)據(jù)庫(kù)備份以及大型站點(diǎn)MySQL備份策略分享
為了防止web服務(wù)器出現(xiàn)故障而引起的數(shù)據(jù)丟失,數(shù)據(jù)庫(kù)備份顯得非常重要,以免出現(xiàn)重大損失。本文分析研究一下小型的Drupal站的備份策略以及大型站點(diǎn)的mysql備份策略2014-11-11
mysql之關(guān)于CST和GMT時(shí)區(qū)時(shí)間轉(zhuǎn)換方式
這篇文章主要介紹了mysql之關(guān)于CST和GMT時(shí)區(qū)時(shí)間轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
mysql實(shí)現(xiàn)遞歸查詢(xún)的方法示例
本文主要介紹了mysql實(shí)現(xiàn)遞歸查詢(xún)的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
MySQL如何處理InnoDB并發(fā)事務(wù)中的間隙鎖死鎖
這篇文章主要為大家介紹了MySQL如何處理InnoDB并發(fā)事務(wù)中的間隙鎖死鎖,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
將圖片儲(chǔ)存在MySQL數(shù)據(jù)庫(kù)中的幾種方法
今天小編就為大家分享一篇關(guān)于將圖片儲(chǔ)存在MySQL數(shù)據(jù)庫(kù)中的幾種方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03

