记录一次SqlServer数据库阻塞死锁问题排查过程

Posted 娃都会打酱油了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录一次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');

2019-12-20补充 上述内容和数据库事务隔离级别有关,默认SqlServer的隔离级别为Read committed,如果更改默认隔离级别为最低的Read uncommitted,虽未测试,但应该不会产生死锁,当然并不是没有问题,而是产生了新的问题脏读,该部分内容后续有时间测试看下上述内容是否正确。

以上是关于记录一次SqlServer数据库阻塞死锁问题排查过程的主要内容,如果未能解决你的问题,请参考以下文章

记录一次Mysql死锁排查过程

记录一次Mysql死锁排查过程

记录一次MySQL死锁排查过程

记录一次@Cacheable导致死锁问题排查过程

记录一次@Cacheable导致死锁问题排查过程

记录一次@Cacheable导致死锁问题排查过程