事務是資料庫區別於文件系統的核心特性。MySQL 中僅 **InnoDB** 和 **BDB** (已棄用) 支援事務。本文以 InnoDB 為例,結合「銀行轉賬」、「網上搶書」經典案例,深度講解 ACID 四大特性、隔離級別、鎖機制與 PHP 實戰代碼。
| 特性 | 含義 | 轉賬場景體現 |
|---|---|---|
| 原子性 | 事務不可分割,全做或全不做 | 扣款與入賬必須同時成功/失敗 |
| 一致性 | 事務前後資料庫滿足所有約束 | 總金額守恆、賬戶餘額非負 |
| 隔離性 | 併發事務互不干擾,可序列化 | A 轉賬未提交時,B 查詢見不到中間狀態 |
| 持久性 | 提交後永久保存,抗故障 | 系統崩潰重啟後轉賬記錄不丟失 |
表 book(id, name, price, number),庫存 number=1。用戶甲乙近同時下單。
-- 甲: SELECT number=1 → UPDATE number=0
-- 乙: SELECT number=1 (甲未提交) → UPDATE number=-1 -- 超賣!
START TRANSACTION;
SELECT number FROM book WHERE id=123 FOR UPDATE; -- 加排他鎖 (X Lock)
-- 業務邏輯判斷 number > 0
UPDATE book SET number = number - 1 WHERE id=123;
COMMIT;
FOR UPDATE 在讀取時加 **排他鎖 (X Lock)**,阻塞其他事務的 FOR UPDATE / LOCK IN SHARE MODE / 寫操作。SELECT ... FOR UPDATE 會**阻塞等待**,直到甲 COMMIT 釋放鎖,乙再讀到 number=0 放棄下單。SELECT (無鎖) 仍可讀到舊值 (MVCC 快照讀),不受保護。| 隔離級別 | 髒讀 | 不可重複讀 | 幻讀 | 鎖策略 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | 無鎖 |
| READ COMMITTED | ❌ | ✅ | ✅ | 行鎖 (RC) |
| REPEATABLE READ | ❌ | ❌ | ❌ (Next-Key Lock) | 行鎖 + 間隙鎖 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 表鎖/範圍鎖 |
MySQL 8.0 可通過 SET SESSION TRANSACTION ISOLATION LEVEL ... 動態調整。
<?php
$mysqli = new mysqli("localhost", "root", "123456", "shop", 3306);
if ($mysqli->connect_errno) die("連接失敗: " . $mysqli->connect_error);
$mysqli->begin_transaction(); // 或 $mysqli->query("START TRANSACTION");
try {
$sql1 = "INSERT INTO main (num) VALUES (123)";
$sql2 = "INSERT INTO main (num1) VALUES (456)"; // 故意寫錯欄位名模擬失敗
if (!$mysqli->query($sql1)) throw new Exception("SQL1 失敗: " . $mysqli->error);
if (!$mysqli->query($sql2)) throw new Exception("SQL2 失敗: " . $mysqli->error);
$mysqli->commit();
echo "提交成功";
} catch (Exception $e) {
$mysqli->rollback();
echo "回滾: " . $e->getMessage();
}
$mysqli->close();
?>
SHOW CREATE TABLE t 確認 ENGINE=InnoDB。SET autocommit=0 或顯式 BEGIN 開啟事務。CREATE/DROP/ALTER TABLE 會隱式提交當前事務,勿混用。結語: 事務是資料一致性的基石。**InnoDB + 顯式 BEGIN/COMMIT/ROLLBACK + SELECT FOR UPDATE 行鎖** 是處理併發寫入的標準組合拳。