一、隱藏字段對 InnoDB 的行鎖(Record Lock)與間隙鎖(Gap Lock)的影響
1. 隱藏字段與鎖的三大核心影響
類型 | 影響維度 | 描述 |
---|---|---|
DB_TRX_ID | MVCC 可見性控制 | 決定是否讀取當前版本,或在加鎖時避開不可見版本(影響加鎖粒度) |
DB_ROLL_PTR | 構建多版本鏈 | 影響鎖等待、加鎖時的記錄版本選擇(間接決定是否鎖沖突) |
隱式 RowID | 行定位與加鎖 | 無主鍵時 RowID 作為唯一定位字段,對行鎖加鎖范圍關鍵 |
2.?DB_TRX_ID
與行鎖沖突判斷
? 場景:事務并發修改同一行數據
-
讀取事務依據當前事務的
ReadView
,根據DB_TRX_ID
判斷該版本是否“可見”。 -
寫入事務加鎖時,若當前行的
DB_TRX_ID
≠ 當前事務,則可能出現“當前鎖沖突”或“等待前版本釋放”。 -
若版本不可見,可能繞過該記錄不加鎖(如 RC 隔離級別下的 Next-Key Lock)
🎯 例子:RC 與 RR 加鎖行為不同
-- T1: 開啟事務,修改一行
BEGIN;
UPDATE user SET age = 30 WHERE id = 100;-- T2: 并發事務,嘗試更新相同 id
BEGIN;
UPDATE user SET age = 35 WHERE id = 100; -- 被阻塞
解釋:
-
InnoDB 通過
DB_TRX_ID
比較當前行的修改者是誰; -
若與當前事務不同 → 加鎖或等待;
-
Read Committed 級別可能跳過不可見版本的加鎖(幻讀可能出現)。
3.?DB_ROLL_PTR
與版本鏈上的加鎖行為
? 場景:一行數據有多個歷史版本
-
DB_ROLL_PTR
指向 undo log(之前的版本); -
在 RR 隔離級別下,InnoDB 可能根據
ReadView
沿著版本鏈找到合適版本讀取; -
加鎖時只鎖當前版本(最新版本),但讀取時可能讀取舊版本。
🎯 幻讀控制與間隙鎖相關:
-- T1: 查詢 WHERE age > 30
-- T2: 在該范圍內插入新記錄
-- T1: 再次查詢,同樣語句,發現多了新記錄 → 幻讀
為了防止 T2 插入“未來可能匹配”的記錄,InnoDB 使用
Gap Lock
或Next-Key Lock
對間隙加鎖。
是否加鎖哪些行/間隙,DB_ROLL_PTR
決定了當前記錄是否屬于可見范圍 → 是否參與鎖計算。
4. 隱式 RowID 與加鎖定位(Record Lock)
? 場景:無主鍵表
CREATE TABLE t (name VARCHAR(100)
) ENGINE=InnoDB;
-
InnoDB 自動創建一個 6 字節 RowID(隱式主鍵)
-
聚簇索引以 RowID 為 key 建樹
-
所有輔助索引也指向 RowID
🔐 加鎖行為:
-
對于無主鍵表,InnoDB 通過 RowID 精確加鎖;
-
行鎖定位依賴 RowID;
-
輔助索引加鎖回表時,也依賴 RowID 判斷目標行;
📌 所以:如果你不建主鍵,行鎖仍然是可精確的,但依賴的是隱式 RowID
5. 隱藏字段如何影響三種鎖類型
鎖類型 | 是否依賴隱藏字段 | 說明 |
---|---|---|
行鎖(Record) | ? RowID , DB_TRX_ID | 判斷是否鎖沖突、鎖定位 |
間隙鎖(Gap) | ? DB_TRX_ID , ROLL_PTR | 是否跳過某些版本,加鎖哪些間隙 |
Next-Key Lock | ? 混合依賴 | 加鎖實際記錄+其間隙 |
6. 實戰舉例:兩個事務交錯更新記錄
-- T1
BEGIN;
SELECT * FROM user WHERE age > 30 FOR UPDATE;-- T2
BEGIN;
INSERT INTO user(id, name, age) VALUES (5, 'Mike', 35);
🔍 解析:
-
T1 通過聚簇索引掃描記錄,遇到每一行:
-
檢查
DB_TRX_ID
,判斷是否可見; -
依據可見版本決定加鎖(Next-Key Lock → 行+間隙);
-
-
T2 插入時,必須檢測新記錄是否在 T1 加鎖區間內 → 若是則阻塞;
即便某一行在 T1 的快照中不可見,但只要它是當前版本,T1 可能仍然加鎖(受隔離級別控制)
7. 隱藏字段在鎖機制中的作用
隱藏字段 | 對鎖的影響 |
---|---|
DB_TRX_ID | 決定事務是否看到當前版本 → 影響加鎖行為與鎖沖突判定 |
ROLL_PTR | 構造歷史版本鏈,決定 MVCC 可見性 → 影響是否需要加鎖 |
Row ID | 無主鍵表唯一標識 → 用于加鎖定位、輔助索引回表行鎖 |
RecordHeader | 是否刪除標志 → 已刪除記錄是否參與加鎖由此決定 |
二、深入剖析行級鎖和間隙鎖
主要將深入剖析:
-
? 各種鎖類型的定義與底層機制
-
? 鎖的觸發場景與加鎖策略
-
? InnoDB 加鎖流程與隱藏字段的關系
-
? 常見加鎖案例分析(如幻讀、唯一鍵沖突)
-
? 可視化鎖沖突與調試技巧
1. InnoDB 鎖類型總覽
鎖類型 | 粒度 | 描述 |
---|---|---|
? 行鎖(Record Lock) | 精確鎖住一行記錄(聚簇索引記錄) | |
? 間隙鎖(Gap Lock) | 鎖住兩個索引記錄之間的“間隙”,不含記錄本身 | |
? Next-Key Lock | 行鎖 + 間隙鎖,鎖住記錄及其前后間隙 | |
🔄 插入意向鎖(Insert Intention Lock) | 特殊鎖 | 標記插入意圖,不是互斥鎖,但參與死鎖檢測 |
2. 行鎖(Record Lock)
📌 定義:
鎖定聚簇索引中的一條具體記錄。
🔧 加鎖條件:
-
明確通過 主鍵 / 唯一鍵 精確定位某條記錄;
-
觸發語句通常為:
SELECT * FROM t WHERE id = 1 FOR UPDATE; UPDATE t SET name = 'x' WHERE id = 1;
🧬 底層機制:
-
鎖記錄基于 B+ 樹中記錄的物理位置;
-
鎖信息存儲在 鎖數據結構 lock_t 中,并掛載到事務事務結構 trx_t 的鎖鏈表。
3. 間隙鎖(Gap Lock)
📌 定義:
鎖住兩條索引記錄之間的范圍(gap),但不包括已有的記錄。
🔧 加鎖場景:
-
防止幻讀:防止其他事務在該范圍內插入新記錄;
-
RR
(Repeatable Read)下執行范圍條件的SELECT ... FOR UPDATE
、DELETE
、UPDATE
; -
示例:
-- 假設表中已有 id = 100, 200
SELECT * FROM t WHERE id > 100 AND id < 200 FOR UPDATE;
-- 鎖定的是 (100, 200) 的間隙,不包括100和200
🧬 底層機制:
-
鎖住 B+ 樹中的兩個鍵值之間的指針區域;
-
無具體記錄,但會在鎖表中以特殊“GAP”標志表示。
4. Next-Key Lock(默認使用)
📌 定義:
Next-Key Lock = Record Lock + Gap Lock
即鎖住 記錄本身 + 其前面的間隙
🔧 加鎖場景:
-
默認隔離級別為 RR(可重復讀) 時,InnoDB 對范圍查詢使用 Next-Key Lock;
-
作用:
-
防止幻讀(新插入記錄“幻出現”)
-
保證范圍讀一致性
-
🌰 舉例:
-- 表中已有 id = 100, 200
SELECT * FROM t WHERE id >= 100 AND id < 200 FOR UPDATE;
此時鎖住范圍:
-
間隙 (100, 200)
-
記錄 id = 100
5. 鎖的觸發機制
1?? 鎖的決定因素
影響項 | 描述 |
---|---|
SQL 類型 | SELECT ... FOR UPDATE / DELETE / UPDATE 會加鎖 |
隔離級別 | RR 使用 Next-Key Lock,RC 只鎖記錄本身 |
訪問條件 | 主鍵精確命中加 Record Lock,范圍條件加 Gap Lock/Next-Key Lock |
是否命中索引 | 走索引加行鎖;走全表掃描加表鎖 |
2?? 加鎖時機
-
執行語句解析完成、訪問 B+ 樹查找記錄時;
-
遇到匹配記錄時,根據事務隔離級別加鎖;
-
加鎖時會檢查
DB_TRX_ID
,判斷該版本是否對當前事務可見;-
若不可見(由其他事務正在修改),可能等待或加鎖歷史版本(undo 構建視圖)
-
6. 鎖沖突案例剖析
📍 幻讀問題(RR下通過 Gap Lock/Next-Key Lock 解決)
-- T1
BEGIN;
SELECT * FROM t WHERE age > 30 FOR UPDATE;-- T2
INSERT INTO t(age) VALUES (35); -- 被阻塞(因 T1 已加鎖間隙)
📍 唯一鍵沖突(加鎖 + 意向鎖)
-- T1
INSERT INTO t(id, name) VALUES (100, 'A');-- T2
INSERT INTO t(id, name) VALUES (100, 'B'); -- 被阻塞(同一主鍵)
📍 死鎖觸發
-- T1
UPDATE t SET name = 'A' WHERE id = 1;-- T2
UPDATE t SET name = 'B' WHERE id = 2;-- 然后相互更新對方的記錄,將觸發死鎖
7. 加鎖調試技巧
? 查看當前鎖情況:
SELECT * FROM information_schema.innodb_locks;
SELECT * FROM performance_schema.data_locks;
? 死鎖日志:
SHOW ENGINE INNODB STATUS \G
可定位誰等待誰、加了什么鎖、是否超時或死鎖。
8. 鎖類型與機制全圖
┌────────────────────┐│ SQL 語句類型 │└────────────────────┘│┌────────▼────────┐│ 是否走索引? │──否──? 表鎖(意外情況)└────────┬────────┘│是┌────────▼────────┐│ 鎖定條件類型 │└────────┬────────┘精確匹配 │ 范圍匹配│ ▼┌──────▼──────┐ ┌────────────┐│ 行鎖 │ │ Next-Key 鎖 │└─────────────┘ └────────────┘↑ ↑RC 可降為 Record Lock RR 加間隙鎖避免幻讀
三、深入剖析 MySQL InnoDB 中多事務并發場景下的鎖競爭與回滾機制
1. 核心概念概覽
概念 | 描述 |
---|---|
鎖競爭 | 多個事務試圖訪問同一資源(記錄/間隙)但互斥,形成等待或死鎖 |
回滾機制 | 事務執行失敗、沖突或死鎖時,撤銷已執行部分操作,恢復一致狀態 |
死鎖檢測與回滾策略 | InnoDB 采用 Wait-for Graph 檢測死鎖,選擇某個事務回滾釋放鎖 |
2. 典型并發沖突與回滾案例
📍 案例 1:更新相同記錄引發鎖等待
表結構:
CREATE TABLE account (id INT PRIMARY KEY,balance INT
) ENGINE=InnoDB;
INSERT INTO account VALUES (1, 1000), (2, 2000);
并發場景:
-- Session A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;-- Session B
START TRANSACTION;
UPDATE account SET balance = balance + 100 WHERE id = 1; -- 被阻塞
🔍 分析:
-
id=1
被 Session A 先鎖定(行鎖)。 -
Session B 嘗試更新同一記錄,被阻塞。
-
若 A 提交或回滾,B 才能繼續執行。
📍 案例 2:幻讀沖突引發間隙鎖競爭(RR隔離)
-- Session A
START TRANSACTION;
SELECT * FROM account WHERE id BETWEEN 1 AND 3 FOR UPDATE;-- Session B
INSERT INTO account VALUES (3, 3000); -- 阻塞
🔍 分析:
-
A 加了 Next-Key Lock:鎖定了 id=1 和 id=2 以及間隙 (2,∞)。
-
插入 id=3 的操作沖突于間隙鎖,Session B 阻塞。
-
A 提交或回滾后,B 才能插入。
📍 案例 3:死鎖發生,InnoDB 檢測并回滾
-- Session A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;-- Session B
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 2;-- Session A
UPDATE account SET balance = balance + 100 WHERE id = 2; -- 阻塞-- Session B
UPDATE account SET balance = balance + 100 WHERE id = 1; -- 死鎖
🔍 死鎖圖:
A 等待 B 釋放 id=2
B 等待 A 釋放 id=1
? 形成死鎖
💥 InnoDB 處理機制:
-
InnoDB 啟動“死鎖檢測器”,構建 Wait-for Graph;
-
選擇一個開銷更小的事務(通常是等待時間短的),執行自動回滾;
-
拋出錯誤:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
3. InnoDB 回滾機制詳解
? 回滾觸發點:
-
顯式
ROLLBACK
-
死鎖檢測回滾事務
-
唯一鍵/外鍵沖突
-
DDL 失敗隱式回滾
? 回滾操作步驟:
-
利用隱藏字段
DB_ROLL_PTR
回溯 Undo Log 鏈; -
撤銷所有已變更記錄(覆蓋舊值);
-
釋放已加鎖資源(行鎖、間隙鎖);
-
標記事務為
ROLLING BACK
狀態;
? 相關結構:
結構 | 描述 |
---|---|
Undo Log | 存儲舊版本數據用于回滾與 MVCC |
TRX結構體 | 保存事務狀態及鎖信息鏈表 |
Lock Hash Table | 管理鎖持有和等待信息 |
4. 鎖沖突 & 回滾實驗演示命令
🔧 查看當前鎖持有狀態
-- 查看當前鎖
SELECT * FROM information_schema.innodb_locks;-- 查看鎖等待關系
SELECT * FROM information_schema.innodb_lock_waits;
🔧 查看死鎖日志
SHOW ENGINE INNODB STATUS\G;
查看最新一次死鎖信息、涉及記錄、被回滾的事務等。
5. 最佳實踐建議
場景 | 建議 |
---|---|
多事務更新熱點記錄 | 使用悲觀鎖 + 樂觀重試機制,避免死鎖 |
范圍鎖定 | 使用主鍵精準定位,減少間隙鎖 |
防止死鎖 | 保證事務更新順序一致,如永遠先更新 id小的記錄 |
并發沖突排查 | 使用 innodb_status + performance_schema.data_locks 分析鎖鏈和回滾 |
6. 總結
關鍵點 | 內容 |
---|---|
鎖競爭 | 由事務訪問沖突資源引發,可能阻塞 |
回滾 | 自動或手動撤銷事務操作,使用 Undo 日志還原 |
死鎖檢測 | InnoDB 內部維護等待圖,自動檢測并終止代價小的事務 |
調試工具 | information_schema 、SHOW ENGINE INNODB STATUS 、慢查詢日志等 |