我最近遇到一个问题。就是死锁。
事务(进程 ID 54)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务。
但是我的语句也很简单。就是执行sql的一个存储过程。因为没有太多的更新操作。我觉得我完全没必要使用事务。但是我发现没有事务好像也会产生锁的机制。因为对SQL SERVER 机制理解不够深入所以想问问大牛。
问题1:简单的语句查询,更新也会产生锁的机制吗?(答:都是有的。即使没有事务。)
问题2:先查询后更新。非常正常的流程。为什么也会产生死锁?(答:平时并发率不高,当并发非常高时,简单流程也会发生死锁!)
问题3:我应该如何减少死锁的出现频率?
ALTER PROCEDURE [dbo].[SP1] ( @Wid UNIQUEIDENTIFIER, @Sid UNIQUEIDENTIFIER, @timeCount INT, @LDate datetime ) AS DECLARE @Time INT DECLARE @Pass INT DECLARE @Date DATETIME DECLARE @LastUpdate DATETIME BEGIN --SET TRANSACTION ISOLATION LEVEL Read UnCommitted SELECT @Time=[TimeOut] FROM CW INNER JOIN CWE ON CW.Wid = CWE.id WHERE CW.id = @Wid IF EXISTS(SELECT 1 FROM CR WITH(NOLOCK) WHERE Sid = @Sid AND Wid = @Wid) BEGIN SELECT @Pass=SUM(Pass),@Date=max(CDate),@LastUpdate=max(LDate) FROM CR WITH(NOLOCK) WHERE Sid = @Sid AND Wid = @Wid IF(@Pass != @Time ) BEGIN IF(CONVERT(VARCHAR(10),@Date,120) = CONVERT(VARCHAR(10),GETDATE(),120)) BEGIN IF(@Pass + @timeCount+10 >= @Time ) BEGIN UPDATE CR SET Pass = Pass + (@Time - @Pass),LDate=@LDate WHERE Sid = @Sid AND Wid = @Wid AND CDate = @Date END ELSE BEGIN UPDATE CR SET Pass = Pass + @timeCount,LDate=@LDate WHERE Sid = @Sid AND Wid = @Wid AND CDate = @Date END END ELSE BEGIN SET @Pass = @timeCount INSERT INTO CR (id,Pass,Sid,Wid,CDate,LDate) VALUES(NEWID(),@Pass,@Sid,@Wid,GETDATE(),@LDate) END END END ELSE BEGIN SET @Pass = @timeCount INSERT INTO CR (id,Pass,Sid,Wid,CDate,LDate) VALUES(NEWID(),@Pass,@Sid,@Wid,GETDATE(),@LDate) END END
文章中多次提到事务!所以我就开始有疑问了,我都没用事务!锁还是不锁呢?
@薛凯凯凯凯凯: 默认都有锁,你得看下面的评论,注意 noblock 关键字。
@Launcher: 好的~谢谢你回答。不过即使我看完这篇文章由于理解能力也还是蛮低,我还是没有思路去优化我如何减少死锁出现的频率或者说让死锁不再出现。按我的理解就是这个过程并发肯定很多。我如何让死锁减少呢?不知道大牛有没有经验。
@薛凯凯凯凯凯: 还有就是NOLOCK关键字。我也看到评论说性能是很高的。关键是衡量对自己业务和用户能不能接受脏数据。我这边使用NOLOCK是因为CR表的信息 我完全不读给用户看。所以我使用NOLOCK。不知道大牛让我NOBLOCK关键字是想说明什么?能否给予一些建议和方案呢?
@薛凯凯凯凯凯: 通常来说经验很重要,而且要有宏观视角,你能对你的所有代码有个清晰的逻辑视图,然后可以编写出基本上不存在死锁的程序,当然,即使这样,也有可能引入死锁,所以事后分析死锁发生的原因是最重要的应对死锁的策略。我给出的链接里很清晰的描述了当死锁产生后,你如何找出死锁发生的位置和资源的竞争方都有谁,根据这些信息,你就可以分析你的代码,并修改相应的逻辑来避免死锁。
@薛凯凯凯凯凯: 我提到 NOLOCK,是告诉你默认的操作都有锁,只有显示指定了 NOLOCK,操作才没有锁。因为你说你没用事务,好像就没有锁一样,实际上不是这样的。
@Launcher: 说到经验,我确实不足,所以才问。我还想问下目前由于这个数据设计上是使用GUID为主键,那么我并没有创建聚集索引的。所以表是没有索引的~我的问题是如果表没有索引是否会影响更新或者插入的速度?您觉得我这个过程有问题吗?是否逻辑判断太多?造成慢了等待过久呢?
@薛凯凯凯凯凯: 本来是说死锁,怎么又扯到聚集索引对更新和插入的性能影响上了呢?
http://www.cnblogs.com/CareySon/archive/2012/03/06/2381582.html
逻辑判断太多肯定会增加处理时间,但是并不表示这就可以减少逻辑判断,因为这可能是业务上的需求。你应该从你的业务出发,重构你的逻辑,保证资源最小化的同时,又能符合你的业务需求。
@薛凯凯凯凯凯: 还有一点就是,假如这是刚需!就是必须这样才能满足用户。那么假设1秒钟 有1W个用户并发请求这个过程。那么我该有什么解决方法让死锁能少点?
更换行版本控制(READ_COMMITTED_SNAPSHOT)?降低隔离级别(其实也用过了感觉没什么效果)?读写分离?或者说您有更好的思路呢?
@Launcher: 因为我觉得索引可能影响了 更新速度。才导致其他请求等待太久。所以我才问下你。如果真有影响的话,那它也是解决死锁的问题之一。怎么会说又扯到另一个问题上的呢、!
@薛凯凯凯凯凯: 你这是猜测,不是实证,当然你要是经验丰富的话,你这样猜测也是有你的直觉的。因为慢并不表示就会产生死锁,死锁的发生是两个线程互相等待对方释放资源。我还是建议你:我给出的链接里很清晰的描述了当死锁产生后,你如何找出死锁发生的位置和资源的竞争方都有谁,根据这些信息,你就可以分析你的代码,并修改相应的逻辑来避免死锁。