您的位置:首页 > 数据库

一个典型的数据库操作事务死锁分析

2016-02-02 10:16 309 查看
【表A】与【表B】之间有外键约束(具体怎么约束的无所谓,因为外键和事务死锁没有绝对关系)。【表A】=主键表,【表B】=外键表。

using(事务)

{

try{

for(

)//一个循环

{

if(查询【表A】有该【记录】==false)//这个查询没有用当前事务的数据库连接,而是新开一个数据库连接查询数据库

{

将【记录】插入【表A】;

插入【表B】;

}

}

事务.提交();

}catch{

事务.回滚();

}

}

4月25日早9:00补充:以上代码逻辑如果放在一个存储过程里面,是没有问题的。但是放在C#中,程序员稍不注意,就很可能出现问题。出现问题的关键地方在“查询【表A】”。C#中,如果查询【表A】和当前的事务不是同一个数据库连接,而是新开一个连接,就会死锁(可以看留言中我的回复)。所以:要么

1,查询【表A】改为使用当前事务的数据库连接(推荐用这种方法)

2,要么按下面的进行代码改造,将事务移到循环体内。

当循环只有1次的时候,上面代码运行没有问题。

一旦循环大于1次的时候,死锁立即出现,运行SQL Server 2005的sp_who_lock,发现死锁的地方正是“查询【表A】”这块。为什么呢?

因为:第1次循环,插入【表A】后,事务将【表A】设置了独占锁,但是第1次循环完后,事务并没有提交,也就没有解开【表A】上的独占锁。因为表A被独占锁了,所以第2次循环时,“查询【表A】”这个操作进行不下去(后面的插入【表A】更是如此),一直在等待事务提交以解开锁,但是事务运行到第2次循环的查询【表A】就死了,循环无法继续进行,也就不能运行到循环外边的事务.提交(),【表A】的独占锁永远没法解开。死锁就这样产生了。

既然知道了死锁的原因是因为循环里面没有解开独占锁,所以我们应该把事务.提交()放置在循环内。另外根据事务体的逻辑尽量少的原则,我们把事务的声明移植循环体内,使事务体的代码行数尽量少。代码如下。

for(

)//一个循环

{

bool 有记录=查询【表A】有该【记录】;//将这个查询移出事务是良好的编码习惯,虽然这里不必要

using(事务)

{

try{

if(有记录==false)

{

将【记录】插入【表A】;

插入【表B】;

}

事务.提交();

}catch{

事务.回滚();

}

}

}

经过以上改造之后,几位程序员写的业务运行正常。

由此总结:

1,在C#等程序代码中使用事务,并在事务内进行查询的时候,特别要小心,确保该查询和事务使用的是同一个数据库连接。防止表被独占死锁。

2,事务体内应尽可能少的逻辑,尽可能少的代码行数。

3,4月25日9:00补充:防止事务死锁最好的方法,还是大家推荐的用存储过程,在存储过程里面使用事务(只不过门槛稍高,手工活儿稍多)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: