数据库死锁问题 及 解决方法

时间:2021-12-27 08:27:23
 什么是数据库死锁

     每个使用关系型数据库的程序都可能遇到数据死锁的情况。理解什么是死锁之前先要了解锁定的概念:如果需要“修改”一条数据,首先数据库管理系统会在上面加锁,以保证在同一时间只有一个事务能进行修改操作。锁定(Locking)发生在当一个事务获得对某一资源的“锁”时,这时,其他的事务就不能更改这个资源了,这种机制的存在是为了保证数据一致性。

     多数情况下,可以认为如果一个资源被锁定,它总会在以后某个时间被释放。而死锁发生在当多个进程访问同一数据库时,其中每个进程拥有的锁都是其他进程所需的,由此造成每个进程都无法继续下去。简单的说,进程A等待进程B释放他的资源,B又等待A释放他的资源,这样就互相等待就形成死锁。

  • 导致数据库死锁的原因

     一般情况只发生锁超时,就是一个进程需要访问数据库表或者字段的时候,另外一个程序正在执行带锁的访问(比如修改数据),那么这个进程就会等待,当等了很久锁还没有解除的话就会锁超时,报告一个系统错误,拒绝执行相应的SQL操作。

     发生死锁的情况比较少,比如一个进程需要访问两个资源(数据库表或者字段),当获取一个资源的时候进程就对它执行锁定,然后等待下一个资源空闲,这时候如果另外一个进程也需要两个资源,而已经获得并锁定了第二个资源,那么就会死锁,因为当前进程锁定第一个资源等待第二个资源,而另外一个进程锁定了第二个资源等待第一个资源,两个进程都永远得不到满足。

  • 数据库死锁的解决方案
use  master         -- 必须在master数据库中创建
go

if   exists  ( select   *   from  dbo.sysobjects  where  id  =   object_id (N ' [dbo].[p_lockinfo] ' and   OBJECTPROPERTY (id, N ' IsProcedure ' =   1 )
drop   procedure   [ dbo ] . [ p_lockinfo ]
GO

/**//* --处理死锁

        查看当前进程,或死锁进程,并能自动杀掉死进程

        因为是针对死锁的,所以如果有死锁进程,只能查看死锁进程
        当然,你可以通过参数控制,不管有没有死锁,都只查看死锁进程

        感谢: caiyunxia,jiangopen 两位提供的参考信息

--邹建 2004.04(引用请保留此信息)--
*/

/**//* --调用示例

        exec p_lockinfo
--
*/
create   proc  p_lockinfo
@kill_lock_spid   bit = 1 ,                 -- 是否杀掉死锁的进程,1 杀掉, 0 仅显示
@show_spid_if_nolock   bit = 1          -- 如果没有死锁的进程,是否显示正常进程信息,1 显示,0 不显示
as
set  nocount  on
declare   @count   int , @s   nvarchar ( 1000 ), @i   int
select  id = identity ( int , 1 , 1 ),标志,
        进程ID
= spid,线程ID = kpid,块进程ID = blocked,数据库ID = dbid,
        数据库名
= db_name (dbid),用户ID = uid,用户名 = loginame,累计CPU时间 = cpu,
        登陆时间
= login_time,打开事务数 = open_tran,        进程状态 = status,
        工作站名
= hostname,应用程序名 = program_name,工作站进程ID = hostprocess,
        域名
= nt_domain,网卡地址 = net_address
into  #t  from (
        
select  标志 = ' 死锁的进程 ' ,
                spid,kpid,a.blocked,dbid,uid,loginame,cpu,login_time,open_tran,
                status,hostname,program_name,hostprocess,nt_domain,net_address,
                s1
= a.spid,s2 = 0
        
from  master..sysprocesses a  join  (
                
select  blocked  from  master..sysprocesses  group   by  blocked
                )b 
on  a.spid = b.blocked  where  a.blocked = 0
        
union   all
        
select   ' |_牺牲品_> ' ,
                spid,kpid,blocked,dbid,uid,loginame,cpu,login_time,open_tran,
                status,hostname,program_name,hostprocess,nt_domain,net_address,
                s1
= blocked,s2 = 1
        
from  master..sysprocesses a  where  blocked <> 0
)a 
order   by  s1,s2

select   @count = @@rowcount , @i = 1

if   @count = 0   and   @show_spid_if_nolock = 1
begin
        
insert  #t
        
select  标志 = ' 正常的进程 ' ,
                spid,kpid,blocked,dbid,
db_name (dbid),uid,loginame,cpu,login_time,
                open_tran,status,hostname,program_name,hostprocess,nt_domain,net_address
        
from  master..sysprocesses
        
set   @count = @@rowcount
end

if   @count > 0
begin
        
create   table  #t1(id  int   identity ( 1 , 1 ),a  nvarchar ( 30 ),b  Int ,EventInfo  nvarchar ( 255 ))
        
if   @kill_lock_spid = 1
        
begin
                
declare   @spid   varchar ( 10 ), @标志   varchar ( 10 )
                
while   @i <= @count
                
begin
                        
select   @spid = 进程ID, @标志 = 标志  from  #t  where  id = @i
                        
insert  #t1  exec ( ' dbcc inputbuffer( ' + @spid + ' ) ' )
                        
if   @@rowcount = 0   insert  #t1(a)  values ( null )
                        
if   @标志 = ' 死锁的进程 '   exec ( ' kill  ' + @spid )
                        
set   @i = @i + 1
                
end
        
end
        
else
                
while   @i <= @count
                
begin
                        
select   @s = ' dbcc inputbuffer( ' + cast (进程ID  as   varchar ) + ' ) '   from  #t  where  id = @i
                        
insert  #t1  exec ( @s )
                        
if   @@rowcount = 0   insert  #t1(a)  values ( null )
                        
set   @i = @i + 1
                
end
        
select  a. * ,进程的SQL语句 = b.EventInfo
        
from  #t a  join  #t1 b  on  a.id = b.id
        
order   by  进程ID
end
set  nocount  off
go