GVKun编程网logo

sqlserver 死锁原因及解决方法(sqlserver死锁的原因)

13

最近很多小伙伴都在问sqlserver死锁原因及解决方法和sqlserver死锁的原因这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展I2C死锁原因及解决方法、I2C死锁原因及解决

最近很多小伙伴都在问sqlserver 死锁原因及解决方法sqlserver死锁的原因这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展I2C 死锁原因及解决方法、I2C死锁原因及解决方法(转)、Java中多线程详解(2)产生死锁原因及解决方法、MSSQL产生死锁的根本原因及解决方法等相关知识,下面开始了哦!

本文目录一览:

sqlserver 死锁原因及解决方法(sqlserver死锁的原因)

sqlserver 死锁原因及解决方法(sqlserver死锁的原因)

其实所有的死锁最深层的原因就是一个:资源竞争

 

表现一:

  一个用户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 ....

 

--------------------------------------------------------------------------------

接上面文章,继续探讨数据库死锁问题 

 

死锁,简而言之,两个或者多个trans,同时请求对方正在请求的某个对象,导致双方互相等待。简单的例子如下:
   trans1                                                    trans2
   ------------------------------------------------------------------------------------------------
   1.IDBConnection.BeginTransaction    1.IDBConnection.BeginTransaction
   2.update table A                                2.update table B
   3.update table B                                3.update table A
   4.IDBConnection.Commit                   4.IDBConnection.Commit
   那么,很容易看到,如果trans1和trans2,分别到达了step3,那么trans1会请求对于B的X锁,trans2会请求对于A的X锁,而二者的锁在step2上已经被对方分别持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。

   好,我们看一个简单的例子,来解释一下,应该如何解决死锁问题。


   -- Batch #1


   CREATE DATABASE deadlocktest
   GO


   USE deadlocktest
   SET NOCOUNT ON
   DBCC TRACEON (1222,-1)
   -- 在sql2005中,增加了一个新的dbcc参数,就是1222,原来在2000下,我们知道,可以执行dbcc   
   -- traceon(1204,3605,-1)看到所有的死锁信息。sqlServer 2005中,对于1204进行了增强,这就是1222。
   GO  
  
   IF OBJECT_ID ('t1') IS NOT NULL DROP TABLE t1
   IF OBJECT_ID ('p1') IS NOT NULL DROP PROC p1
   IF OBJECT_ID ('p2') IS NOT NULL DROP PROC p2
   GO

 

   CREATE TABLE t1 (c1 int,c2 int,c3 int,c4 char(5000))
   GO

 

   DECLARE @x int
   SET @x = 1
   WHILE (@x <= 1000) BEGIN
            INSERT INTO t1 VALUES (@x*2,@x*2,@x*2)
            SET @x = @x + 1
   END
   GO

 

   CREATE CLUSTERED INDEX cidx ON t1 (c1)
   CREATE NONCLUSTERED INDEX idx1 ON t1 (c2)
   GO

 

   CREATE PROC p1 @p1 int AS SELECT c2,c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO

 

   CREATE PROC p2 @p1 int AS
            UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
            UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

 

   上述sql创建一个deadlock的示范数据库,插入了1000条数据,并在表t1上建立了c1列的聚集索引,和c2列的非聚集索引。另外创建了两个sp,分别是从t1中select数据和update数据。

   好,打开一个新的查询窗口,我们开始执行下面的query:


   -- Batch #2

 

   USE deadlocktest
   SET NOCOUNT ON
   WHILE (1=1) EXEC p2 4
   GO

 

   开始执行后,然后我们打开第三个查询窗口,执行下面的query:


   -- Batch #3

 

   USE deadlocktest
   SET NOCOUNT ON
   CREATE TABLE #t1 (c2 int,c3 int)
   GO

 

   WHILE (1=1) BEGIN
             INSERT INTO #t1 EXEC p1 4
             TruncATE TABLE #t1
   END
   GO

 

   开始执行,哈哈,很快,我们看到了这样的错误信息:
   Msg 1205,Level 13,State 51,Procedure p1,Line 4
   Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

 

   spid54发现了死锁。
   那么,我们该如何解决它?

   在sqlServer 2005中,我们可以这么做:
   1.在trans3的窗口中,选择EXEC p1 4,然后right click,看到了菜单了吗?选择Analyse Query in Database Engine Tuning Advisor。
   2.注意右面的窗口中,wordload有三个选择:负载文件、表、查询语句,因为我们选择了查询语句的方式,所以就不需要修改这个radio option了。
   3.点左上角的Start Analysis按钮
   4.抽根烟,回来后看结果吧!出现了一个分析结果窗口,其中,在Index Recommendations中,我们发现了一条信息:大意是,在表t1上增加一个非聚集索引索引:t2+t1。
   5.在当前窗口的上方菜单上,选择Action菜单,选择Apply Recommendations,系统会自动创建这个索引。

   重新运行batch #3,呵呵,死锁没有了。

