最近很多小伙伴都在问sqlserver死锁原因及解决方法和sqlserver死锁的原因这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展I2C死锁原因及解决方法、I2C死锁原因及解决
最近很多小伙伴都在问sqlserver 死锁原因及解决方法和sqlserver死锁的原因这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展I2C 死锁原因及解决方法、I2C死锁原因及解决方法(转)、Java中多线程详解(2)产生死锁原因及解决方法、MSSQL产生死锁的根本原因及解决方法等相关知识,下面开始了哦!
本文目录一览:- sqlserver 死锁原因及解决方法(sqlserver死锁的原因)
- I2C 死锁原因及解决方法
- I2C死锁原因及解决方法(转)
- Java中多线程详解(2)产生死锁原因及解决方法
- MSSQL产生死锁的根本原因及解决方法
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,使得已提交读事务使用行版本控制。
. 使用快照隔离。
. 使用绑定连接。
其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。
如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。
通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。
动作描述 | 使用聚集索引 | 使用非聚集索引 |
列经常被分组排序 | 应 | 应 |
返回某范围内的数据 | 应 | 不应 |
一个或极少不同值 | 不应 | 不应 |
小数目的不同值 | 应 | 不应 |
大数目的不同值 | 不应 | 应 |
频繁更新的列 | 不应 | 应 |
外键列 | 应 | 应 |
主键列 | 应 | 应 |
频繁修改索引列 | 不应 | 应 |
通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,sql SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。
显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。
从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。
在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。
通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。
在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):
where fariqi> dateadd(day,-90,getdate())
set @d=getdate()
事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。
从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。
很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列):
where fariqi>''2004-1-1'' and fariqi<''2004-6-6''用时:3280毫秒
where fariqi>''2004-1-1'' order by fariqi用时:6390毫秒
where fariqi<''2004-1-1'' order by fariqi用时:6453毫秒
所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。
当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。
思想基本一致的,总结下来,对日期建立聚集索引比较合适。
还有一有关索引性能的MS的文章:使用 SQL Server 2000 索引视图提高性能
I2C 死锁原因及解决方法

原因
当单片机正在和 I2C 从机通信,如果主正好发生打算发第 9 个时钟,此时 SCL 为高,而从开始拉低 SDA 为低做准备(作为 ACK 信号),等待主 SCL 变低后,从再释放 SDA 为高。如果此时正好单片机复位,主 SCL 还没来得及变低,直接变成高电平,此时从还在等待 SCL 变低,所以一直拉低 SDA;而主由于复位,发现 SDA 一直为低,也在等待从释放 SDA 为高。因此主从都进入一个相互等待的死锁状态。
解决办法
- 最好的方法是采用模拟 i2c. 但由于已经配置成硬件 i2c, 程序改为上电或复位改成发 9 个 SCL 时钟信号,使从好释放 SDA。
- 尽量选用带复位输人的 I2C 从器件。
- 将所有的从 I2C 设备的电源连接在一起,通过 MOS 管连接到主电源,而 MOS 管的导通关断由 I2C 主设备来实现。
- 在 I2C 从设备设计看门狗的功能。
- 在 I2C 主设备中增加 I2C 总线恢复程序。每次 I2C 主设备复位后,如果检测到 SDA 数据线被拉低,则控制 I2C 中的 SCL 时钟线产生 9 个时钟脉冲 (针对 8 位数据的情况),这样 I2C 从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。 这种方法有很大的局限性,因为大部分主设备的 I2C 模块由内置的硬件电路来实现,软件并不能够直接控制 SCL 信号模拟 产生需要时钟脉冲。
- 在 I2C 总线上增加一个额外的总线恢复设备。这个设备监视 I2C 总线。当设备检测到 SDA 信号被拉低超过指定时间 时,就在 SCL 总线上产生 9 个时钟脉冲,使 I2C 从设备完成读操作,从死锁状态上恢复出来。总线恢复设备需要有具有编程 功能,一般可以用单片机或 CPLD 实现这一功能。
- 在 I2C 上串人一个具有死锁恢复的 I2C 缓冲器,如 Linear 公司的 LTC4307:LTC4307 是一个双向的 I2C 总线缓冲器,并且具有 I2C 总线死锁恢复的功能。LTC4307 总线输人侧连接主设备,总线输出侧连接所有从设备。当 LTC4307 检测到输出侧 SDA 或 SCL 信号被拉低 30ms 时,就自动断开 I2C 总线输人侧与输出侧的连接。并且在输出侧 SCL 信号上产生 16 个时钟脉冲来释放总线。当总线成功恢复后,LTC4307 会再次连接输人输出侧,使总线能够正常工作。
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)产生死锁原因及解决方法
一、什么是死锁
死锁理解起来很简单,就是一个字,堵,下面图中拥堵的十字路口就可以看做一个死锁的状态,四个方向的车都要往前走,但是十字路口只有一个,只能允许一个方向的车通过后,才能让另一个方向的车通过。
在多线程中,四个方向的车流就可以看做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产生死锁的根本原因及解决方法
一、 什么是死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程.
二、 死锁产生的四个必要条件
•互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放
•请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放
•不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放
•环路等待条件:指在发生死锁时,必然存在一个进程――资源的环形链,即进程集合{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产生死锁的根本原因及解决方法的相关知识,请在本站搜索。
本文标签: