代码逻辑死锁导致数据库超时

本文讨论了在数据库操作中遇到的锁等待超时问题,并通过代码示例展示了如何解决这一问题。通过分析代码逻辑,我们发现了新功能引入的逻辑死锁,并通过检查数据库锁表状态找到了根本原因。文章最后探讨了使用`with(nolock)`或`with(readpast)`的可行性,并得出了不适用结论。

又是很久没记录工作点滴了,年后就忙着找新东家了,总算尘埃落定。不料,刚交接出去的系统就出问题了,系统老是超时。两天前看测试环境报错还没在意,因为什么代码也没改而且正式系统上也没问题,再加上公司权限系统切换有问题,大家什么异常第一个就想到权限系统。今天发现正式库上也报错了,一直报超时。于是查问题,最终结果是交接的人新加了个功能,代码中人为照成锁等待超时(在函数中前面加事务,后面没加事务,后面没加事务的等待锁释放,而事务等待执行完提交,于是造成等待超时)。

错误抽象出来如下:

public static void Test()
{
    using (SqlConnection myConnection = DBHelper.MyConnection)
    {
        myConnection.Open();
        SqlTransaction _varTrans = myConnection.BeginTransaction();
        try
        {
            InsertA(_varTrans);
            UpdateA(_varTrans);
            SelectA();
            _varTrans.Commit();
        }
        catch (Exception ex)
        {
            _varTrans.Rollback();
            Console.WriteLine(ex.ToString());
        }
    }
}
运行症状,每次执行这个方法都会卡在SelectA那里,直到数据库超时、报异常、释放锁。原因是因为在插入和更新时该事务会把表锁住不让其他进程进来访问,这时SelectA也就访问不了这个表(因为它不属于那个事务,不是锁的拥有者),只能等待那个事务提交然后释放锁,然而只要SelectA不执行,那个事务就提交不了,照成逻辑上的死锁(不是数据库的死锁,因为等待到超时时就会报异常,自然解除锁表)。所以每当执行这个方法时,所有操作这个表的程序都会报错(我们程序中,对于操作失败会每隔三分钟再去尝试,所以就导致这个表几乎废了,还好有缓存机制,否则全线瘫痪)。
查询方法:每次一卡,首先看所有的锁:exec sp_lock,查看ObjId不为0的,根据dbcc inputbuffer(spid)--spid为所有锁中的spid字段,看到执行的SQL语句为EventInfo字段,找到相应的代码。到代码中一找,原来刚接手项目的哥们新加了一个功能,断点调试,发现运行到后面的无事务查询卡住了,导致逻辑死锁,卡住的这段时间到数据库执行查询也是超时,遂以为这就是数据库的死锁。查看相关的资料发现,这种只是逻辑等待超时。

问题是解决了,然后就想可不可以用with(nolock)或者是with(readpast)的,经过业务考虑不适合;
进一步思考哪些情况会导致锁表等待,经过调查,个人从表象认为插入操作时锁主键索引,更新时锁定行,删除时锁主键索引。(具体测试代码见源码
查询数据库锁表的语句如下:

--查看所有锁表的语句
use master;
go
if not exists(select * from sysobjects where name = '#LockDetails' and xtype='U')
	begin
	CREATE Table #LockDetails
	(
	[EventType] varchar(2000),
	[Parameters] int,
	EventInfo nvarchar(2000)
	);
	end
go
DECLARE crsr Cursor FOR
SELECT spid blk FROM (
	select distinct resource_database_id dbid,
	resource_associated_entity_id objid,
	request_mode lockmode,
	request_session_id spid,
	b.name from master.sys.dm_tran_locks a
	left join  master.sys.sysdatabases b on a.resource_database_id=b.dbid
	where resource_associated_entity_id<>0 
) a;
DECLARE @blk int;
--游标开始
open crsr;
FETCH NEXT FROM crsr INTO @blk;
WHILE (@@FETCH_STATUS=0)
BEGIN;
	INSERT INTO #LockDetails exec('dbcc inputbuffer('+@blk+')')
	FETCH NEXT FROM crsr INTO @blk;
END;
close crsr;
DEALLOCATE crsr;
--游标结束
select distinct * from #LockDetails;
DROP Table #LockDetails;

源码下载

参考:

SQL Server死锁总结 :http://www.cnblogs.com/happyhippy/archive/2008/11/14/1333922.html
msdn的sp_lock :http://msdn.microsoft.com/zh-cn/library/ms187749.aspx
msdn的sys.dm_tran_locks :http://msdn.microsoft.com/zh-cn/library/ms190345.aspx
msdn的DBCC INPUTBUFFER:http://msdn.microsoft.com/zh-cn/library/ms187730(v=SQL.105).aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值