为什么会死锁呢?再回顾一下两个sp的写法:
   CREATE PROC p1 @p1 int AS
      SELECT c2,c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO


   CREATE PROC p2 @p1 int AS
         UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

 

   很奇怪吧!p1没有insert,没有delete,没有update,只是一个select,p2才是update。这个和我们前面说过的,trans1里面updata A,update B;trans2里面upate B,update A,根本不贴边啊!
   那么,什么导致了死锁?

   需要从事件日志中,看sql的死锁信息:
   Spid X is running this query (line 2 of proc [p1],inputbuffer “… EXEC p1 4 …”):
   SELECT c2,c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   Spid Y is running this query (line 2 of proc [p2],inputbuffer “EXEC p2 4”):
   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
               
   The SELECT is waiting for a Shared KEY lock on index t1.cidx.  The UPDATE holds a conflicting X lock.
   The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1.  The SELECT holds a conflicting S lock.

 

   首先,我们看看p1的执行计划。怎么看呢?可以执行set statistics profile on,这句就可以了。下面是p1的执行计划


   SELECT c2,c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
        |--nested Loops(Inner Join,OUTER REFERENCES:([Uniq1002],[t1].[c1]))
               |--Index Seek(OBJECT:([t1].[idx1]),SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
                     |--Clustered Index Seek(OBJECT:([t1].[cidx]),SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)

 

   我们看到了一个nested loops,第一行,利用索引t1.c2来进行seek,seek出来的那个rowid,在第二行中,用来通过聚集索引来查找整行的数据。这是什么?就是bookmark lookup啊!为什么?因为我们需要的c2、c3不能完全的被索引t1.c1带出来,所以需要书签查找。
   好,我们接着看p2的执行计划。


   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         |--Clustered Index Update(OBJECT:([t1].[cidx]),OBJECT:([t1].[idx1]),SET:([t1].[c2] = [Expr1004]))
               |--Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))
                     |--Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1),[Expr1013]=CASE WHEN CASE WHEN ...
                           |--Top(ROWCOUNT est 0)
                                 |--Clustered Index Seek(OBJECT:([t1].[cidx]),SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD)

 

   通过聚集索引的seek找到了一行,然后开始更新。这里注意的是,update的时候,它会申请一个针对clustered index的X锁的。

   实际上到这里,我们就明白了为什么update会对select产生死锁。update的时候,会申请一个针对clustered index的X锁,这样就阻塞住了(注意,不是死锁!)select里面最后的那个clustered index seek。死锁的另一半在哪里呢?注意我们的select语句,c2存在于索引idx1中,c1是一个聚集索引cidx。问题就在这里!我们在p2中更新了c2这个值,所以sqlserver会自动更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我们刚才的select语句中。而对这个索引列的更改,意味着索引集合的某个行或者某些行,需要重新排列,而重新排列,需要一个X锁。
   SO………,问题就这样被发现了。

 

   总结一下,就是说,某个query使用非聚集索引来select数据,那么它会在非聚集索引上持有一个S锁。当有一些select的列不在该索引上,它需要根据rowid找到对应的聚集索引的那行,然后找到其他数据。而此时,第二个的查询中,update正在聚集索引上忙乎:定位、加锁、修改等。但因为正在修改的某个列,是另外一个非聚集索引的某个列,所以此时,它需要同时更改那个非聚集索引的信息,这就需要在那个非聚集索引上,加第二个X锁。select开始等待update的X锁,update开始等待select的S锁,死锁,就这样发生鸟。

 

   那么,为什么我们增加了一个非聚集索引,死锁就消失鸟?我们看一下,按照上文中自动增加的索引之后的执行计划:


   SELECT c2,c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
      |--Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]),SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)

 

   哦,对于clustered index的需求没有了,因为增加的覆盖索引已经足够把所有的信息都select出来。就这么简单。

 

   实际上,在sqlserver 2005中,如果用profiler来抓eventid:1222,那么会出现一个死锁的图,很直观的说。

 

   下面的方法,有助于将死锁减至最少(详细情况,请看sqlServer联机帮助,搜索:将死锁减至最少即可。

       . 按同一顺序访问对象。 
       . 避免事务中的用户交互。 
       . 保持事务简短并处于一个批处理中。 
       . 使用较低的隔离级别。 
       . 使用基于行版本控制的隔离级别。 
              . 将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版本控制。 
              . 使用快照隔离。 
       . 使用绑定连接。

 

--------------------------------------------------------------------------------
那么需要我们弄懂一个问题,什么是索引,如何添加索引及其使用规则?
这里有前辈总接出得文章,拿来直接用,我就没必要在重写一次了
sql Server 索引结构及其使用(一)
作者:freedk
一、深入浅出理解索引结构
  实际上,您可以把索引理解为一种特殊的目录。微软的sql SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:
  其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。
  如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。
  通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。
 
二、何时使用聚集索引或非聚集索引
下面的表总结了何时使用聚集索引或非聚集索引(很重要):
动作描述 使用聚集索引 使用非聚集索引
列经常被分组排序
返回某范围内的数据 不应
一个或极少不同值 不应 不应
小数目的不同值 不应
大数目的不同值 不应
频繁更新的列 不应
外键列
主键列
频繁修改索引列 不应
  事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。
三、结合实际,谈索引使用的误区
  理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。
1、主键就是聚集索引
  这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然sql SERVER默认是在主键上建立聚集索引的。
  通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,sql SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。
  显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。
  从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。
  在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。
  通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。
  在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):
