“網站又打不開了!”下午剛睡完午覺不久,就從客戶那聽見這不好的消息,因爲之前也出過兩次同樣的情況,直覺就是數據庫又死鎖阻塞了,但之前幾次都是發佈完程序沒多久,所以前幾次都是通過“估計是發佈時有事務正在執行導致事務死鎖”搪塞過去,但這次可沒發佈過,再用這理由搪塞,估計客戶會板着臉,然後一臉“雖然我不懂技術,但我不是傻子”的表情!
我們可以通過下面的sql
來查看當前有哪些sql
正在被阻塞中:
SELECT DISTINCT r.scheduler_id,
r.status,
r.session_id AS SPID,
r.blocking_session_id AS BlkBy,
sp.program_name AS ProgramName,
COALESCE(sp.LOGINAME, sp.nt_username) AS HostName,
SUBSTRING(LTRIM(q.text), r.statement_start_offset / 2 + 1, ( CASE
WHEN r.statement_end_offset = -1
THEN LEN(CONVERT( NVARCHAR(MAX), q.text)) * 2
ELSE r.statement_end_offset
END - r.statement_start_offset ) / 2) AS SQL_Statment,
r.cpu_time AS [CPU Time(ms)],
DATEDIFF(minute, r.start_time, GETDATE()) AS Duration,
r.start_time,
r.total_elapsed_time AS Total_Execution_Time,
r.reads AS Reads,
r.writes AS Writes,
r.logical_reads,
--q.text, --FULL TEXT
d.name AS DataBaseName
FROM sys.dm_exec_requests AS r
CROSS APPLY sys.dm_exec_sql_text( sql_handle ) AS q
LEFT JOIN sys.databases AS d ON r.database_id = d.database_id
LEFT OUTER JOIN master.dbo.sysprocesses AS sp ON SP.spid = r.session_id
WHERE r.session_id > 50
AND r.session_id <> @@SPID
AND COALESCE(sp.LOGINAME, sp.nt_username) <> ''
ORDER BY r.total_elapsed_time DESC;
執行結果類似下圖:
當然這不是實際生產的執行結果,只是爲了展示而執行的模擬效果,可以看到SPID
和BlkBy
這兩列,SPID
即爲進程ID,BlkBy
爲該進程是被哪個進程鎖阻塞,實際環境中,會有多列數據結果,這時候你就要按查詢結果仔細判斷究竟哪行Blkby
纔是真正最終的阻塞源頭。
查到阻塞源頭進程後,你就可以參考該篇博客內容,通過SQL Server Management Studio
數據庫管理工具的“活動和監聽器”查看對應進程究竟執行了啥sql
(選中進程,然後右鍵,在彈出的菜單中點擊“詳細信息”),然後再按照查到的sql
,反查你的程序,看發生問題的代碼究竟在何處,進而分析究竟是什麼原因導致了死鎖或阻塞。
最後再簡單的展示下用於模擬阻塞的代碼,也許看起來代碼邏輯過於愚蠢(在一個事務中,居然出現了第二甚至第三個數據庫連接,而且還去查詢了上面被修改導致更新鎖的數據表),但這實際上正是我們項目中的代碼對應的示例過程,而且由於項目代碼用的是封裝的有問題的SqlHelper
,DbTransaction
和DbConnection
在異常後並不會被釋放,導致只要符合條件,用戶執行一下操作,整個數據庫就立馬死鎖!
using Dapper;
using System.Data;
using System.Data.SqlClient;
public class LockDbDemo
{
public static void LockDb()
{
var connStr = @"Uid=sa;Pwd=123456;Initial Catalog=Test;Data Source=.;Connect Timeout=900";
using (IDbConnection conn1 = new SqlConnection(connStr))
{
conn1.Open();
using (var trans = conn1.BeginTransaction())
{
try
{
//事務+更新操作 導致更新鎖
conn1.ExecuteScalar("update [LockDemo] set [Name]='ddd' where id=1", transaction: trans);
using (var conn2 = new SqlConnection(connStr))
{
conn2.Open();
//這裏的conn2是阻塞的關鍵
//在不是同一個連接且沒指定使用nolock的情況下,訪問同一張表,導致阻塞
conn2.Query("select * from [LockDemo]");
}
trans.Commit();
}
catch(Exception ex)
{
trans.Rollback();
}
}
}
}
}
貌似忘了表的初始化sql
了
CREATE TABLE [dbo].[LockDemo](
[Id] [bigint] NOT NULL,
[Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_LockDemo] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
INSERT INTO [dbo].[LockDemo]([Id],[Name])
VALUES (1 ,'test');