sqlserver 死锁总结

来源:互联网 发布:bim软件分类 编辑:程序博客网 时间:2024/06/05 06:02
虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务: 
回滚,而回滚会取消事务执行的所有工作。 
由于死锁时回滚而由应用程序重新提交。 

下列方法有助于最大限度地降低死锁: 
按同一顺序访问对象。 
避免事务中的用户交互。 
保持事务简短并在一个批处理中。 
使用低隔离级别。 
使用绑定连接。 




按同一顺序访问对象:
如果所有并发事务按同一顺序访问对象,则发生死锁的可能性会降低。例如,如果两个并发事务获得 Supplier 表上的锁,然后获得 Part 表上的锁,则在其中一个事务完成之前,另一个事务被阻塞在 Supplier 表上。第一个事务提交或回滚后,第二个事务继续进行。不发生死锁。将存储过程用于所有的数据修改可以标准化访问对象的顺序。

避免事务中的用户交互:
避免编写包含用户交互的事务,因为运行没有用户交互的批处理的速度要远远快于用户手动响应查询的速度,例如答复应用程序请求参数的提示。例如,如果事务正在等待用户输入,而用户去吃午餐了或者甚至回家过周末了,则用户将此事务挂起使之不能完成。这样将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或回滚时才会释放。即使不出现死锁的情况,访问同一资源的其它事务也会被阻塞,等待该事务完成。

保持事务简短并在一个批处理中:
在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长,其持有排它锁或更新锁的时间也就越长,从而堵塞了其它活动并可能导致死锁。
保持事务在一个批处理中,可以最小化事务的网络通信往返量,减少完成事务可能的延迟并释放锁。

使用低隔离级别:
确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如提交读)而不使用较高的隔离级别(例如可串行读)可以缩短持有共享锁的时间,从而降低了锁定争夺。

使用绑定连接:
使用绑定连接使同一应用程序所打开的两个或多个连接可以相互合作。次级连接所获得的任何锁可以象由主连接获得的锁那样持有,反之亦然,因此不会相互阻塞


检测死锁
如果发生死锁了,我们怎么去检测具体发生死锁的是哪条SQL语句或存储过程?

这时我们可以使用以下存储过程来检测,就可以查出引起死锁的进程和SQL语句。SQL Server自带的系统存储过程sp_who和sp_lock也可以用来查找阻塞和死锁, 但没有这里介绍的方法好用。

[sql] view plaincopyprint?
  1. use master  
  2. go  
  3. create procedure sp_who_lock  
  4. as  
  5. begin  
  6. declare @spid int,@bl int,  
  7.  @intTransactionCountOnEntry  int,  
  8.         @intRowcount    int,  
  9.         @intCountProperties   int,  
  10.         @intCounter    int  
  11.   
  12.   
  13.  create table #tmp_lock_who (  
  14.  id int identity(1,1),  
  15.  spid smallint,  
  16.  bl smallint)  
  17.    
  18.  IF @@ERROR<>0 RETURN @@ERROR  
  19.    
  20.  insert into #tmp_lock_who(spid,bl) select  0 ,blocked  
  21.    from (select * from sysprocesses where  blocked>0 ) a   
  22.    where not exists(select * from (select * from sysprocesses where  blocked>0 ) b   
  23.    where a.blocked=spid)  
  24.    union select spid,blocked from sysprocesses where  blocked>0  
  25.   
  26.   
  27.  IF @@ERROR<>0 RETURN @@ERROR   
  28.     
  29. -- 找到临时表的记录数  
  30.  select  @intCountProperties = Count(*),@intCounter = 1  
  31.  from #tmp_lock_who  
  32.    
  33.  IF @@ERROR<>0 RETURN @@ERROR   
  34.    
  35.  if @intCountProperties=0  
  36.   select '现在没有阻塞和死锁信息' as message  
  37.   
  38.   
  39. -- 循环开始  
  40. while @intCounter <= @intCountProperties  
  41. begin  
  42. -- 取第一条记录  
  43.   select  @spid = spid,@bl = bl  
  44.   from #tmp_lock_who where Id = @intCounter   
  45.  begin  
  46.   if @spid =0   
  47.             select '引起数据库死锁的是: 'CAST(@bl AS VARCHAR(10)) + '进程号,其执行的SQL语法如下'  
  48.  else  
  49.             select '进程号SPID:'CAST(@spid AS VARCHAR(10))+ '被' + '进程号SPID:'CAST(@bl AS VARCHAR(10)) +'阻塞,其当前进程执行的SQL语法如下'  
  50.  DBCC INPUTBUFFER (@bl )  
  51.  end   
  52.   
  53.   
  54. -- 循环指针下移  
  55.  set @intCounter = @intCounter + 1  
  56. end  
  57.   
  58.   
  59. drop table #tmp_lock_who  
  60.   
  61.   
  62. return 0  
  63. end  

