阻塞 是資料庫高併發環境常見問題。當一個 Session 持有鎖定資源,導致其他 Session 無法存取同一資源時即發生阻塞。本文以實戰範例示範如何建立測試環境、查詢阻塞鏈、三種解除方案,並說明根本預防之道。
CREATE TABLE Employee (
Empid INT NOT NULL, Name NCHAR(10) NULL, City NCHAR(10) NULL
);
INSERT INTO Employee VALUES
(1245,'George','Jax'), (1045,'Peter','Anadale'), (1157,'John','Dallas'),
(1175,'Pete','Topeka'), (875,'Petron','Vienna'), (2311,'Kohli','Mumbai'),
(1547,'Peter','Kansas'), (3514,'Abian','KHI'), (4251,'Ghani','Alexandria'),
(957,'Ahmed','Vienna'), (1084,'Bhanu','Manderin'), (2954,'Ganeshan','Mcclean');
BEGIN TRAN INSERT INTO Employee VALUES (1245,'George','Jax'); -- 故意不執行 COMMIT/ROLLBACK,交易保持開啟

UPDATE Employee SET Name = 'SHERAZ' WHERE Empid = 1245; -- 此處會卡住等待 Session A 釋放鎖

SELECT DISTINCT
DB_NAME(dtl.resource_database_id) AS database_name,
des.session_id, des.host_name, des.login_time, des.login_name,
des.reads, des.writes
FROM sys.dm_exec_sessions des
LEFT OUTER JOIN sys.dm_tran_locks dtl ON des.session_id = dtl.request_session_id
INNER JOIN sys.databases sd ON dtl.resource_database_id = sd.database_id
WHERE dtl.resource_type <> 'DATABASE'
ORDER BY database_name;

SELECT
der.session_id,
der.blocking_session_id,
dest.text AS sql_text
FROM sys.dm_exec_requests der
CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) dest;

結果顯示 blocking_session_id = 54 阻塞 session_id = 58。
KILL 54; -- 強制終止 Session 54,釋放鎖定
⚠️ 會導致該 Session 交易回滾,可能造成資料不一致,需確認業務影響後再執行。
SET LOCK_TIMEOUT 1000; -- 超過 1000ms 自動終止等待,回傳錯誤 1222
優點:避免無限期等待;缺點:需應用程式捕捉錯誤並重試。
在 Session A 執行 COMMIT TRAN 或 ROLLBACK TRAN,釋放鎖定,Session B 即可繼續。
/**** 建立測試表 ****/
CREATE TABLE Employee (Empid INT NOT NULL, Name NCHAR(10) NULL, City NCHAR(10) NULL);
INSERT INTO Employee VALUES
(1245,'George','Jax'), (1045,'Peter','Anadale'), (1157,'John','Dallas'),
(1175,'Pete','Topeka'), (875,'Petron','Vienna'), (2311,'Kohli','Mumbai'),
(1547,'Peter','Kansas'), (3514,'Abian','KHI'), (4251,'Ghani','Alexandria'),
(957,'Ahmed','Vienna'), (1084,'Bhanu','Manderin'), (2954,'Ganeshan','Mcclean');
/**** Session A:開啟交易插入不提交 ****/
BEGIN TRAN
INSERT INTO Employee VALUES (1245,'George','Jax');
/**** 查詢目前 Session ****/
SELECT DISTINCT DB_NAME(dtl.resource_database_id) AS database_name, des.session_id,
des.host_name, des.login_time, des.login_name, des.reads, des.writes
FROM sys.dm_exec_sessions des
LEFT OUTER JOIN sys.dm_tran_locks dtl ON des.session_id = dtl.request_session_id
INNER JOIN sys.databases sd ON dtl.resource_database_id = sd.database_id
WHERE dtl.resource_type <> 'DATABASE' ORDER BY database_name;
/**** Session B:更新同一資料列(將阻塞) ****/
UPDATE Employee SET Name = 'SHERAZ' WHERE Empid = 1245;
/**** 查詢阻塞鏈 ****/
SELECT der.session_id, der.blocking_session_id, dest.text
FROM sys.dm_exec_requests der
CROSS APPLY sys.dm_exec_sql_text(der.sql_handle) dest;
/**** 解除:KILL 阻塞者 ****/
KILL 54;
READ COMMITTED SNAPSHOT 減少阻塞。總結:KILL 只是止痛藥,優化交易設計與索引才是治本良方。