(1)仅在主键上建立聚集索引,并且不划分时间段:
Select gid,fariqi,neibuyonghu,title from tgongwen
用时:128470毫秒(即:128秒)
(2)在主键上建立聚集索引,在fariq上建立非聚集索引:
select gid,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用时:53763毫秒(54秒)
(3)将聚合索引建立在日期列(fariqi)上:
select gid,getdate())
用时:2423毫秒(2秒)
  虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第1、2种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。得出以上速度的方法是:在各个select语句前加:
declare @d datetime
set @d=getdate()
并在select语句后加:
select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())
2、只要建立索引就能显著提高查询速度
  事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。
  从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。
3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度
  上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。
  很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列):
(1)select gid,title from Tgongwen where fariqi>''2004-5-5''
查询速度:2513毫秒
(2)select gid,title from Tgongwen where fariqi>''2004-5-5'' and neibuyonghu=''办公室''
查询速度:2516毫秒
(3)select gid,title from Tgongwen where neibuyonghu=''办公室''
查询速度:60280毫秒
  从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。
四、其他书上没有的索引使用经验总结
1、用聚合索引比用不是聚合索引的主键速度快
  下面是实例语句:(都是提取25万条数据)
select gid,reader,title from Tgongwen where fariqi=''2004-9-16''使用时间:3326毫秒
select gid,title from Tgongwen where gid<=250000使用时间:4470毫秒
这里,用聚合索引比用不是聚合索引的主键速度快了近1/4。
2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下
select gid,title from Tgongwen order by fariqi用时:12936
select gid,title from Tgongwen order by gid用时:18843
  这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。
3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个:
select gid,title from Tgongwen where fariqi>''2004-1-1''用时:6343毫秒(提取100万条)
select gid,title from Tgongwen where fariqi>''2004-6-6''用时:3170毫秒(提取50万条)
select gid,title from Tgongwen where fariqi=''2004-9-16''用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的)
select gid,title from Tgongwen
            where fariqi>''2004-1-1'' and fariqi<''2004-6-6''用时:3280毫秒
4、日期列不会因为有分秒的输入而减慢查询速度
  下面的例子中,共有100万条数据,2004年1月1日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。
select gid,title from Tgongwen
          where fariqi>''2004-1-1'' order by fariqi用时:6390毫秒
select gid,title from Tgongwen
            where fariqi<''2004-1-1'' order by fariqi用时:6453毫秒
五、其他注意事项
  “水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。
  所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。
  当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。
 此文章引自 http://www.vckbase.com/document/viewdoc/?id=1307
 
感觉笔者讲比较透彻,并参考了另一篇: 索引的使用和优化

 

 思想基本一致的,总结下来,对日期建立聚集索引比较合适。

 

 还有一有关索引性能的MS的文章:使用 SQL Server 2000 索引视图提高性能

