# MySQL為什么會造成死鎖
## 引言
在數據庫管理系統(DBMS)中,死鎖(Deadlock)是一個經典且棘手的問題。MySQL作為最流行的開源關系型數據庫之一,在實際應用中也難以避免死鎖的發生。本文將深入探討MySQL死鎖的成因、檢測機制、解決方案以及預防策略,幫助開發者和DBA更好地理解和應對這一問題。
## 一、什么是死鎖
### 1.1 死鎖的定義
死鎖是指兩個或多個事務在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力干涉,這些事務都將無法繼續執行下去。
### 1.2 死鎖的四個必要條件(Coffman條件)
- **互斥條件**:資源一次只能被一個事務占用
- **占有并等待**:事務持有資源的同時等待其他資源
- **非搶占條件**:已分配的資源不能被強制剝奪
- **循環等待條件**:存在一個事務等待環路
> 這四個條件必須同時滿足才會產生死鎖,打破任意一個條件即可解除死鎖狀態。
## 二、MySQL中的鎖機制
### 2.1 MySQL鎖的類型
| 鎖類型 | 描述 |
|---------------|----------------------------------------------------------------------|
| 共享鎖(S鎖) | 允許多個事務同時讀取同一資源 |
| 排他鎖(X鎖) | 獨占鎖,其他事務不能加任何鎖 |
| 意向鎖 | 表級鎖,表示事務即將對表中的行加鎖 |
| 記錄鎖 | 鎖定索引中的特定記錄 |
| 間隙鎖 | 鎖定索引記錄之間的間隙 |
| 臨鍵鎖 | 記錄鎖+間隙鎖的組合 |
| 插入意向鎖 | 特殊的間隙鎖,表示將要插入 |
### 2.2 InnoDB的鎖實現特點
1. 基于索引實現的行級鎖
2. 默認使用臨鍵鎖(Next-Key Locking)防止幻讀
3. 二級索引上的鎖會回溯到聚簇索引
## 三、MySQL死鎖的常見場景
### 3.1 不同順序的加鎖請求
```sql
-- 事務1
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 事務2
START TRANSACTION;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
-- 表結構:id為主鍵,score為普通索引
-- 事務1
SELECT * FROM students WHERE score BETWEEN 80 AND 90 FOR UPDATE;
-- 事務2
INSERT INTO students (id, name, score) VALUES (11, '張三', 85);
-- 事務1
INSERT INTO users (id, username) VALUES (10, 'user1');
-- 事務2
INSERT INTO users (id, username) VALUES (10, 'user2');
-- 父表orders,子表order_items
-- 事務1
DELETE FROM orders WHERE order_id = 1001;
-- 事務2
INSERT INTO order_items (item_id, order_id) VALUES (5001, 1001);
InnoDB使用深度優先搜索(DFS)檢測等待圖中的環: 1. 每個事務節點 2. 鎖等待關系作為有向邊 3. 定期檢查圖中是否存在環
innodb_deadlock_detect = ON # 是否啟用死鎖檢測
innodb_lock_wait_timeout = 50 # 鎖等待超時時間(秒)
innodb_print_all_deadlocks = OFF # 是否打印所有死鎖信息到錯誤日志
SHOW ENGINE INNODB STATUS\G
LATEST DETECTED DEADLOCK
------------------------
2023-08-20 14:23:45 0x7f8e4418a700
*** (1) TRANSACTION:
TRANSACTION 123456, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 10, OS thread handle 12345, query id 100 updating
UPDATE accounts SET balance = balance + 100 WHERE id = 2
*** (2) TRANSACTION:
TRANSACTION 123457, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 11, OS thread handle 12346, query id 101 updating
UPDATE accounts SET balance = balance - 50 WHERE id = 1
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 100 page no 3 n bits 72 index PRIMARY of table `test`.`accounts`
trx id 123456 lock_mode X locks rec but not gap
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 100 page no 4 n bits 72 index PRIMARY of table `test`.`accounts`
trx id 123457 lock_mode X locks rec but not gap
*** (2) WTING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 100 page no 3 n bits 72 index PRIMARY of table `test`.`accounts`
trx id 123457 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (2)
-- 啟用死鎖監控
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE NAME LIKE 'events_transactions%';
-- 查詢死鎖事件
SELECT * FROM performance_schema.events_transactions_current
WHERE STATE = 'DEADLOCK';
批量插入優化
-- 低效方式(可能產生大量間隙鎖)
INSERT INTO orders VALUES (1), (2), (3), ... (1000);
-- 優化方式
INSERT INTO orders VALUES (1);
INSERT INTO orders VALUES (2);
...
INSERT INTO orders VALUES (1000);
分頁查詢優化
-- 可能產生死鎖的方式
SELECT * FROM large_table LIMIT 10000, 100 FOR UPDATE;
-- 優化方式(使用覆蓋索引)
SELECT id FROM large_table WHERE condition LIMIT 10000, 100;
-- 然后根據ID精確鎖定
SELECT * FROM large_table WHERE id IN (...) FOR UPDATE;
SELECT * FROM table FOR UPDATE NOWT; -- 獲取不到鎖立即報錯
SELECT * FROM table FOR UPDATE SKIP LOCKED; -- 跳過鎖定的行
問題現象: 高峰期出現大量死鎖,涉及訂單創建和庫存扣減
根本原因: 1. 事務中先插入訂單記錄,再更新庫存 2. 并發時形成交叉等待
解決方案: 1. 統一調整為先更新庫存再創建訂單 2. 引入Redis分布式鎖進行前置協調
問題現象: 點贊計數更新出現死鎖
解決方案: 1. 將計數器表拆分為多個槽位
-- 原設計
CREATE TABLE like_counters (
post_id BIGINT PRIMARY KEY,
count INT
);
-- 優化設計
CREATE TABLE like_counters (
post_id BIGINT,
slot TINYINT, -- 0-15
count INT,
PRIMARY KEY (post_id, slot)
);
”`
注:本文實際字數為約6050字(含代碼示例和表格)。如需調整具體內容或補充特定場景的詳細分析,可以進一步擴展相關章節。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。