SQL Server能够自动检测到死锁,死锁检测是由一个称为锁监视器的线程单独执行的。SQL Server每5S钟执行一次检测,检测到死锁后,SQL Server通过选择其中一个线程作为deadlock victim来解除死锁。SQL Server终止deadlock victim线程当前执行的处理,回滚其事务,并将1205 错误返回到应用程序。默认情况下,SQL Server选择运行回滚开销最小的事务的线程作为deadlock victim。
SQL Server对阻塞和死锁的解决方案不同,对于死锁,SQL Server负责检测和解除,但是对于阻塞,SQL Server不会自动检测和解除。阻塞是单向的,如果两个事务互相阻塞,则阻塞变成死锁。当一个事务A申请一个被事务B锁定的资源上的封锁时,发出封锁请求的事务A会一直等待,直到该锁被事务B释放,事务A申请到封锁为止。此时,事务B可能正在进行大量的修改操作,不能释放封锁。如果没有设置Lock_timeout,那么事务A会一直等待,直到事务B处理完成,释放封锁,事务A申请到封锁,阻塞才会自动解除。阻塞状态往往会持续很长时间,SQL Server也不会做出干预,而对于死锁,SQL Server内置死锁的检测方案,至少5S会消除一个Deadlock,对性能的影响往往没有阻塞严重。
如何检测阻塞?通过查询视图Master.sys.sysprocesses,筛选条件是Blocked>0,并且Waittime 很大,“很大”是一个需要斟酌的值。当检测到阻塞之后,可以单纯的将阻塞的线程Kill,保障被阻塞线程的运行,这是一种思路,虽然不一定是最好的。
--代码没有进行测试,后续完善 if object_id(‘tempdb..#tmpblocked‘) is not null drop table #tmpblocked create table #tmpblocked ( id int not null identity(1,1), blocked int ) --get blocked thread spid ;with cte as ( select spid,blocked from master.sys.sysprocesses with(nolock) where blocked>0 ) insert into #tmpblocked(blocked) select a.blocked from cte a with(nolock) inner join cte b with(nolock) on a.blocked<>b.spid declare @i int declare @maxid int declare @blockid int declare @sql Nvarchar(max) select @maxid=max(id),@i=1 from #tmpblocked while @i<=@maxid begin select @blockid=blocked from #tmpblocked where id=@i set @sql=N‘kill ‘+ cast(@blockid as Nvarchar) exec sys.sp_executesql @sql set @i=@i+1 end
单纯的Kill阻塞的线程不一定是最好的。在Kill阻塞的线程时,SQL Server终止该线程当前执行的处理,回滚其事务。如果被kill的线程正在做大批量数据的更新操作,其事务的回滚将会持续很长世间,而被阻塞的事务必须等待其回滚完成,才能申请封锁,对性能的影响比较严重。
如何有效的解除阻塞?需要捕捉阻塞的现场信息,分析阻塞产生的原因,有针对性的解除阻塞。