I2C 死锁原因及解决方法

I2C 死锁原因及解决方法

OSC 请你来轰趴啦!1028 苏州源创会,一起寻宝 AI 时代

原因

当单片机正在和 I2C 从机通信,如果主正好发生打算发第 9 个时钟,此时 SCL 为高,而从开始拉低 SDA 为低做准备(作为 ACK 信号),等待主 SCL 变低后,从再释放 SDA 为高。如果此时正好单片机复位,主 SCL 还没来得及变低,直接变成高电平,此时从还在等待 SCL 变低,所以一直拉低 SDA;而主由于复位,发现 SDA 一直为低,也在等待从释放 SDA 为高。因此主从都进入一个相互等待的死锁状态。

解决办法

  1. 最好的方法是采用模拟 i2c. 但由于已经配置成硬件 i2c, 程序改为上电或复位改成发 9 个 SCL 时钟信号,使从好释放 SDA。
  2. 尽量选用带复位输人的 I2C 从器件。
  3. 将所有的从 I2C 设备的电源连接在一起,通过 MOS 管连接到主电源,而 MOS 管的导通关断由 I2C 主设备来实现。
  4. 在 I2C 从设备设计看门狗的功能。
  5. 在 I2C 主设备中增加 I2C 总线恢复程序。每次 I2C 主设备复位后,如果检测到 SDA 数据线被拉低,则控制 I2C 中的 SCL 时钟线产生 9 个时钟脉冲 (针对 8 位数据的情况),这样 I2C 从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。 这种方法有很大的局限性,因为大部分主设备的 I2C 模块由内置的硬件电路来实现,软件并不能够直接控制 SCL 信号模拟 产生需要时钟脉冲。
  6. 在 I2C 总线上增加一个额外的总线恢复设备。这个设备监视 I2C 总线。当设备检测到 SDA 信号被拉低超过指定时间 时,就在 SCL 总线上产生 9 个时钟脉冲,使 I2C 从设备完成读操作,从死锁状态上恢复出来。总线恢复设备需要有具有编程 功能,一般可以用单片机或 CPLD 实现这一功能。
  7. 在 I2C 上串人一个具有死锁恢复的 I2C 缓冲器,如 Linear 公司的 LTC4307:LTC4307 是一个双向的 I2C 总线缓冲器,并且具有 I2C 总线死锁恢复的功能。LTC4307 总线输人侧连接主设备,总线输出侧连接所有从设备。当 LTC4307 检测到输出侧 SDA 或 SCL 信号被拉低 30ms 时,就自动断开 I2C 总线输人侧与输出侧的连接。并且在输出侧 SCL 信号上产生 16 个时钟脉冲来释放总线。当总线成功恢复后,LTC4307 会再次连接输人输出侧,使总线能够正常工作。

I2C死锁原因及解决方法(转)

I2C死锁原因及解决方法(转)

死锁总线表现为:SCL为高,SDA一直为低

 

现象:单片机采用硬件i2c读取E2PROM,当单片机复位时,会有概率出现再无法与E2PROM通信,此时SCL为高,SDA一直为低

原因:当单片机正在和E2PROM通信,如果主正好发生打算发第9个时钟,此时SCL为高,而从开始拉低SDA为低做准备(作为ACK信号),等待主SCL变低后,从再释放SDA为高。如果此时正好单片机复位,主SCL还没来得及变低,直接变成高电平,此时从还在等待SCL变低,所以一直拉低SDA;而主由于复位,发现SDA一直为低,也在等待从释放SDA为高。因此主从都进入一个相互等待的死锁状态。

解决方法:最好的方法是采用模拟i2c. 但由于已经配置成硬件i2c,程序改为上电或复位改成发9个SCL时钟信号,使从好释放SDA。

 

 

 最近发现单片机(硬件I2C实现)读取E2PROM时候,单片机复位可能会引起i2C死锁,表现为SCL为高,SDA一直为低,后发现是E2PROM从设备拉死i2c总线,从设备断电之后,SDA变高,上电后通信正常。后来通过拉低SCL信号线,SDA就会自动变成高电平,i2c总线恢复。后查看一篇文章,讲的不错,特摘录如下:

 

    在正常情况下,I2C总线协议能够保证总线正常的读写操作。但是,当I2C主设备异常复位时(看门狗动作,板上电源异常
导致复位芯片动作,手动按钮复位等等)有可能导致I2C总线死锁产生。下面详细说明一下总线死锁产生的原因。

 

    在I2C主设备进行读写操作的过程中.主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从设备输出应答信号,将SDA信号拉为低电平。如果这个时候主设备异常复位,SCL就会被释放为高电平。此时,如果从设备没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。而对于I2C主设备来说.复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。这样,I2C主设备等待从设备释放SDA信号,而同时I2C从设备又在等待主设备将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。同样,当I2C进行读操作,I2C从设备应答后输出数据,如果在这个时刻I2C主设备异常复位而此时I2C从设备输出的数据位正好为0,也会导致I2C总线进入死锁状态。

 

