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

