虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务:
- 回滚,而回滚会取消事务执行的所有工作。
- 由于死锁时回滚而由应用程序重新提交。
下列方法有助于最大限度地降低死锁:
- 按同一顺序访问对象。
- 避免事务中的用户交互。
- 保持事务简短并在一个批处理中。
- 使用低隔离级别。
- 使用绑定连接。
按同一顺序访问对象
如果所有并发事务按同一顺序访问对象,则发生死锁的可能性会降低。例如,如果两个并发事务获得 Supplier表上的锁,然后获得 Part表上的锁,则在其中一个事务完成之前,另一个事务被阻塞在 Supplier表上。第一个事务提交或回滚后,第二个事务继续进行。不发生死锁。将存储过程用于所有的数据修改可以标准化访问对象的顺序。
避免事务中的用户交互
避
免编写包含用户交互的事务,因为运行没有用户交互的批处理的速度要远远快于用户手动响应查询的速度,例如答复应用程序请求参数的提示。例如,如果事务正在
等待用户输入,而用户去吃午餐了或者甚至回家过周末了,则用户将此事务挂起使之不能完成。这样将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或
回滚时才会释放。即使不出现死锁的情况,访问同一资源的其它事务也会被阻塞,等待该事务完成。
保持事务简短并在一个批处理中
在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长,其持有排它锁或更新锁的时间也就越长,从而堵塞了其它活动并可能导致死锁。
保持事务在一个批处理中,可以最小化事务的网络通信往返量,减少完成事务可能的延迟并释放锁。
使用低隔离级别
确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如提交读)而不使用较高的隔离级别(例如可串行读)可以缩短持有共享锁的时间,从而降低了锁定争夺。
使用绑定连接
使用绑定连接使同一应用程序所打开的两个或多个连接可以相互合作。次级连接所获得的任何锁可以象由主连接获得的锁那样持有,反之亦然,因此不会相互阻塞
检测死锁
如果发生死锁了,我们怎么去检测具体发生死锁的是哪条SQL语句或存储过程?
这时我们可以使用以下存储过程来检测,就可以查出引起死锁的进程和SQL语句。SQL Server自带的系统存储过程sp_who和sp_lock也可以用来查找阻塞和死锁, 但没有这里介绍的方法好用。
use master
go
createprocedure sp_who_lock
as
begin
declare@spidint,@blint,
@intTransactionCountOnEntryint,
@intRowcountint,
@intCountPropertiesint,
@intCounterint
createtable #tmp_lock_who (
id intidentity(1,1),
spid smallint,
bl smallint)
IF@@ERROR<>0RETURN@@ERROR
insertinto #tmp_lock_who(spid,bl) select0 ,blocked
from (select*from sysprocesses where blocked>0 ) a
wherenotexists(select*from (select*from sysprocesses where blocked>0 ) b
where a.blocked=spid)
unionselect spid,blocked from sysprocesses where blocked>0
IF@@ERROR<>0RETURN@@ERROR
-- 找到临时表的记录数
select@intCountProperties=Count(*),@intCounter=1
from #tmp_lock_who
IF@@ERROR<>0RETURN@@ERROR
if@intCountProperties=0
select'现在没有阻塞和死锁信息'as message
-- 循环开始
while@intCounter<=@intCountProperties
begin
-- 取第一条记录
select@spid= spid,@bl= bl
from #tmp_lock_who where Id =@intCounter
begin
if@spid=0
select'引起数据库死锁的是: '+CAST(@blASVARCHAR(10)) +'进程号,其执行的SQL语法如下'
else
select'进程号SPID:'+CAST(@spidASVARCHAR(10))+'被'+'进程号SPID:'+CAST(@blASVARCHAR(10)) +'阻塞,其当前进程执行的SQL语法如下'
DBCC INPUTBUFFER (@bl )
end
-- 循环指针下移
set@intCounter=@intCounter+1
end
droptable #tmp_lock_who
return0
end
杀死锁和进程
如何去手动的杀死进程和锁?最简单的办法,重新启动服务。但是这里要介绍一个存储过程,通过显式的调用,可以杀死进程和锁。
use master
go
ifexists (select*from dbo.sysobjects where id =object_id(N'[dbo].[p_killspid]') andOBJECTPROPERTY(id, N'IsProcedure') =1)
dropprocedure[dbo].[p_killspid]
GO
createproc p_killspid
@dbnamevarchar(200) --要关闭进程的数据库名
as
declare@sqlnvarchar(500)
declare@spidnvarchar(20)
declare #tb cursorfor
select spid=cast(spid asvarchar(20)) from master..sysprocesses where dbid=db_id(@dbname)
open #tb
fetchnextfrom #tb into@spid
while@@fetch_status=0
begin
exec('kill '+@spid)
fetchnextfrom #tb into@spid
end
close #tb
deallocate #tb
go
--用法
exec p_killspid 'newdbpy'