在 SQL Server 這類多用戶資料庫系統中,鎖機制是確保交易完整性與資料一致性的基石。然而,許多開發人員只在遭遇死鎖或效能瓶頸時才被迫接觸這個領域。事實上,要有效研究並優化鎖機制,系統需同時滿足兩個前提條件:龐大的資料量與高併發的多用戶存取。若缺乏這兩者,資料庫極難觸發死鎖,盲目研究恐事倍功半;反之,若具備條件卻仍沿用預設設定,則極易引發「遺失更新」、「髒讀」、「不可重複讀」等經典併發異常。
鎖是實現併發控制最常用的手段。當事務欲存取資源(如資料表、資料列)前,必須先向系統請求鎖定該資源。取得鎖後,事務即獲得資料控制權,在釋放鎖之前,其他事務無法更新該資料。事務回滾或提交時,系統自動釋放被鎖定資源。簡單說,當一位使用者鎖住某個資料庫物件時,其他使用者便無法存取該物件,以此保護資料完整性。
SQL Server 支援多粒度鎖定,涵蓋 RID(列鎖)、Key(索引鍵鎖)、Page(頁鎖)、Extent(區塊鎖)、Table(資料表鎖)、Database(資料庫鎖)六個層級。粒度越細(如列鎖),併發度越高,但系統維護鎖的開銷越大;粒度越粗(如資料表鎖),開銷越低,但嚴重限制併發。
為平衡效能與併發,SQL Server 引入鎖升級機制:當單一交易持有的鎖數量達到臨界值,系統自動將大量細粒度鎖(列、頁)升級為粗粒度鎖(資料表),以降低系統負荷。值得注意的是,升級門檻與過程完全由系統自動判定,無需人工干預。
SQL Server 定義多種鎖模式以應對不同業務場景:
透過 SET DEADLOCK_PRIORITY { LOW | NORMAL } 控制死鎖發生時的犧牲者選擇。設為 LOW 的會話優先被終止。
SET LOCK_TIMEOUT 設定陳述式等待阻塞資源的最長時間(毫秒)。超時後自動取消並回傳錯誤 1222。例如 SET LOCK_TIMEOUT 1800 設為 1.8 秒。可用 @@LOCK_TIMEOUT 查看目前設定。
使用系統預存程序 sp_indexoption 設定特定索引的鎖定粒度(如禁止頁鎖、列鎖)。
透過 SET TRANSACTION ISOLATION LEVEL 設定隔離等級(Read Uncommitted, Read Committed, Repeatable Read, Serializable),直接影響鎖的持有時間與相容性。
EXEC SP_LOCK 回報目前鎖狀態。sys.dm_tran_locks 提供更詳細的即時鎖資訊(適用新版本)。BEGIN TRAN
UPDATE titles SET title_id = title_id WHERE 1 = 2 -- 永不成立的條件
IF (SELECT AVG(price) FROM titles) > 15
BEGIN
UPDATE titles SET price = price * 1.10 WHERE price < (SELECT AVG(price) FROM titles)
END
COMMIT TRAN
此處 WHERE 1=2 雖不更新任何列,但優化器因找不到有效 SARG(搜尋引數),會強制採用資料表掃描並施加排他資料表鎖 (X Lock)。雖然 WHERE 立即評估為 FALSE 而不執行實際掃描,但該交易已成功取得資料表級排他鎖。此技巧可確保後續邏輯執行期間無其他交易修改資料,實現可重複讀,且避免 HOLDLOCK 可能導致的死鎖風險。代價是增加資料表鎖爭用,需權衡「避免死鎖」與「允許併發存取」的優先序。
@@ERROR 處理邏輯。NOLOCK 或 READ UNCOMMITTED 可避免鎖衝突,適用於可容忍髒資料的報表查詢。Bound Connections 允許多連接共享交易與鎖,消除內部衝突。SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM TableName WITH (ROWLOCK) WHERE ID = 1
SELECT Col1 FROM TableName WITH (TABLOCKX) WHERE 1=1
-- 鎖定期間他人不可操作,COMMIT 或 ROLLBACK 釋放
建立測試資料表 table1、table2 並插入樣本資料。透過兩個交易以相反順序更新兩張表並加入 WAITFOR DELAY,即可穩定重現死鎖:交易 A 鎖 table1 等 table2,交易 B 鎖 table2 等 table1,雙方陷入無限等待。
| 提示 | 說明 |
|---|---|
| HOLDLOCK | 持有共享鎖至交易結束,等同 SERIALIZABLE。 |
| NOLOCK / READUNCOMMITTED | 不加共享/排他鎖,可能讀到髒資料,僅限 SELECT。 |
| PAGLOCK | 指定使用頁鎖(預設可能升級為表鎖)。 |
| READCOMMITTED | 以提交讀隔離等級語義執行掃描(預設行為)。 |
| READPAST | 跳過已鎖定列,不阻塞等待,僅限 READ COMMITTED 下的 SELECT。 |
| REPEATABLEREAD | 設定可重複讀隔離等級。 |
| ROWLOCK | 強制使用列鎖,抑制頁/表鎖升級。 |
| SERIALIZABLE | 以可序列化語義掃描,等同 HOLDLOCK。 |
| TABLOCK | 使用表級共享鎖,語句結束釋放;搭配 HOLDLOCK 持有至交易結束。 |
| TABLOCKX | 表級排他鎖,阻擋所有其他讀寫,至語句/交易結束。 |
| UPDLOCK | 讀取時加更新鎖而非共享鎖,持有至交易結束,允許並發讀但防止更新衝突。 |
應用程式鎖由用戶端代碼生成,非 SQL Server 內部鎖。透過兩個系統預存程序操作:
sp_getapplock:鎖定應用程式資源(需指定資源名、鎖模式、擁有者、逾時)。sp_releaseapplock:釋放應用程式資源鎖。適用於協調分散式應用、外部資源存取等資料庫鎖無法覆蓋的同步場景。