悲觀並發控制假設資料衝突機率高,因此在整個資料處理過程中預先鎖定目標資料,阻擋其他交易修改,確保資料一致性。在 SQL Server 中,核心實作依賴交易 + 鎖機制,唯有資料庫層級提供的鎖才能真正保證跨系統、跨連線的排他性。
| 鎖提示 | 行為 | 適用場景 |
|---|---|---|
HOLDLOCK (相當於 SERIALIZABLE) | 共享鎖持有至交易結束,允許讀取,封鎖更新/刪除 | 需確保讀取資料在交易期間不被修改(可重複讀 + 幻讀防護) |
TABLOCKX | 排他資料表鎖,封鎖所有讀寫操作 | 大量批次更新、結構變更、需獨佔資料表時 |
ROWLOCK + REPEATABLE READ | 強制行鎖 + 可重複讀隔離級別 | 精細粒度控制,減少鎖升級機率 |
預設隔離級別為 READ COMMITTED。若需行級鎖配合可重複讀語意,可在交易開啟前執行:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM Users WITH (ROWLOCK) WHERE Id = 2;
private void btnUpdate_Click(object sender, EventArgs e) {
using (var conn = new SqlConnection(connStr)) {
conn.Open();
using (var tran = conn.BeginTransaction()) {
try {
// 先查詢加共享鎖,再更新,鎖持有至 Commit/Rollback
var cmd = new SqlCommand(
"SELECT * FROM Users WITH (HOLDLOCK) WHERE Id = 2; " +
"UPDATE Users SET Name = N'C羅', Address = N'basi' WHERE Id = 2",
conn, tran);
cmd.ExecuteNonQuery();
Thread.Sleep(20000); // 模擬長時間業務處理
tran.Commit();
MessageBox.Show("A 完成");
} catch {
tran.Rollback();
}
}
}
}
private void btnUpdate_Click(object sender, EventArgs e) {
using (var conn = new SqlConnection(connStr)) {
conn.Open();
// 無 WITH (HOLDLOCK),但會被 A 的鎖封鎖
var cmd = new SqlCommand(
"UPDATE Users SET Name = N'大羅', Address = N'basi' WHERE Id = 2", conn);
cmd.ExecuteNonQuery(); // 此處阻塞等待 A 釋放鎖
MessageBox.Show("B 完成");
}
}
SELECT ... HOLDLOCK 取得 Id=2 記錄的共享鎖(S 鎖),後續 UPDATE 轉為更新鎖 (U) 再轉排他鎖 (X)。UPDATE,請求 X 鎖,因與 A 的 X 鎖衝突,進入等待隊列。Thread.Sleep(20000) 模擬業務邏輯,20 秒後 Commit 釋放所有鎖。在日終結算處理中,必須釐清某個 Cut-off 時間點的資料快照,結算過程(可能持續數秒至數小時)絕不能允許資料變動。此時在查詢快照資料時加上 HOLDLOCK 或使用 SERIALIZABLE 隔離級別,即可確保整個結算窗口期間資料凍結,防止髒寫與不可重複讀。
ROWLOCK) 減少爭用;避免不必要的資料表鎖 (TABLOCKX) 影響並發度。READ COMMITTED SNAPSHOT (RCSI) 降低讀寫衝突(屬樂觀並發範疇)。Catch 區塊呼叫 Rollback(),並在 Finally 確保連線關閉。悲觀鎖透過資料庫原生鎖機制,在關鍵業務流程中提供強一致性保證。正確選擇鎖提示、隔離級別、控制交易範圍,是平衡資料正確性與系統並發效能的關鍵。實務上應視業務衝突頻率、容忍延遲程度,在悲觀與樂觀並發策略間做權衡選擇。