杀死锁和进程
如何去手动的杀死进程和锁?最简单的办法,重新启动服务。但是这里要介绍一个存储过程,通过显式的调用,可以杀死进程和锁。
[sql] view plaincopyprint?
  1. use master  
  2. go  
  3.   
  4.   
  5. if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[p_killspid]'and OBJECTPROPERTY(id, N'IsProcedure') = 1)  
  6. drop procedure [dbo].[p_killspid]  
  7. GO  
  8.   
  9.   
  10. create proc p_killspid  
  11. @dbname varchar(200)    --要关闭进程的数据库名  
  12. as    
  13.     declare @sql  nvarchar(500)    
  14.     declare @spid nvarchar(20)  
  15.   
  16.   
  17.     declare #tb cursor for  
  18.         select spid=cast(spid as varchar(20)) from master..sysprocesses where dbid=db_id(@dbname)  
  19.     open #tb  
  20.     fetch next from #tb into @spid  
  21.     while @@fetch_status=0  
  22.     begin    
  23.         exec('kill '+@spid)  
  24.         fetch next from #tb into @spid  
  25.     end    
  26.     close #tb  
  27.     deallocate #tb  
  28. go  
  29.   
  30.   
  31. --用法    
  32. exec p_killspid  'newdbpy'  

查看锁信息
如何查看系统中所有锁的详细信息?在企业管理管理器中,我们可以看到一些进程和锁的信息,这里介绍另外一种方法。
[sql] view plaincopyprint?
  1. --查看锁信息  
  2. create table #t(req_spid int,obj_name sysname)  
  3.   
  4.   
  5. declare @s nvarchar(4000)  
  6.     ,@rid int,@dbname sysname,@id int,@objname sysname  
  7.   
  8.   
  9. declare tb cursor for   
  10.     select distinct req_spid,dbname=db_name(rsc_dbid),rsc_objid  
  11.     from master..syslockinfo where rsc_type in(4,5)  
  12. open tb  
  13. fetch next from tb into @rid,@dbname,@id  
  14. while @@fetch_status=0  
  15. begin  
  16.     set @s='select @objname=name from ['+@dbname+']..sysobjects where id=@id'  
  17.     exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id  
  18.     insert into #t values(@rid,@objname)  
  19.     fetch next from tb into @rid,@dbname,@id  
  20. end  
  21. close tb  
  22. deallocate tb  
  23.   
  24.   
  25. select 进程id=a.req_spid  
  26.     ,数据库=db_name(rsc_dbid)  
  27.     ,类型=case rsc_type when 1 then 'NULL 资源(未使用)'  
  28.         when 2 then '数据库'  
  29.         when 3 then '文件'  
  30.         when 4 then '索引'  
  31.         when 5 then '表'  
  32.         when 6 then '页'  
  33.         when 7 then '键'  
  34.         when 8 then '扩展盘区'  
  35.         when 9 then 'RID(行 ID)'  
  36.         when 10 then '应用程序'  
  37.     end  
  38.     ,对象id=rsc_objid  
  39.     ,对象名=b.obj_name  
  40.     ,rsc_indid  
  41.  from master..syslockinfo a left join #t b on a.req_spid=b.req_spid  
  42.   
  43.   
  44. go  
  45. drop table #t  

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/fredrickhu/archive/2009/12/24/5071970.aspx
0 0
原创粉丝点击