SCL为高,SDA一直为低原因

从:正常时序下:SDA信号是在SCL为低的状态下改变,即从应答SDA为低电平时,此时SCL应为为低电平(即从设备是先拉低SDA信号,等待主设备SCL由高变低,“取走”ACK信号后,从再释放SDA为高)。但如果此时时序被打乱,例如单片机i2c通信时突然复位,SCL突然变高,则从设备SDA一直为低,等待SCL变低。

主:SDA被从拉低,故主认为i2c总线占用,一直等待SDA变高

这样主从进入一个相互等待的死锁过程。

 

 方法

    最好用模拟I2C实现,则不会死锁

    (1)尽量选用带复位输人的I2C从器件。

    (2)将所有的从I2C设备的电源连接在一起,通过MOS管连接到主电源,而MOS管的导通关断由I2C主设备来实现。
    (3)在I2C从设备设计看门狗的功能。

    (4)在I2C主设备中增加I2C总线恢复程序。每次I2C主设备复位后,如果检测到SDA数据线被拉低,则控制I2C中的
SCL时钟线产生9个时钟脉冲
(针对8位数据的情况),这样I2C从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。
这种方法有很大的局限性,因为大部分主设备的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟
产生需要时钟脉冲。

  (5)在I2C总线上增加一个额外的总线恢复设备。这个设备监视I2C总线。当设备检测到SDA信号被拉低超过指定时间
时,就在SCL总线上产生9个时钟脉冲,使I2C从设备完成读操作,从死锁状态上恢复出来。总线恢复设备需要有具有编程
功能,一般可以用单片机或CPLD实现这一功能。

  (6)在I2C上串人一个具有死锁恢复的I2C缓冲器,如Linear公司的LTC4307如图2所示:LTC4307是一个双向的I2C
总线缓冲器,并且具有I2C总线死锁恢复的功能。LTC4307总线输人侧连接主设备,总线输出侧连接所有从设备。当LTC4307
检测到输出侧SDA或SCL信号被拉低30ms时,就自动断开I2C总线输人侧与输出侧的连接.并且在输出侧SCL信号上产生16个时钟脉冲来释放总线
。当总线成功恢复后,LTC4307会再次连接输人输出侧,使总线能够正常工作

Java中多线程详解(2)产生死锁原因及解决方法

Java中多线程详解(2)产生死锁原因及解决方法

一、什么是死锁

死锁理解起来很简单,就是一个字,堵,下面图中拥堵的十字路口就可以看做一个死锁的状态,四个方向的车都要往前走,但是十字路口只有一个,只能允许一个方向的车通过后,才能让另一个方向的车通过。

在多线程中,四个方向的车流就可以看做4个线程,而十字路口可以看做一个资源对象,四个线程都要占有它,会导致程序无法正常的运行,这就叫死锁,是不是很容易理解呢。

用比较官方的解释死锁的话,就是经典的四大条件

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

二、创建死锁案例

