記錄一次SqlServer數據庫阻塞死鎖問題排查過程

“網站又打不開了!”下午剛睡完午覺不久,就從客戶那聽見這不好的消息,因爲之前也出過兩次同樣的情況,直覺就是數據庫又死鎖阻塞了,但之前幾次都是發佈完程序沒多久,所以前幾次都是通過“估計是發佈時有事務正在執行導致事務死鎖”搪塞過去,但這次可沒發佈過,再用這理由搪塞,估計客戶會板着臉,然後一臉“雖然我不懂技術,但我不是傻子”的表情!

我們可以通過下面的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;

執行結果類似下圖:
在這裏插入圖片描述
當然這不是實際生產的執行結果,只是爲了展示而執行的模擬效果,可以看到SPIDBlkBy這兩列,SPID即爲進程ID,BlkBy爲該進程是被哪個進程鎖阻塞,實際環境中,會有多列數據結果,這時候你就要按查詢結果仔細判斷究竟哪行Blkby纔是真正最終的阻塞源頭。

查到阻塞源頭進程後,你就可以參考該篇博客內容,通過SQL Server Management Studio數據庫管理工具的“活動和監聽器”查看對應進程究竟執行了啥sql(選中進程,然後右鍵,在彈出的菜單中點擊“詳細信息”),然後再按照查到的sql,反查你的程序,看發生問題的代碼究竟在何處,進而分析究竟是什麼原因導致了死鎖或阻塞。
在這裏插入圖片描述
最後再簡單的展示下用於模擬阻塞的代碼,也許看起來代碼邏輯過於愚蠢(在一個事務中,居然出現了第二甚至第三個數據庫連接,而且還去查詢了上面被修改導致更新鎖的數據表),但這實際上正是我們項目中的代碼對應的示例過程,而且由於項目代碼用的是封裝的有問題的SqlHelperDbTransactionDbConnection在異常後並不會被釋放,導致只要符合條件,用戶執行一下操作,整個數據庫就立馬死鎖!

    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');

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章