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

来源:互联网 发布:gom引擎插件源码 编辑:程序博客网 时间:2024/05/16 06:05

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

错误抽象出来如下:

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

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

[sql] view plaincopy
  1. --查看所有锁表的语句  
  2. use master;  
  3. go  
  4. if not exists(select * from sysobjects where name = '#LockDetails' and xtype='U')  
  5.     begin  
  6.     CREATE Table #LockDetails  
  7.     (  
  8.     [EventType] varchar(2000),  
  9.     [Parameters] int,  
  10.     EventInfo nvarchar(2000)  
  11.     );  
  12.     end  
  13. go  
  14. DECLARE crsr Cursor FOR  
  15. SELECT spid blk FROM (  
  16.     select distinct resource_database_id dbid,  
  17.     resource_associated_entity_id objid,  
  18.     request_mode lockmode,  
  19.     request_session_id spid,  
  20.     b.name from master.sys.dm_tran_locks a  
  21.     left join  master.sys.sysdatabases b on a.resource_database_id=b.dbid  
  22.     where resource_associated_entity_id<>0   
  23. ) a;  
  24. DECLARE @blk int;  
  25. --游标开始  
  26. open crsr;  
  27. FETCH NEXT FROM crsr INTO @blk;  
  28. WHILE (@@FETCH_STATUS=0)  
  29. BEGIN;  
  30.     INSERT INTO #LockDetails exec('dbcc inputbuffer('+@blk+')')  
  31.     FETCH NEXT FROM crsr INTO @blk;  
  32. END;  
  33. close crsr;  
  34. DEALLOCATE crsr;  
  35. --游标结束  
  36. select distinct * from #LockDetails;  
  37. 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
0 0
原创粉丝点击