死鎖:兩個以上執行緒互相持有資源並等待對方釋放,形成循環等待。本文解析死鎖成因、提供監控預存程序、列出七大預防原則與五項具體建議,助你從根源降低死鎖發生率。
-- Session 1 -- Session 2
BEGIN TRAN BEGIN TRAN
UPDATE TableA SET ... WHERE id=1 UPDATE TableB SET ... WHERE id=2
-- 持有 TableA X 鎖 -- 持有 TableB X 鎖
UPDATE TableB SET ... WHERE id=2 UPDATE TableA SET ... WHERE id=1
-- 等待 TableB X 鎖 (被 S2 阻塞) -- 等待 TableA X 鎖 (被 S1 阻塞)
-- 死鎖發生,SQL Server 選擇犧牲者
CREATE PROCEDURE sp_who_lock
AS
BEGIN
SET NOCOUNT ON;
DECLARE @spid SMALLINT, @bl SMALLINT, @cnt INT, @i INT = 1;
CREATE TABLE #tmp_lock_who (
id INT IDENTITY(1,1),
spid SMALLINT,
bl SMALLINT
);
INSERT #tmp_lock_who (spid, bl)
SELECT 0, blocked FROM sys.sysprocesses WHERE blocked > 0
AND blocked NOT IN (SELECT spid FROM sys.sysprocesses WHERE blocked > 0)
UNION
SELECT spid, blocked FROM sys.sysprocesses WHERE blocked > 0;
SELECT @cnt = COUNT(*) FROM #tmp_lock_who;
IF @cnt = 0
BEGIN
PRINT '目前無阻塞與死鎖資訊';
RETURN;
END
WHILE @i <= @cnt
BEGIN
SELECT @spid = spid, @bl = bl FROM #tmp_lock_who WHERE id = @i;
IF @spid = 0
PRINT '引發死鎖的頭部阻塞程序: SPID ' + CAST(@bl AS VARCHAR);
ELSE
PRINT 'SPID ' + CAST(@spid AS VARCHAR) + ' 被 SPID ' + CAST(@bl AS VARCHAR) + ' 阻塞';
DBCC INPUTBUFFER(@bl); -- 顯示阻塞者最後一批 T-SQL
SET @i += 1;
END
DROP TABLE #tmp_lock_who;
END
GO
用法:EXEC sp_who_lock; 即可輸出阻塞鏈與問題 SQL。
WAITFOR、使用者輸入等待,縮短鎖持有時間。READ COMMITTED 或 READ UNCOMMITTED (NOLOCK),減少共享鎖持有。sp_getbindtoken/sp_bindsession 讓多連線共用鎖空間,避免自我阻塞。ALTER TABLE ... SET (LOCK_ESCALATION = AUTO/DISABLE) (SQL 2008+)。UPDATE/DELETE (如 WHERE 1=1),分批處理 (每批 1000-5000 列)。UPDATE/DELETE 的 WHERE 條件走索引求 (Index Seek)。COMMIT/ROLLBACK,避免隱含交易計數器錯亂。WITH (NOLOCK) 或設定 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED。xml_deadlock_report:輕量、完整圖形化死鎖圖。系統化套用上述原則與監控機制,可將死鎖發生率壓至極低,保障高併發環境穩定運行。