记录一次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;
执行结果类似下图:
当然这不是实际生产的执行结果,只是为了展示而执行的模拟效果,可以看到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');
2019-12-20补充 上述内容和数据库事务隔离级别有关,默认SqlServer
的隔离级别为Read committed
,如果更改默认隔离级别为最低的Read uncommitted
,虽未测试,但应该不会产生死锁,当然并不是没有问题,而是产生了新的问题脏读
,该部分内容后续有时间测试看下上述内容是否正确。
以上是关于记录一次SqlServer数据库阻塞死锁问题排查过程的主要内容,如果未能解决你的问题,请参考以下文章