一、事務核心特性 (ACID) 與 MySQL 實現
| 特性 | 含義 | MySQL/InnoDB 實現機制 |
| Atomicity (原子性) | 事務內所有操作要么全成功,要么全失敗回滾 | Undo Log 記錄舊值,回滾時反向恢復;Crash Recovery 通過 Redo Log + Undo Log 保證 |
| Consistency (一致性) | 事務前後數據庫從一個一致性狀態轉移到另一個 | 約束 (PK/FK/UK/Check)、觸發器、應用層邏輯共同保證 |
| Isolation (隔離性) | 併發事務互不干擾,四大隔離級別解決髒讀/不可重複讀/幻讀 | MVCC (多版本並發控制) + 行鎖 (Record Lock/Gap Lock/Next-Key Lock) |
| Durability (持久性) | 事務提交後永久生效,不會因故障丟失 | Redo Log (WAL) 強制刷盤 (fsync) + Doublewrite Buffer + Binary Log |
二、存儲引擎與事務支持
- InnoDB:完全支持 ACID、行級鎖、MVCC、外鍵、崩潰恢復,**生產首選**。
- MyISAM:不支持事務、表級鎖、無崩潰恢復,**禁用於事務場景**。
- Memory/CSV/Archive:不支持事務。
- 檢查表引擎:
SHOW TABLE STATUS LIKE 't'\G 查看 Engine 列。
- 轉換引擎:
ALTER TABLE t ENGINE=InnoDB; (會重建表、鎖表,大表用 pt-online-schema-change)。
三、事務控制語句
| 語句 | 作用 | 備註 |
START TRANSACTION / BEGIN / BEGIN WORK | 顯式開啟事務 | 等價,推薦 START TRANSACTION 標準語法 |
COMMIT / COMMIT WORK | 提交事務,持久化所有修改 | 隱式釋放所有鎖 |
ROLLBACK / ROLLBACK WORK | 回滾事務,撤銷所有修改 | 隱式釋放所有鎖 |
SET autocommit = 0/1 | 關閉/開啟自動提交模式 | 默認 ON (每條語句自動提交),事務場景必須 SET autocommit=0 |
SAVEPOINT name | 設置保存點 | 同一事務內可多個,名稱唯一 |
ROLLBACK TO SAVEPOINT name | 回滾到指定保存點 | 保存點之後的修改撤銷,之前保留,保存點本身保留 |
RELEASE SAVEPOINT name | 釋放保存點 | 釋放後無法再回滾到該點 |
SET TRANSACTION ISOLATION LEVEL ... | 設置隔離級別 (全局/會話/下一事務) | READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE |
四、隔離級別深度解析 (InnoDB 默認 REPEATABLE READ)
| 隔離級別 | 髒讀 | 不可重複讀 | 幻讀 | InnoDB 實現 | 性能/並發 |
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | 無鎖讀,直接讀最新版本 | 最高/最低 |
| READ COMMITTED (RC) | ❌ | ✅ 可能 | ✅ 可能 | 每次 SELECT 重新生成 Read View,只讀已提交版本;行鎖 (Record Lock) | 高/高 |
| REPEATABLE READ (RR) 默認 | ❌ | ❌ | ❌ (通過 Next-Key Lock 解決) | 事務首次 SELECT 生成 Read View,全程可見同一快照;Next-Key Lock (Record+Gap) 防幻讀 | 中/中 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 讀加共享鎖 (S Lock),寫加排他鎖 (X Lock),完全串行化 | 最低/最低 |
關鍵點:
- InnoDB RR 級別**通過 Next-Key Lock 實質上解決了幻讀**,但非標準 SQL 定義的幻讀 (如
INSERT ... SELECT 可能仍見到幻影)。
- RC 級別需設置
binlog_format=ROW 保證主從一致性。
- 查看當前隔離級別:
SELECT @@transaction_isolation; (8.0+) 或 SELECT @@tx_isolation; (5.7)。
五、保存點實戰:部分回滾模式
-- 場景:批量處理訂單,單個訂單失敗不影響已處理的,但需記錄失敗原因
START TRANSACTION;
-- 訂單 1
UPDATE orders SET status='processing' WHERE id=1;
SAVEPOINT sp_order_1;
INSERT INTO order_log (order_id, action) VALUES (1, 'start');
-- 若後續失敗
-- ROLLBACK TO SAVEPOINT sp_order_1; -- 僅撤銷 order_log 插入,orders 更新保留
-- 訂單 2
UPDATE orders SET status='processing' WHERE id=2;
SAVEPOINT sp_order_2;
INSERT INTO order_log (order_id, action) VALUES (2, 'start');
-- 發現庫存不足
ROLLBACK TO SAVEPOINT sp_order_2; -- 撤銷訂單 2 相關操作
INSERT INTO failed_orders (order_id, reason) VALUES (2, 'out_of_stock');
-- 訂單 3 成功
UPDATE orders SET status='processing' WHERE id=3;
INSERT INTO order_log (order_id, action) VALUES (3, 'start');
COMMIT; -- 訂單 1、3 及 failed_orders 記錄持久化
注意:
COMMIT 會提交所有未回滾的修改 (包括保存點之前的)。
ROLLBACK (無保存點) 回滾整個事務,所有保存點失效。
- 保存點不支持嵌套事務語義,只是標記位置。
六、死鎖原理、檢測與解決
6.1 死鎖形成條件 (Coffman 條件)
- 互斥:資源獨佔 (行鎖)
- 請求與保持:持有鎖等待新鎖
- 不剝奪:鎖只能主動釋放
- 循環等待:T1 等 T2 的鎖,T2 等 T1 的鎖
6.2 典型死鎖場景
-- 會話 A -- 會話 B
START TRANSACTION; START TRANSACTION;
UPDATE accounts SET balance=balance-100 WHERE id=1; -- X鎖 id=1
UPDATE accounts SET balance=balance+100 WHERE id=2; -- X鎖 id=2
UPDATE accounts SET balance=balance+100 WHERE id=2; -- 等待 B 釋放 id=2
UPDATE accounts SET balance=balance-100 WHERE id=1; -- 等待 A 釋放 id=1
-- DEADLOCK! -- DEADLOCK!
6.3 InnoDB 死鎖檢測與處理
- Wait-for Graph:InnoDB 內部維護等待圖,定期 (毫秒級) 掃描檢測環路。
- 犧牲者選擇:權重 =
trx_weight = rows_modified + rows_locked,**權重較小的事務被回滾** (釋放鎖,報錯 ERROR 1213 (HY000): Deadlock found when trying to get lock; try restarting transaction)。
- 配置:
innodb_deadlock_detect=ON (默認),關閉後依賴 innodb_lock_wait_timeout (默認 50s) 超時回滾。
6.4 死鎖預防最佳實踐
- 固定訪問順序:所有事務按**相同順序**訪問表/行 (如按主鍵 ID 升序)。
- 縮短事務:少做非必要查詢、業務邏輯前置、提交前不做網絡調用。
- 低隔離級別:RC 級別無 Gap Lock,減少鎖範圍 (需配合 ROW Binlog)。
- 避免大事務:批量操作分批提交,單事務修改行數 < 1000。
- 應用層重試:捕獲 1213 錯誤,指數退避重試 (建議 3 次內)。
- 索引優化:確保 WHERE/UPDATE 列有索引,避免全表掃描升級為表鎖/大量行鎖。
七、事務相關監控與診斷
| 指標/命令 | 用途 |
SHOW ENGINE INNODB STATUS\G | 查看最近死鎖詳情 (LATEST DETECTED DEADLOCK)、鎖等待、事務列表、Undo Log 統計 |
SELECT * FROM information_schema.INNODB_TRX\G | 當前活躍事務:ID、狀態、啟動時間、鎖結構、等待鎖、SQL 文本 |
SELECT * FROM information_schema.INNODB_LOCKS\G | 當前鎖:表、索引、類型 (RECORD/GAP/NEXT-KEY)、模式 (S/X)、事務 ID |
SELECT * FROM information_schema.INNODB_LOCK_WAITS\G | 鎖等待鏈:請求事務、阻塞事務、等待鎖 |
SHOW STATUS LIKE 'Innodb_row_lock%'; | 行鎖統計:等待次數、等待時間、平均等待、最大等待 |
SHOW STATUS LIKE 'Handler_rollback'; | 回滾次數 (含隱式死鎖回滾) |
八、應用開發模式建議
# Python (PyMySQL) 事務模板
import pymysql
from contextlib import contextmanager
@contextmanager
def transaction(conn):
try:
conn.begin()
yield conn
conn.commit()
except Exception as e:
conn.rollback()
raise
# 使用
with transaction(conn) as c:
with c.cursor() as cur:
cur.execute("UPDATE accounts SET balance=balance-%s WHERE id=%s", (100, 1))
cur.execute("UPDATE accounts SET balance=balance+%s WHERE id=%s", (100, 2))
# 無異常自動提交,有異常自動回滾
總結
MySQL 事務是 InnoDB 核心價值所在。掌握 ACID 實現原理、隔離級別選型 (推薦 RR 或 RC+ROW Binlog)、保存點部分回滾、死鎖預防與應用層重試,是開發高併發、強一致性業務的必備技能。記住:**事務越短越好、索引越全越好、順序越固定越好、異常必重試**。