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

【表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补充:防止事务死锁最好的方法,还是大家推荐的用存储过程,在存储过程里面使用事务(只不过门槛稍高,手工活儿稍多)

posted @ 2008-04-24 14:24 Kai.Ma 阅读(...) 评论(...) 编辑 收藏