“网站又打不开了!”下午刚睡完午觉不久,就从客户那听见这不好的消息,因为之前也出过两次同样的情况,直觉就是数据库又死锁阻塞了,但之前几次都是发布完程序没多久,所以前几次都是通过“估计是发布时有事务正在执行导致事务死锁”搪塞过去,但这次可没发布过,再用这理由搪塞,估计客户会板着脸,然后一脸“虽然我不懂技术,但我不是傻子”的表情!
我们可以通过下面的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,虽未测试,但应该不会产生死锁,当然并不是没有问题,而是产生了新的问题脏读,该部分内容后续有时间测试看下上述内容是否正确。

本文详细介绍如何使用SQL查询定位SQL Server数据库中的死锁源,包括执行特定SQL语句检查阻塞进程,以及通过SQL Server Management Studio跟踪具体SQL语句。同时提供了一个模拟死锁的代码示例。
326

被折叠的 条评论
为什么被折叠?



