大家好!今天我要和各位分享一個在 MySQL 項目中經常讓開發者頭疼的問題——InnoDB 的死鎖問題。相信不少朋友都遇到過這樣的情況:一個好好運行的系統突然報錯,日誌裏冒出"Deadlock found when trying to get lock; try restarting transaction",然後你就開始了漫長的排查之旅...
別擔心,這篇文章會用真實案例帶你從現象到根源,徹底掌握死鎖的排查技巧和解決方法。無論你是數據庫管理員還是後端開發,這些內容都能幫你在實際工作中少走彎路。
InnoDB 鎖機制基礎知識
在深入案例前,我們先用通俗的話聊聊 InnoDB 的鎖機制。
InnoDB 有幾種主要的鎖類型:
- 共享鎖(S 鎖):大家一起看,不能改(SELECT ... LOCK IN SHARE MODE)
- 排他鎖(X 鎖):我改數據時誰都別動(SELECT ... FOR UPDATE, UPDATE, DELETE)
- 意向鎖(IS/IX 鎖):打個招呼説"我要在這張表的某些行上加鎖了"
- 記錄鎖:鎖住單條記錄
- 間隙鎖:鎖住一個範圍,但不包含記錄本身
- 臨鍵鎖(Next-Key Lock):記錄鎖+間隙鎖的組合,防止幻讀的重要機制
- 插入意向鎖:一種特殊的間隙鎖,表示插入操作的意向,多個事務可以在同一間隙中設置插入意向鎖而不衝突,但會與間隙鎖衝突
需要特別強調的是:間隙鎖和臨鍵鎖只在 REPEATABLE READ 隔離級別下默認生效。在 READ COMMITTED 隔離級別下,InnoDB 不使用間隙鎖,臨鍵鎖會退化為記錄鎖,這是減少死鎖的重要知識點。
想象一下,鎖就像是在圖書館看書。共享鎖就像大家一起看同一本書但不能寫批註,排他鎖就像你借走了這本書,別人就看不了了。
什麼是死鎖?
死鎖就像兩個人互相給對方讓路,結果誰都動不了的尷尬局面。在數據庫中,當兩個或多個事務互相等待對方釋放鎖,導致所有事務都無法繼續執行時,就形成了死鎖。
舉個簡單例子:小明要拿筷子和碗才能吃飯,小紅也一樣。現在小明拿了筷子,小紅拿了碗,兩人都在等對方放下手中的東西,結果誰都吃不了飯。
案例一:經典的行鎖更新死鎖
場景描述
假設我們有一個訂單系統,有一張orders表,結構如下:
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`product_id` int NOT NULL,
`status` int NOT NULL,
`amount` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB;
系統中存在兩個併發執行的事務,導致了死鎖:
- 事務 A:更新用户 1 的所有訂單狀態
- 事務 B:更新特定產品相關的所有訂單金額
問題重現
以下是導致死鎖的操作序列:
這裏需要注意一個重要點:InnoDB 的行鎖是通過索引實現的。在這個案例中,事務 A 使用user_id索引,事務 B 使用product_id索引,導致鎖定的記錄順序不同,增加了死鎖風險。如果查詢條件未命中索引,情況會更糟,可能導致表鎖或大範圍的臨鍵鎖。
死鎖日誌分析
當死鎖發生時,MySQL 會在錯誤日誌中記錄詳細信息。使用SHOW ENGINE INNODB STATUS可以查看最近一次死鎖的詳情:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-04-17 14:32:51 0x7f9a1c3a2700
*** (1) TRANSACTION:
TRANSACTION 10795, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 155, OS thread handle 140301189614336, query id 9697 localhost root updating
UPDATE orders SET status=2 WHERE user_id=1 AND id>2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 72 index PRIMARY of table `test`.`orders` trx id 10795 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 6; hex 000000002a25; asc *%;;
2: len 7; hex 81000000110137; asc 7;;
3: len 4; hex 80000002; asc ;;
4: len 4; hex 80000065; asc e;;
5: len 4; hex 80000001; asc ;;
*** (2) TRANSACTION:
TRANSACTION 10794, ACTIVE 5 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 156, OS thread handle 140301235861248, query id 9696 localhost root updating
UPDATE orders SET amount=amount*1.1 WHERE product_id=101 LIMIT 1 OFFSET 1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 24 page no 4 n bits 72 index PRIMARY of table `test`.`orders` trx id 10794 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 6; hex 000000002a25; asc *%;;
2: len 7; hex 81000000110137; asc 7;;
3: len 4; hex 80000002; asc ;;
4: len 4; hex 80000065; asc e;;
5: len 4; hex 80000001; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 72 index PRIMARY of table `test`.`orders` trx id 10794 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000002a23; asc *#;;
2: len 7; hex 81000000110135; asc 5;;
3: len 4; hex 80000001; asc ;;
4: len 4; hex 80000065; asc e;;
5: len 4; hex 80000001; asc ;;
*** WE ROLL BACK TRANSACTION (1)
從日誌中我們可以看到:
- 事務 A(
TRANSACTION 10795)想獲取 id=3 的行鎖,但被事務 B 持有 - 同時事務 B(
TRANSACTION 10794)想獲取 id=1 的行鎖,但被事務 A 持有 - 形成循環等待,MySQL 檢測到死鎖並回滾了事務 A
日誌中一些專業術語解釋:
lock_mode X:代表排他鎖(X 鎖)locks rec but not gap:表示只鎖記錄,不鎖間隙(記錄鎖)heap no:表示記錄在索引頁中的位置
解決方案
對於這種情況,以下是按實用性排序的解決方法:
- 統一訪問順序(最有效):確保所有事務按相同的順序訪問記錄,例如總是按主鍵順序
-- 事務A
BEGIN;
UPDATE orders SET status=2 WHERE user_id=1 ORDER BY id;
COMMIT;
-- 事務B
BEGIN;
UPDATE orders SET amount=amount*1.1 WHERE product_id=101 ORDER BY id;
COMMIT;
- 減小事務範圍:不要在一個大事務中做太多事情,拆分為多個小事務
-- 原來的大事務
BEGIN;
UPDATE orders SET status=2 WHERE user_id=1;
-- 其他操作...
COMMIT;
-- 拆分後
BEGIN;
UPDATE orders SET status=2 WHERE user_id=1 AND id BETWEEN 1 AND 100;
COMMIT;
BEGIN;
UPDATE orders SET status=2 WHERE user_id=1 AND id BETWEEN 101 AND 200;
COMMIT;
- 添加適當的鎖超時設置:
SET innodb_lock_wait_timeout = 50; -- 設置鎖等待超時
- 使用樂觀鎖替代悲觀鎖(適合讀多寫少場景):
-- 使用版本號控制
UPDATE orders SET amount=amount*1.1, version=version+1
WHERE product_id=101 AND version=當前版本;
需要注意的是,樂觀鎖在併發衝突頻繁的場景下可能導致大量重試,反而降低性能。此時應考慮悲觀鎖加合理的鎖超時設置。
案例二:Gap 鎖導致的死鎖
場景描述
在 REPEATABLE READ 隔離級別下,InnoDB 會使用間隙鎖(Gap Lock)來防止幻讀。這種鎖可能導致一些不直觀的死鎖。
假設有一個用户積分表:
CREATE TABLE `user_points` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`points` int NOT NULL,
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB;
表中已有數據:
id | user_id | points | created_at
1 | 1 | 100 | 2023-04-15 10:00:00
3 | 3 | 150 | 2023-04-15 11:00:00
5 | 5 | 200 | 2023-04-15 12:00:00
注意這裏 user_id 為 2 和 4 的記錄不存在。
問題重現
這裏的關鍵是理解:在 RR 隔離級別下,即使查詢的記錄不存在,FOR UPDATE也會在該位置獲取間隙鎖或臨鍵鎖。當執行 INSERT 操作時,事務會先請求一個插入意向鎖(Insert Intention Lock),這是一種特殊的間隙鎖。雖然多個事務可以在同一個間隙內持有不同的插入意向鎖(允許併發插入不同位置),但插入意向鎖會與普通間隙鎖衝突,導致等待。
當兩個事務交叉持有間隙鎖並嘗試插入時,就會形成死鎖。
臨鍵鎖案例補充
為更好理解臨鍵鎖,考慮以下場景:
-- 在REPEATABLE READ隔離級別下
BEGIN;
SELECT * FROM orders WHERE id BETWEEN 5 AND 15 FOR UPDATE;
-- 其他操作...
COMMIT;
這個 SELECT 語句會做什麼?它會:
- 對 id 值為 5 到 15 的記錄加記錄鎖(Record Lock)
- 對 id 值範圍(15, "下一個索引值")加間隙鎖(Gap Lock)
- 這兩種鎖合起來形成臨鍵鎖(Next-Key Lock)
這種鎖定策略可以防止其他事務在鎖定範圍內插入新記錄(防幻讀),同時允許對鎖定範圍之外的記錄進行修改。
死鎖日誌分析
------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-04-17 15:12:45 0x7f9a1c3a2700
*** (1) TRANSACTION:
TRANSACTION 10801, ACTIVE 6 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 157, OS thread handle 140301189614336, query id 9712 localhost root update
INSERT INTO user_points(user_id,points,created_at) VALUES(4,120,NOW())
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 25 page no 4 n bits 72 index idx_user_id of table `test`.`user_points` trx id 10801 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
*** (2) TRANSACTION:
TRANSACTION 10802, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 158, OS thread handle 140301235861248, query id 9713 localhost root update
INSERT INTO user_points(user_id,points,created_at) VALUES(2,110,NOW())
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 25 page no 4 n bits 72 index idx_user_id of table `test`.`user_points` trx id 10802 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 4; hex 80000003; asc ;;
*** WE ROLL BACK TRANSACTION (2)
日誌中的關鍵信息:
lock_mode X locks gap before rec insert intention waiting:表示事務嘗試獲取插入意向鎖,但被另一個事務的間隙鎖阻止- 兩個事務分別持有對方需要的間隙鎖,形成死鎖
解決方案
按照有效性排序:
- 降低隔離級別(最直接有效):將隔離級別從 REPEATABLE READ 降低到 READ COMMITTED,這樣就不會使用 Gap 鎖
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
注意:隔離級別調整需謹慎評估業務一致性需求,避免幻讀問題對應用造成影響。
- 使用 INSERT ON DUPLICATE KEY UPDATE:替代先 SELECT 再 INSERT 的模式
INSERT INTO user_points(user_id,points,created_at)
VALUES(2,110,NOW())
ON DUPLICATE KEY UPDATE points=110, created_at=NOW();
- 拆分事務:不要在同一個事務中先查詢再插入
-- 原來的模式
BEGIN;
SELECT * FROM user_points WHERE user_id=2 FOR UPDATE;
-- 如果不存在則插入
INSERT INTO user_points(user_id,points,created_at) VALUES(2,110,NOW());
COMMIT;
-- 改進後
-- 查詢階段(可以使用共享鎖或不加鎖)
SELECT * FROM user_points WHERE user_id=2;
-- 插入階段(單獨事務)
BEGIN;
INSERT INTO user_points(user_id,points,created_at) VALUES(2,110,NOW())
ON DUPLICATE KEY UPDATE points=110, created_at=NOW();
COMMIT;
- 索引優化:確保查詢條件有合適的索引
案例三:外鍵約束導致的死鎖
場景描述
外鍵約束也是死鎖的常見原因。考慮以下兩個表:
CREATE TABLE `departments` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE `employees` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`dept_id` int NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`dept_id`),
CONSTRAINT `fk_dept_id` FOREIGN KEY (`dept_id`) REFERENCES `departments` (`id`)
) ENGINE=InnoDB;
問題重現
這裏的關鍵點:外鍵約束會導致額外的鎖請求。當我們修改引用字段時,InnoDB 需要驗證引用的完整性:
- 如果更新子表的外鍵值(如 dept_id),InnoDB 會檢查父表(departments)中對應的值是否存在,需要在父表相應記錄上添加共享鎖(S 鎖),而非排他鎖
- 如果更新或刪除父表中被引用的記錄,InnoDB 會檢查子表是否有依賴,可能添加父子表間的額外鎖
這些額外的鎖請求大大增加了死鎖的可能性。
死鎖日誌分析
------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-04-17 16:03:11 0x7f9a1c3a2700
*** (1) TRANSACTION:
TRANSACTION 10809, ACTIVE 8 sec updating or deleting
mysql tables in use 2, locked 2
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1
MySQL thread id 159, OS thread handle 140301189614336, query id 9725 localhost root updating
UPDATE employees SET dept_id=2 WHERE id=1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table `test`.`employees` trx id 10809 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000002a3d; asc *=;;
2: len 7; hex 81000000110110; asc ;;
3: len 3; hex 426f62; asc Bob;;
4: len 4; hex 80000001; asc ;;
*** (2) TRANSACTION:
TRANSACTION 10810, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 160, OS thread handle 140301235861248, query id 9726 localhost root update
INSERT INTO departments(id,name) VALUES(3,'HR')
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 4 n bits 72 index PRIMARY of table `test`.`departments` trx id 10810 lock_mode X locks rec but not gap waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
解決方案
按照有效性排序:
- 調整事務順序(最有效):按照固定順序訪問相關表
-- 統一先操作父表,再操作子表
BEGIN;
UPDATE departments SET name='IT' WHERE id=1;
UPDATE employees SET dept_id=2 WHERE id=1;
COMMIT;
- 設置外鍵約束時使用
RESTRICT而非CASCADE:
ALTER TABLE employees DROP FOREIGN KEY fk_dept_id;
ALTER TABLE employees ADD CONSTRAINT fk_dept_id FOREIGN KEY (dept_id)
REFERENCES departments(id) ON DELETE RESTRICT ON UPDATE RESTRICT;
- 分割事務:避免在一個事務中同時操作多個相關表
-- 分兩個事務操作
-- 事務1:更新父表
BEGIN;
UPDATE departments SET name='IT' WHERE id=1;
COMMIT;
-- 事務2:更新子表
BEGIN;
UPDATE employees SET dept_id=2 WHERE id=1;
COMMIT;
- 減少外鍵使用(謹慎考慮):在高併發系統中考慮減少外鍵約束,由應用程序保證數據一致性
案例四:自增鎖死鎖
場景描述
自增鎖是一種特殊的鎖,用於處理 AUTO_INCREMENT 列的值生成。在高併發場景下,自增鎖爭用也可能導致死鎖。
MySQL 通過innodb_autoinc_lock_mode參數控制自增鎖行為:
innodb_autoinc_lock_mode=0:傳統模式,所有插入語句都需要獲取表級鎖-
innodb_autoinc_lock_mode=1(默認值):混合模式,其特點是:- 對於行數已知的插入(如
INSERT ... VALUES),使用輕量級互斥鎖 - 僅對於行數未知的插入(如
INSERT ... SELECT),才使用表級鎖
- 對於行數已知的插入(如
innodb_autoinc_lock_mode=2:交叉模式,所有插入使用輕量級互斥鎖,不保證自增值連續性
問題重現
考慮以下場景:
CREATE TABLE `order_items` (
`id` int NOT NULL AUTO_INCREMENT,
`order_id` int NOT NULL,
`product_id` int NOT NULL,
`quantity` int NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB;
這裏的關鍵點是理解:在innodb_autoinc_lock_mode=1(默認)模式下,INSERT ... SELECT等行數不確定的語句會獲取表級自增鎖,而INSERT ... VALUES等行數確定的語句僅獲取輕量級互斥鎖。當兩種類型的插入混合使用時,容易導致鎖衝突和死鎖。
解決方案
- 調整自增鎖模式(最有效):
-- 全局設置,需要重啓MySQL生效
SET GLOBAL innodb_autoinc_lock_mode = 2;
-- 或在配置文件中設置
[mysqld]
innodb_autoinc_lock_mode = 2
注意:mode=2不保證自增值連續性,但能顯著減少鎖衝突。
- 批量插入優化:儘量在一個語句中插入多行數據,明確指定字段順序
-- 替代多次單行插入
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (101, 1, 2), (101, 2, 1), (101, 3, 5);
- 使用應用生成的 ID:對於高併發系統,考慮使用應用層生成唯一 ID
-- 使用應用生成的UUID或其他唯一ID
INSERT INTO order_items (id, order_id, product_id, quantity)
VALUES (GENERATED_ID, 101, 1, 2);
死鎖排查工具與技巧
1. 使用 SHOW ENGINE INNODB STATUS 查看死鎖信息
SHOW ENGINE INNODB STATUS\G
關注輸出中的"LATEST DETECTED DEADLOCK"部分。
2. 開啓死鎖日誌記錄
SET GLOBAL innodb_print_all_deadlocks = 1;
這會將所有死鎖信息記錄到 MySQL 錯誤日誌中。
3. 使用 performance_schema 監控鎖等待
-- 啓用performance_schema
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME LIKE 'wait/lock/metadata/%' OR NAME LIKE 'wait/lock/innodb/%';
-- 查詢當前鎖等待
SELECT * FROM performance_schema.events_waits_current
WHERE EVENT_NAME LIKE 'wait/lock%';
-- 查詢鎖等待歷史
SELECT * FROM performance_schema.events_waits_history
WHERE EVENT_NAME LIKE 'wait/lock%';
4. 使用工具分析死鎖
- pt-deadlock-logger(Percona 工具集)
- MySQL 企業版監控工具
5. 死鎖監控與預警
其他常見死鎖類型簡述
元數據鎖死鎖
當 DDL 操作(如 ALTER TABLE)與 DML 操作(如 INSERT、UPDATE)併發執行時,可能出現元數據鎖死鎖。
解決方案:
- 將 DDL 操作安排在低峯期
- 使用在線 DDL 工具(如 pt-online-schema-change):這些工具通過創建臨時表、複製數據和表結構交換來避免長時間鎖表
- MySQL 5.6+的
ALGORITHM=INPLACE或 8.0+的ALGORITHM=INSTANT參數可實現某些 DDL 操作的在線執行 - 避免長事務與 DDL 併發
預防死鎖的實用建議
-
控制事務大小和持續時間
- 保持事務短小、快速完成
- 只在必要時使用事務
-
合理設計數據訪問順序
- 按照主鍵或索引順序訪問數據
- 使用 ORDER BY 確保訪問順序一致
-
選擇合適的隔離級別
- 不需要可重複讀的場景使用 READ COMMITTED
- 瞭解每個隔離級別的鎖行為
-
優化索引設計
- 確保查詢條件有合適的索引
- 避免使用不必要的鎖定查詢
-
謹慎使用外鍵
- 高併發系統可考慮減少外鍵約束
- 使用 RESTRICT 代替 CASCADE
-
應用層重試機制
- 捕獲死鎖異常並實現重試邏輯
int retries = 3;
boolean success = false;
while (retries > 0 && !success) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 開啓事務
// 數據庫操作
PreparedStatement ps = conn.prepareStatement("UPDATE...");
ps.executeUpdate();
conn.commit(); // 提交事務
success = true;
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 回滾事務
} catch (SQLException ex) {
// 處理回滾異常
}
}
if (e.getErrorCode() == 1213 && retries > 1) { // MySQL死鎖錯誤碼1213
retries--;
Thread.sleep(100); // 短暫延遲後重試
} else {
throw e; // 重試失敗或其他錯誤,繼續拋出
}
} finally {
if (conn != null) {
conn.close();
}
}
}
-
使用樂觀鎖替代悲觀鎖
- 適合讀多寫少的場景
- 使用版本號或時間戳實現
- 注意:高衝突場景下樂觀鎖可能導致頻繁重試
死鎖排查流程
總結
| 死鎖類型 | 典型特徵 | 解決方案 | 開發成本 | 適用場景 |
|---|---|---|---|---|
| 行鎖更新衝突 | 多個事務更新相同或相關行數據 | 統一訪問順序、減小事務範圍、使用樂觀鎖 | 低 | 高併發多表更新業務 |
| Gap 鎖衝突 | REPEATABLE READ 隔離級別下插入操作死鎖 | 降低隔離級別、使用 ON DUPLICATE KEY UPDATE | 中 | 需要頻繁插入查詢的應用 |
| 外鍵約束死鎖 | 父子表併發操作導致鎖衝突 | 減少外鍵使用、調整事務順序、使用 RESTRICT 約束 | 中高 | 具有複雜關係模型的系統 |
| 元數據鎖死鎖 | DDL 和 DML 語句混合執行 | 將 DDL 操作放在低峯期、使用在線 DDL 工具 | 高 | 需要頻繁架構變更的應用 |
| 自增鎖死鎖 | 多事務同時插入自增列 | 調整 innodb_autoinc_lock_mode、批量插入 | 低 | 高併發寫入場景 |
通過本文的案例和分析,相信你已經對 InnoDB 死鎖有了更深入的理解。記住,死鎖不可完全避免,但可以通過合理的設計和實踐大大減少其發生頻率和影響。當死鎖發生時,保持冷靜,按照本文提供的排查流程,你一定能夠找到問題所在並解決它。
感謝您耐心閲讀到這裏!如果覺得本文對您有幫助,歡迎點贊 👍、收藏 ⭐、分享給需要的朋友,您的支持是我持續輸出技術乾貨的最大動力!
如果想獲取更多 Java 技術深度解析,歡迎點擊頭像關注我,後續會每日更新高質量技術文章,陪您一起進階成長~