資料庫交易是確保資料一致性的核心機制。本文從 ACID 理論出發,深入解析 ADO.NET 中 SqlTransaction 的操作模式、隔離等級、儲存點、巢狀交易限制與效能考量,提供完整 C# 實戰範例。
交易將一組相關資料庫操作視為單一邏輯工作單元:要麼全部成功,要麼全部失敗。典型情境:網購付款流程需同時扣庫存、記付款、建訂單、更新會員資訊,任一步驟失敗需全部復原。
| 特性 | 說明 | ADO.NET 對應機制 |
|---|---|---|
| 原子性 Atomicity | 所有操作視為不可分割整體,全做或全不做 | SqlTransaction.Commit() / Rollback() |
| 一致性 Consistency | 交易完成前後資料庫皆滿足所有完整性條件約束 | 資料庫引擎強制執行 (FK, CK, Trigger 等) |
| 隔離性 Isolation | 並行交易互不干擾,避免髒讀、不可重複讀、幻讀 | IsolationLevel 列舉設定 |
| 持久性 Durability | 交易提交後異動永久保存,不因系統故障遺失 | 資料庫 WAL (Write-Ahead Logging) 機制 |
SqlConnection.BeginTransaction():啟動交易,回傳 SqlTransaction 物件。SqlCommand.Transaction:將命令關聯至交易。SqlTransaction.Commit():提交交易。SqlTransaction.Rollback():回滾交易 (可指定儲存點)。SqlTransaction.Save(string savePoint):建立儲存點。
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
SqlTransaction tran = conn.BeginTransaction(); // 或指定 IsolationLevel
SqlCommand cmd = conn.CreateCommand();
cmd.Transaction = tran;
try
{
cmd.CommandText = "UPDATE Accounts SET Balance -= 100 WHERE Id = 1";
cmd.ExecuteNonQuery();
cmd.CommandText = "UPDATE Accounts SET Balance += 100 WHERE Id = 2";
cmd.ExecuteNonQuery();
tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
// 記錄錯誤、拋出或處理
throw;
}
}
以下 DDL/管理指令會導致例外:
CREATE/ALTER/DROP DATABASE, BACKUP/RESTORE, DISK INIT, RECONFIGURE, UPDATE STATISTICS, LOAD DATABASE 等。
原則:交易僅適用於 DML (SELECT/INSERT/UPDATE/DELETE) 及部分 DDL (如 CREATE TABLE 在某些版本受限)。
tran.Save("BeforeRiskyUpdate");
cmd.CommandText = "UPDATE HighRiskTable SET ...";
try { cmd.ExecuteNonQuery(); }
catch { tran.Rollback("BeforeRiskyUpdate"); /* 僅回滾到此點,前面更新保留 */ }
適用情境:驗證成本極高、失敗機率極低的作業,避免事前檢查的效能損耗。
| IsolationLevel | 髒讀 | 不可重複讀 | 幻讀 | 並發度 | 適用場景 |
|---|---|---|---|---|---|
| ReadUncommitted | ✅ | ✅ | ✅ | 最高 | 近似報表、可容忍髒資料 |
| ReadCommitted (預設) | ❌ | ✅ | ✅ | 高 | 大多數 OLTP 系統 |
| RepeatableRead | ❌ | ❌ | ✅ | 中 | 需穩定讀取同一資料集 |
| Serializable | ❌ | ❌ | ❌ | 最低 | 嚴格財務對帳、庫存扣款 |
| Snapshot | ❌ | ❌ | ❌ | 高 (行版本) | 高並發且需一致性快照,需資料庫啟用 ALLOW_SNAPSHOT_ISOLATION |
設定方式:conn.BeginTransaction(IsolationLevel.Serializable)。注意:交易執行中途變更隔離等級立即生效,後續指令採用新等級。
跨多個資料庫/資源管理員需使用 TransactionScope (需 MSDTC 服務):
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection conn1 = new SqlConnection(connStr1))
using (SqlConnection conn2 = new SqlConnection(connStr2))
{
conn1.Open(); conn2.Open();
// 多資料庫操作
scope.Complete(); // 未呼叫則自動 Rollback
}
}
using 區塊確保 Connection/Transaction/Command 正確 Dispose。SqlException (Number=1205) 實施重試邏輯 (指數退避)。await conn.BeginTransactionAsync()、await tran.CommitAsync()。| 錯誤現象 | 可能原因 | 解決方向 |
|---|---|---|
| SqlTransaction 建構函式無參數 | 嘗試 new SqlTransaction() | 必須用 conn.BeginTransaction() 取得 |
| 交易未提交資料卻可見 | 隔離等級 ReadUncommitted 或未提交讀 | 調整隔離等級或確認交易已 Commit |
| Timeout expired | 鎖定等待過久、死鎖、索引缺失 | 優化查詢、縮短交易、檢查索引、設定 CommandTimeout |
| 分散式交易失敗 | MSDTC 未啟動、防火牆阻擋、網路不通 | 服務啟動、防火牆開放 135/動態埠、測試 DTCPing |