public class DeadLock {
    public static String r1="r1";
    public static String r2="r2";
    public static void main(String[] args) {
        Runable1 runable1=new Runable1();
        Runable2 runable2=new Runable2();
        Thread thread1=new Thread(runable1);
        Thread thread2=new Thread(runable2);
        thread1.start();
        thread2.start();
    }
}
class Runable1 implements Runnable{
    @Override
    public void run() {
        try {
             (DeadLock.r1) {
                System.out.println("r1的synchronized锁住r1");
                Thread.sleep(3000);
                synchronized (DeadLock.r2) {
                    System.out.println("拿不到r2略略略");
                    Thread.sleep(60 * 1000);
                }
                System.out.println("被锁住了,无法打印");
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Runable2 implements Runnable{
    @Override
    public void run() {
        try {
            synchronized (DeadLock.r2){
                System.out.println("synchronized锁住r2");
                Thread.sleep(3000);
                synchronized (DeadLock.r1){
                    System.out.println("拿不到r1略略略");
                    Thread.sleep(60 * 1000);
                }
                System.out.println("被锁住了,无法打印");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//运行结果
r1的synchronized锁住r1
synchronized锁住r2
复制代码

在上面这个案例中,先创建了两个资源对象r1和r2,然后创建了两个线程类Runable1和Runable2,用synchronized方法去给资源添加锁,使得两个线程在执行完run方法之前都不会释放资源对象r1和r2,(synchronized方法的作用就是一个同步锁,代码中放入synchronized括号中的资源在线程结束前都不会释放,也就不能被其他线程使用)之后用sleep方法让线程阻塞(sleep(3000)的作用是是给线程能锁住机会,sleep(60*1000)的作用是让线程阻塞),这样就造成了死锁的现象,导致后面的语句无法打印出来。

三、如何解决死锁问题

1.在写多线程操作时,最好不要一次锁定多个对象,这样就不会导致死锁了。修改后的代码如下。

public class DeadLock {
    public static String r1="r1";
    public static String r2="r2";
    public static void main(String[] args) {
        Runable1 runable1=new Runable1();
        Runable2 runable2=new Runable2();
        Thread thread1=new Thread(runable1);
        Thread thread2=new Thread(runable2);
        thread1.start();
        thread2.start();
    }
}
class Runable1 implements Runnable{
    @Override
    public void run() {
        try {
            synchronized (DeadLock.r1) {
                System.out.println("r1的synchronized锁住r1");
                Thread.sleep(3000);
                    System.out.println("拿到r2");
                    Thread.sleep(3000);
                System.out.println("解锁了,可以打印");
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Runable2 implements Runnable{
    @Override
    public void run() {
        try {
            synchronized (DeadLock.r2){
                System.out.println("synchronized锁住r2");
                Thread.sleep(3000); 
                   System.out.println("拿到r1");
                    Thread.sleep(3000);
                System.out.println("解锁了,可以打印");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//运行结果
r1的synchronized锁住r1
synchronized锁住r2
拿到r2
拿到r1
解锁了,可以打印
解锁了,可以打印
复制代码

2.如果业务场景需要一次锁定多个资源对象,可以根据资源的某个属性或者hashCode值来做比较,定义锁的先后顺序,下面是一个通过hashCode来定义锁先后顺序的例子。

public class DeadLock {
    public static String r1="r1";
    public static String r2="r2";
    public static void main(String[] args) {
        Runable1 runable1=new Runable1();
        Runable2 runable2=new Runable2();
        Thread thread1=new Thread(runable1);
        Thread thread2=new Thread(runable2);
        thread1.start();
        thread2.start();
    }
}
class Runable1 implements Runnable{
    @Override
    public void run() {
        try {
            if (DeadLock.r1.hashCode()>DeadLock.r2.hashCode()) {
                synchronized (DeadLock.r1) {
                    System.out.println("Runable1的synchronized锁住r1");
                    Thread.sleep(3000);
                    synchronized (DeadLock.r2) {
                        System.out.println("Runable1的synchronized锁住r2");
                        Thread.sleep(3000);
                    }
                    System.out.println("解锁了,可以打印");
                }
            }else{
                synchronized (DeadLock.r2) {
                    System.out.println("Runable1的synchronized锁住r1");
                    Thread.sleep(3000);
                    synchronized (DeadLock.r1) {
                        System.out.println("Runable1的synchronized锁住r2");
                        Thread.sleep(3000);
                    }
                    System.out.println("解锁了,可以打印");
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Runable2 implements Runnable{
    @Override
    public void run() {
        try {
            if (DeadLock.r1.hashCode()<DeadLock.r2.hashCode()) {
                synchronized (DeadLock.r2) {
                    System.out.println("Runable2的synchronized锁住r2");
                    Thread.sleep(3000);
                    synchronized (DeadLock.r1) {
                        System.out.println("Runable2的synchronized锁住r1");
                        Thread.sleep(3000); 
                   }
                    System.out.println("解锁了,可以打印");
                }
            }else{
                synchronized (DeadLock.r1) {
                    System.out.println("Runable2的synchronized锁住r1");
                    Thread.sleep(3000);
                    synchronized (DeadLock.r2) {
                        System.out.println("Runable2的synchronized锁住r2");
                        Thread.sleep(3000);
                    }
                    System.out.println("解锁了,可以打印");
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//运行结果
Runable1的synchronized锁住r1
Runable1的synchronized锁住r2
解锁了,可以打印
Runable2的synchronized锁住r1
Runable2的synchronized锁住r2
解锁了,可以打印

参考:《2020最新Java基础精讲视频教程和学习路线!》
链接:https://juejin.cn/post/694043...

MSSQL产生死锁的根本原因及解决方法

MSSQL产生死锁的根本原因及解决方法

一、 什么是死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程.

二、 死锁产生的四个必要条件

•互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放

•请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放

•不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放

•环路等待条件:指在发生死锁时,必然存在一个进程――资源的环形链,即进程集合{P0,P1,P2,・・・,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

三、 如何处理死锁

1) 锁模式

1.共享锁(S)

由读操作创建的锁,防止在读取数据的过程中,其它事务对数据进行更新;其它事务可以并发读取数据。共享锁可以加在表、页、索引键或者数据行上。在sql SERVER默认隔离级别下数据读取完毕后就会释放共享锁,但可以通过锁提示或设置更高的事务隔离级别改变共享锁的释放时间。

2.独占锁(X)

对资源独占的锁,一个进程独占地锁定了请求的数据源,那么别的进程无法在此数据源上获得任何类型的锁。独占锁一致持有到事务结束。

3.更新锁(U)

更新锁实际上并不是一种独立的锁,而是共享锁与独占锁的混合。当sql SERVER执行数据修改操作却首先需要搜索表以找到需要修改的资源时,会获得更新锁。

更新锁与共享锁兼容,但只有一个进程可以获取当前数据源上的更新锁,

其它进程无法获取该资源的更新锁或独占锁,更新锁的作用就好像一个序列化阀门(serialization gate),将后续申请独占锁的请求压入队列中。持有更新锁的进程能够将其转换成该资源上的独占锁。更新锁不足以用于更新数据―实际的数据修改仍需要用到独占锁。对于独占锁的序列化访问可以避免转换死锁的发生,更新锁会保留到事务结束或者当它们转换成独占锁时为止。

4. 意向锁(IX,IU,IS)

意向锁并不是独立的锁定模式,而是一种指出哪些资源已经被锁定的机制。

如果一个表页上存在独占锁,那么另一个进程就无法获得该表上的共享表锁,这种层次关系是用意向锁来实现的。进程要获得独占页锁、更新页锁或意向独占页锁,首先必须获得该表上的意向独占锁。同理,进程要获得共享行锁,必须首先获得该表的意向共享锁,以防止别的进程获得独占表锁。

5. 特殊锁模式(Sch_s,Sch_m,BU)

sql SERVER提供3种额外的锁模式:架构稳定锁、架构修改锁、大容量更新锁。

6.转换锁(SIX,SIU,UIX)

转换锁不会由sql SERVER 直接请求,而是从一种模式转换到另一种模式所造成的。sql SERVER 2008支持3种类型的转换锁:SIX、SIU、UIX.其中最常见的是SIX锁,如果事务持有一个资源上的共享锁(S),然后又需要一个IX锁,此时就会出现SIX。

7.键范围锁

键范围锁是在可序列化隔离级别中锁定一定范围内数据的锁。保证在查询数据的键范围内不允许插入数据。

sql SERVER 锁模式

缩写

锁模式

说明

S

Shared

允许其他进程读取但不能修改锁定的资源

X

Exclusive

防止别的进程读取或者修改锁定资源中的数据

U

Update

防止其它进程获取更新锁或独占锁;在搜索要修改的数据时使用

IS

Intent shared

表示该资源的一个组件被共享锁锁定了。只有在表或页级别才能获得这类锁

IU

Intent update

表示该资源的一个组件被更新锁锁定了。只有在表或页级别才能获得这类锁

IX

Intent exclusive

表示该资源的一个组件被独占锁锁定了。只有在表或页级别才能获得这类锁

SIX

Shared with intent exclusive

表示一个正持有共享锁的资源还有一个组件(一页或一行)被独占锁锁定了

SIU

Shared with intent Update

表示一个正持有共享锁的资源还有一个组件(一页或一行)被更新锁锁定了

UIX

Update with intent exclusive

表示一个正持有更新锁的资源还有一个组件(一页或一行)被独占锁锁定了

Sch-S

Schema stability

表示一个使用该表的查询正在被编译

Sch-M

Schema modification

表示表的结构正在被修改

BU

Bulk Update

在一个大容量复制操作将数据导入表中并且(手动或自动)应用了TABLOCK查

询提示时使用

2) 锁粒度

sql SERVER 可以在表、页、行等级别锁定用户的数据资源即非系统资源(系统资源是用闩锁来保护的)。此外sql SERVER 还可以锁定索引键和索引键范围。

通过sys.dm_tran_locks视图可以查看谁被锁定了(如行,键,页)、锁的模式以及特定资源的标志符。基于sys.dm_tran_locks视图创建如下视图用于查看锁定的资源以及锁模式(通过这个视图可以查看事务锁定的表、页、行以及加在数据资源上的锁类型)。

CREATE VIEW dblocks AS 
SELECT request_session_id AS spid,DB_NAME(resource_database_id) AS dbname,CASE WHEN resource_type='object' 
THEN OBJECT_NAME(resource_associated_entity_id) 
WHEN resource_associated_entity_id=0 THEN 'n/a' 
ELSE OBJECT_NAME(p.object_id) END AS entity_name,index_id,resource_type AS RESOURCE,resource_description AS DESCRIPTION,request_mode AS mode,request_status AS STATUS 
FROM sys.dm_tran_locks t LEFT JOIN sys.partitions p ON p.partition_id=t.resource_associated_entity_id 
WHERE resource_database_id=DB_ID()

3) 如何跟踪死锁

通过选择sql server profiler 事件中的如下选项就可以跟踪到死锁产生的相关语句。

4) 死锁案例分析

在该案例中process65db88,process1d0045948为语句1的进程,process629dc8 为语句2的进程; 语句2获取了1689766页上的更新锁,在等待1686247页上的更新锁;而语句1则获取了1686247页上的更新锁在等待1689766页上的更新锁,两个语句等待的资源形成了一个环路,造成死锁。

5) 如何解决死锁

针对如上死锁案例,分析其对应语句执行计划如下:

通过执行计划可以看出,在查找需要更新的数据时使用的是索引扫描,比较耗费性能,这样就造成锁定资源时间过长,增加了语句并发执行时产生死锁的概率。

处理方式:

1. 在表上建立一个聚集索引。

2. 对语句更新的相关字段建立包含索引。

优化后该语句执行计划如下:

优化后的执行计划使用了索引查找,将大幅提升该查询语句的性能,降低了锁定资源的时间,同时也减少了锁定资源的范围,这样就降低了锁资源循环等待事件发生的概率,对于预防死锁的发生会有一定的作用。

死锁是无法完全避免的,但如果应用程序适当处理死锁,对涉及的任何用户及系统其余部分的影响可降至最低(适当处理是指发生错误1205时,应用程序重新提交批处理,第二次尝试大多能成功。一个进程被杀死,它的事务被取消,它的锁被释放,死锁中涉及到的另一个进程就可以完成它的工作并释放锁,所以就不具备产生另一个死锁的条件了。)

四、 如何预防死锁

阻止死锁的途径就是避免满足死锁条件的情况发生,为此我们在开发的过程中需要遵循如下原则:

1.尽量避免并发的执行涉及到修改数据的语句。

2.要求每一个事务一次就将所有要使用到的数据全部加锁,否则就不允许执行。

3.预先规定一个加锁顺序,所有的事务都必须按照这个顺序对数据执行封锁。如不同的过程在事务内部对对象的更新执行顺序应尽量保证一致。

4.每个事务的执行时间不可太长,对程序段的事务可考虑将其分割为几个事务。在事务中不要求输入,应该在事务之前得到输入,然后快速执行事务。

5.使用尽可能低的隔离级别。

6.数据存储空间离散法。该方法是指采用各种手段,将逻辑上在一个表中的数据分散的若干离散的空间上去,以便改善对表的访问性能。主要通过将大表按行或者列分解为若干小表,或者按照不同的用户群两种方法实现。

7.编写应用程序,让进程持有锁的时间尽可能短,这样其它进程就不必花太长的时间等待锁被释放。

今天关于sqlserver 死锁原因及解决方法sqlserver死锁的原因的讲解已经结束,谢谢您的阅读,如果想了解更多关于I2C 死锁原因及解决方法、I2C死锁原因及解决方法(转)、Java中多线程详解(2)产生死锁原因及解决方法、MSSQL产生死锁的根本原因及解决方法的相关知识,请在本站搜索。

本文标签: