-- 事務 A (插入)
START TRANSACTION;
INSERT INTO parent (id, ...) VALUES (1, ...); -- 鎖 parent 行 (X)
INSERT INTO child (parent_id, ...) VALUES (1, ...); -- 需鎖 child 行 (X)
-- 事務 B (刪除)
START TRANSACTION;
DELETE FROM child WHERE parent_id = 1; -- 鎖 child 行 (X)
DELETE FROM parent WHERE id = 1; -- 需鎖 parent 行 (X)
-- 死鎖:A 持有 parent 等 child,B 持有 child 等 parent
| 方案 | 原理 | 優點 | 缺點/風險 | 適用性 |
|---|---|---|---|---|
| 1. 移除外鍵 | 應用層維護完整性,刪除順序統一為 Parent→Child | 徹底消除鎖順序衝突 | 失去資料庫層面約束,需應用嚴格保證 | 高併發、應用層成熟 |
| 2. 刪除前轉移外鍵 | 先 UPDATE child SET parent_id = 0 (虛擬根節點),再刪 parent,最後刪 child | 保留外鍵約束,統一訪問順序 | 額外 UPDATE 開銷、需維護虛擬行、業務邏輯複雜 | 強一致性要求高 |
| 3. ON DELETE CASCADE | 刪 Parent 級聯刪 Child,刪除事務只操作 Parent | 簡單、原子性、無應用邏輯 | 大量級聯刪除鎖擴大、觸發器連鎖、誤刪風險 | 層級明確、資料量可控 |
應用層規範:所有事務按固定順序訪問表 (如 Parent → Child)。插入自然符合,刪除改為先標記軟刪除 (UPDATE status),異步真刪。
-- 事務內按主鍵排序處理
SELECT * FROM parent WHERE id IN (1,2,3) ORDER BY id FOR UPDATE;
SELECT * FROM child WHERE parent_id IN (1,2,3) ORDER BY parent_id, id FOR UPDATE;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 需配合 binlog_format=ROW
-- 會出現不可重複讀、幻讀,業務必須容忍
-- 應用層重試模板 (指數退避)
for attempt in 1..3:
try:
tx.begin()
# 業務邏輯
tx.commit()
break
except DeadlockError:
if attempt == 3: raise
sleep(2^attempt * 0.05 + random())
tx.rollback()
-- 即時查看死鎖
SHOW ENGINE INNODB STATUS\G
-- 關注 LATEST DETECTED DEADLOCK
-- 歷史死鎖 (需開啟)
innodb_print_all_deadlocks = ON
-- 錯誤日誌記錄完整死鎖圖
Innodb_deadlocks 計數器,設定告警