業務場景:歸檔數據到歷史表並刪除主表記錄。隔離級別 RR (REPEATABLE READ),引擎 InnoDB。
-- 表結構 CREATE TABLE test1 (id INT PRIMARY KEY, name VARCHAR(10)); CREATE TABLE test2 (id INT PRIMARY KEY, name VARCHAR(10)); INSERT INTO test1 VALUES (1, 'hello'); -- 事務 1 (歸檔+刪除) BEGIN; INSERT INTO test2 SELECT * FROM test1 WHERE id = 1; -- 步驟 A DELETE FROM test1 WHERE id = 1; -- 步驟 B -- 事務 2 (並發歸檔) BEGIN; INSERT INTO test2 SELECT * FROM test1 WHERE id = 1; -- 步驟 C
SELECT ... FROM test1 WHERE id=1 獲取主鍵 id=1 的 共享鎖 (S Lock)DELETE 需要將持有的 S Lock 升級為排他鎖 (X Lock)MySQL 官方文檔描述的經典死鎖為「X Lock 等待 X Lock」,本案例為「S Lock 升級 X Lock 等待 S Lock」,本質相同。
| 鎖類型 | 開銷 | 並發度 | 死鎖 | 適用場景 |
|---|---|---|---|---|
| 表鎖 | 小/快 | 最低 | 無 | 以讀為主、少量索引更新 |
| 行鎖 | 大/慢 | 最高 | 有 | 大量索引條件並發更新 |
| 頁鎖 | 中等 | 中等 | 有 | 折中方案 |
*** (1) TRANSACTION: ACTIVE 0 sec UPDATE tab_test SET state=1064,time=now() WHERE state=1061 AND time < date_sub(now(), INTERVAL 30 minute) -- 任務1:走 idx_1(state,time) 索引,先鎖二級索引,再鎖主鍵 *** (2) TRANSACTION: ACTIVE 0 sec UPDATE tab_test SET state=1067,time=now() WHERE id in (9921180) -- 任務2:走主鍵索引,鎖主鍵,更新 state 需鎖二級索引 *** DEADLOCK: 任務1持有idx_1等主鍵,任務2持有主鍵等idx_1
-- 步驟 1:只查主鍵,輕量快速 SELECT id FROM tab_test WHERE state=1061 AND time < date_sub(now(), INTERVAL 30 minute); -- 步驟 2:按主鍵批量更新,走主鍵索引,加鎖順序固定 UPDATE tab_test SET state=1064,time=now() WHERE id IN (...);
$maxRetry = 3;
for ($i=0; $i<$maxRetry; $i++) {
try {
$pdo->beginTransaction();
// 業務邏輯
$pdo->commit();
break;
} catch (PDOException $e) {
if ($e->getCode() == 'HY000' && strpos($e->getMessage(), 'Deadlock') !== false) {
$pdo->rollBack();
usleep(100000 * ($i+1)); // 指數退避
continue;
}
throw $e;
}
}
SHOW ENGINE INNODB STATUS:即時查看最近死鎖詳情innodb_print_all_deadlocks=ON:錯誤日誌記錄所有死鎖 (5.6.2+)pt-deadlock-logger:Percona Toolkit 死鎖收集分析工具死鎖本質是 多事務以不同順序請求同一組資源鎖。核心預防原則: