sql server中的死锁

sql server中的死锁

一、死锁原理

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

死锁的四个必要条件


1、互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
2、请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
3、非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
4、循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。

二、sql死锁的表现

表现一


一个用户A 访问表A(锁住了表A),然后又访问表B
另一个用户B 访问表B(锁住了表B),然后企图访问表A

这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了
同样用户B要等用户A释放表A才能继续这就死锁了

 

解决方法


这种死锁是由于你的程序的BUG产生的,除了调整你的程序的逻辑别无他法,仔细分析你程序的逻辑


1:尽量避免同时锁定两个资源
2: 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源.

 


表现二
 

用户A读一条纪录,然后修改该条纪录
这时用户B修改该条纪录


这里用户A的事务里锁的性质由共享锁企图上升到独占锁(for update),而用户B里的独占锁由于A有共享锁存在所以必须等A释
放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。

 

这种死锁比较隐蔽,但其实在稍大点的项目中经常发生。
 

解决方法


让用户A的事务(即先读后写类型的操作),在select 时就是用Update lock


语法如下
select * from table1 with(updlock) where ....

三、死锁排查

(1). 使用SQL Server的系统存储过程sp_who和sp_lock

可以查看当前数据库中的锁情况;进而根据objectID(@objID)(SQL Server 2005)/ object_name(@objID)(Sql Server 2000)可以查看哪个资源被锁,用dbcc ld(@blk),可以查看最后一条发生给SQL Server的Sql语句;

  •  
  • SQL 代码   复制
  • 
    CREATE Table #Who(spid int,
        ecid int,
        status nvarchar(50),
        loginname nvarchar(50),
        hostname nvarchar(50),
        blk int,
        dbname nvarchar(50),
        cmd nvarchar(50),
        request_ID int);
    
    CREATE Table #Lock(spid int,
        dpid int,
        objid int,
        indld int,
        [Type] nvarchar(20),
        Resource nvarchar(50),
        Mode nvarchar(10),
        Status nvarchar(10)
    );
    
    INSERT INTO #Who
        EXEC sp_who active  --看哪个引起的阻塞,blk 
    INSERT INTO #Lock
        EXEC sp_lock  --看锁住了那个资源id,objid 
    
    DECLARE @DBName nvarchar(20);
    SET @DBName='NameOfDataBase'
    
    SELECT #Who.* FROM #Who WHERE dbname=@DBName
    SELECT #Lock.* FROM #Lock
        JOIN #Who
            ON #Who.spid=#Lock.spid
                AND dbname=@DBName;
    
    --最后发送到SQL Server的语句
    DECLARE crsr Cursor FOR
        SELECT blk FROM #Who WHERE dbname=@DBName AND blk<<0;
    DECLARE @blk int;
    open crsr;
    FETCH NEXT FROM crsr INTO @blk;
    WHILE (@@FETCH_STATUS = 0)
    BEGIN;
        dbcc inputbuffer(@blk);
        FETCH NEXT FROM crsr INTO @blk;
    END;
    close crsr;
    DEALLOCATE crsr;
    
    --锁定的资源
    SELECT #Who.spid,hostname,objid,[type],mode,object_name(objid) as objName FROM #Lock
        JOIN #Who
            ON #Who.spid=#Lock.spid
                AND dbname=@DBName
        WHERE objid<<0;
    
    DROP Table #Who;
    DROP Table #Lock;
    
    		
  •  

    (2). 使用 SQL Server Profiler 分析死锁

    Deadlock graph 事件类添加到跟踪。此事件类使用死锁涉及到的进程和对象的XML 数据填充跟踪中的 TextData 数据列。SQL Server 事件探查器 可以将 XML 文档提取到死锁 XML (.xdl) 文件中,以后可在 SQL Server Management Studio 中查看该文件。

    四、 避免死锁

  • 1、按同一顺序访问对象
  •  
  • 如果所有并发事务按同一顺序访问对象,则发生死锁的可能性会降低。例如,如果两个并发事务获得 Supplier 表上的锁,然后获得 Part 表上的锁,则在其中一个事务完成之前,另一个事务被阻塞在 Supplier 表上。第一个事务提交或回滚后,第二个事务继续进行。不发生死锁。将存储过程用于所有的数据修改可以标准化访问对象的顺序。
  •  
  • 2、避免事务中的用户交互
  •  
  • 避免编写包含用户交互的事务,因为运行没有用户交互的批处理的速度要远远快于用户手动响应查询的速度,例如答复应用程序请求参数的提示。例如,如果事务正在等待用户输入,而用户去吃午餐了或者甚至回家过周末了,则用户将此事务挂起使之不能完成。这样将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或回滚时才会释放。即使不出现死锁的情况,访问同一资源的其它事务也会被阻塞,等待该事务完成。
  •  
  • 3、保持事务简短并在一个批处理中
  •  
  • 在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长,其持有排它锁或更新锁的时间也就越长,从而堵塞了其它活动并可能导致死锁。
  •  
  • 保持事务在一个批处理中,可以最小化事务的网络通信往返量,减少完成事务可能的延迟并释放锁。
  •  
  • 4、使用低隔离级别
  •  
  • 确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如提交读)而不使用较高的隔离级别(例如可串行读)可以缩短持有共享锁的时间,从而降低了锁定争夺。
  •  
  • 5、使用绑定连接
  •  
  • 使用绑定连接使同一应用程序所打开的两个或多个连接可以相互合作。次级连接所获得的任何锁可以象由主连接获得的锁那样持有,反之亦然,因此不会相互阻塞
  •  
  • 6、SELECT语句加With(NoLock)提示

    默认情况下SELECT语句会对查询到的资源加S(共享锁)S锁与X(排他锁)不兼容;但加上With(NoLock)后,SELECT不对查询到的资源加锁(或者加Sch-S锁,Sch-S锁可以与任何锁兼容);从而可以是这两条SQL可以并发地访问同一资源。当然,此方法适合解决读与写并发死锁的情况,但加With(NoLock)可能会导致脏读。

    例如

    SELECT * FROM Lock2 WITH(NOLock)
    SELECT * FROM Lock1 WITH(NOLock)

    五、杀死锁和进程

     如何去手动的杀死进程和锁?最简单的办法,重新启动服务。但是这里要介绍一个存储过程,通过显式的调用,可以杀死进程和锁。

  •  
  • SQL 代码   复制
  • 
    use master
    go
    
    if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[p_killspid]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
    drop procedure [dbo].[p_killspid]
    GO
    
    create proc p_killspid
    @dbname varchar(200)    --要关闭进程的数据库名
    as  
        declare @sql  nvarchar(500)  
        declare @spid nvarchar(20)
    
        declare #tb cursor for
            select spid=cast(spid as varchar(20)) from master..sysprocesses where dbid=db_id(@dbname)
        open #tb
        fetch next from #tb into @spid
        while @@fetch_status=0
        begin  
            exec('kill '+@spid)
            fetch next from #tb into @spid
        end  
        close #tb
        deallocate #tb
    go
    
    --用法  
    exec p_killspid  'newdbpy'
    
    		
  • 六、查看锁信息

    如何查看系统中所有锁的详细信息?在企业管理管理器中,我们可以看到一些进程和锁的信息,这里介绍另外一种方法。

  •  
  • SQL 代码   复制
  • 
    --查看锁信息
    create table #t(req_spid int,obj_name sysname)
    
    declare @s nvarchar(4000)
        ,@rid int,@dbname sysname,@id int,@objname sysname
    
    declare tb cursor for 
        select distinct req_spid,dbname=db_name(rsc_dbid),rsc_objid
        from master..syslockinfo where rsc_type in(4,5)
    open tb
    fetch next from tb into @rid,@dbname,@id
    while @@fetch_status=0
    begin
        set @s='select @objname=name from ['+@dbname+']..sysobjects where id=@id'
        exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id
        insert into #t values(@rid,@objname)
        fetch next from tb into @rid,@dbname,@id
    end
    close tb
    deallocate tb
    
    select 进程id=a.req_spid
        ,数据库=db_name(rsc_dbid)
        ,类型=case rsc_type when 1 then 'NULL 资源(未使用)'
            when 2 then '数据库'
            when 3 then '文件'
            when 4 then '索引'
            when 5 then ''
            when 6 then ''
            when 7 then ''
            when 8 then '扩展盘区'
            when 9 then 'RID(行 ID)'
            when 10 then '应用程序'
        end
        ,对象id=rsc_objid
        ,对象名=b.obj_name
        ,rsc_indid
     from master..syslockinfo a left join #t b on a.req_spid=b.req_spid
    
    go
    drop table #t
    
    		
  • 标签: