SQL Server中解決死鎖的新方法介紹和C#中多線程避免並發


C#程序中避免並發控制: 

1.多線程的工作隊列 

http://www.cnblogs.com/miniwiki/archive/2010/07/09/1774583.html 

 2.SmartThreadPool的QueueWorkItem,方法http://www.lingdonge.com/csharp/33.html

 

http://support.microsoft.com/kb/832524/zh-cn

 

SQL Server中解決死鎖的新方法介紹

數據庫 操作的死鎖是不可避免的,本文並不打算討論死鎖如何產生,重點在於解決死鎖,通過SQL Server2005, 現在似乎有了一種新的解決辦法。

將下面的SQL語句放在兩個不同的連接里面,並且在5秒內同時執行,將會發生死鎖。

 

use Northwindbegin tran  insert into Orders(CustomerId) values(@#ALFKI@#)  waitfor delay @#00:00:05@#  select * from Orders where CustomerId = @#ALFKI@#commitprint @#end tran@#

SQL Server對付死鎖的辦法是犧牲掉其中的一個,拋出異常,並且回滾事務。在SQL Server 2000,語句一旦發生異常,T-SQL將不會繼續運行,上面被犧牲的連接中, print @#end tran@#語句將不會被運行,所以我們很難在SQL Server 2000的T-SQL中對死鎖進行進一步的處理。

現在不同了,SQL Server 2005可以在T-SQL中對異常進行捕獲,這樣就給我們提供了一條處理死鎖的途徑:

下面利用的try ... catch來解決死鎖。

 

SET XACT_ABORT ONdeclare @r intset @r = 1while @r <= 3begin  begin tran    begin try       insert into Orders(CustomerId) values(@#ALFKI@#)    waitfor delay @#00:00:05@#    select * from Orders where CustomerId = @#ALFKI@#        commit    break  end try      begin catch    rollback    waitfor delay @#00:00:03@#    set @r = @r + 1    continue  end catchend

解決方法當然就是重試,但捕獲錯誤是前提。rollback后面的waitfor不可少,發生沖突后需要等待一段時間,@retry數目可以調整以應付不同的要求。

但是現在又面臨一個新的問題 : 錯誤被掩蓋了,一但問題發生並且超過3次,異常卻不會被拋出。SQL Server 2005 有一個RaiseError語句,可以拋出異常,但卻不能直接拋出原來的異常,所以需要重新定義發生的錯誤,現在,解決方案 變成了這樣:

 

declare @r intset @r = 1while @r <= 3begin  begin tran    begin try       insert into Orders(CustomerId) values(@#ALFKI@#)    waitfor delay @#00:00:05@#    select * from Orders where CustomerId = @#ALFKI@#        commit    break  end try      begin catch    rollback    waitfor delay @#00:00:03@#    set @r = @r + 1    continue  end catchendif ERROR_NUMBER() <> 0begin  declare @ErrorMessage nvarchar(4000);  declare @ErrorSeverity int;  declare @ErrorState int;  select    @ErrorMessage = ERROR_MESSAGE(),    @ErrorSeverity = ERROR_SEVERITY(),    @ErrorState = ERROR_STATE();  raiserror (@ErrorMessage,        @ErrorSeverity,        @ErrorState        );end

我希望將來SQL Server 2005能夠直接拋出原有異常,比如提供一個無參數的RaiseError。

因此方案有點臃腫,但將死鎖問題封裝到T-SQL中有助於明確職責,提高高層系統的清晰度。現在,對於DataAccess 的代碼,或許再也不需要考慮死鎖問題了 

========================================================================

SQL Server2000中死鎖經驗總結

雖然不能完全避免死鎖,但可以使死鎖的數量減至最少。將死鎖減至最少可以增加事務的吞吐量並減少系統開銷,因為只有很少的事務:
  • 回滾,而回滾會取消事務執行的所有工作。
  • 由於死鎖時回滾而由應用程序重新提交。
下列方法有助於最大限度地降低死鎖:
  • 按同一順序訪問對象。
  • 避免事務中的用戶交互。
  • 保持事務簡短並在一個批處理中。
  • 使用低隔離級別。
  • 使用綁定連接。
按同一順序訪問對象
如果所有並發事務按同一順序訪問對象,則發生死鎖的可能性會降低。例如,如果兩個並發事務獲得 Supplier 表上的鎖,然后獲得 Part 表上的鎖,則在其中一個事務完成之前,另一個事務被阻塞在 Supplier 表上。第一個事務提交或回滾后,第二個事務繼續進行。不發生死鎖。將存儲過程用於所有的數據修改可以標准化訪問對象的順序。
避免事務中的用戶交互
避免編寫包含用戶交互的事務,因為運行沒有用戶交互的批處理的速度要遠遠快於用戶手動響應查詢的速度,例如答復應用程序請求參數的提示。例如,如果事務正在等待用戶輸入,而用戶去吃午餐了或者甚至回家過周末了,則用戶將此事務掛起使之不能完成。這樣將降低系統的吞吐量,因為事務持有的任何鎖只有在事務提交或回滾時才會釋放。即使不出現死鎖的情況,訪問同一資源的其它事務也會被阻塞,等待該事務完成。
保持事務簡短並在一個批處理中
在同一數據庫中並發執行多個需要長時間運行的事務時通常發生死鎖。事務運行時間越長,其持有排它鎖或更新鎖的時間也就越長,從而堵塞了其它活動並可能導致死鎖。
保持事務在一個批處理中,可以最小化事務的網絡通信往返量,減少完成事務可能的延遲並釋放鎖。
使用低隔離級別
確定事務是否能在更低的隔離級別上運行。執行提交讀允許事務讀取另一個事務已讀取(未修改)的數據,而不必等待第一個事務完成。使用較低的隔離級別(例如提交讀)而不使用較高的隔離級別(例如可串行讀)可以縮短持有共享鎖的時間,從而降低了鎖定爭奪。
使用綁定連接
使用綁定連接使同一應用程序所打開的兩個或多個連接可以相互合作。次級連接所獲得的任何鎖可以象由主連接獲得的鎖那樣持有,反之亦然,因此不會相互阻塞
檢測死鎖
如果發生死鎖了,我們怎么去檢測具體發生死鎖的是哪條SQL語句或存儲過程?
這時我們可以使用以下存儲過程來檢測,就可以查出引起死鎖的進程和SQL語句。SQL Server自帶的系統存儲過程sp_who和sp_lock也可以用來查找阻塞和死鎖, 但沒有這里介紹的方法好用。
  use   master
 
go 
 
create   procedure   sp_who_lock
 
as 
 
begin 
 
declare   @spid   int  ,  @bl   int  ,
  
  @intTransactionCountOnEntry      int  ,
         
  @intRowcount        int  ,
         
  @intCountProperties       int  ,
         
  @intCounter        int 
 
  
  create   table   #tmp_lock_who (
  id 
  int   identity  (  1  ,  1  ),
  spid 
  smallint  ,
  bl 
  smallint  )
  
  
  IF   @@ERROR  <>  0   RETURN   @@ERROR 
  
  
  insert   into   #tmp_lock_who(spid,bl)   select      0   ,blocked
    
  from   (  select   *   from   sysprocesses   where    blocked  >  0   ) a 
    
  where   not   exists  (  select   *   from   (  select   *   from   sysprocesses   where    blocked  >  0   ) b 
    
  where   a.blocked  =  spid)
    
  union   select   spid,blocked   from   sysprocesses   where    blocked  >  0 
 
  
  IF   @@ERROR  <>  0   RETURN   @@ERROR   
   
 
--   找到臨時表的記錄數 
 
  select      @intCountProperties   =   Count  (  *  ),  @intCounter   =   1 
  
  from   #tmp_lock_who
  
  
  IF   @@ERROR  <>  0   RETURN   @@ERROR   
  
  
  if   @intCountProperties  =  0 
   
  select   '  現在沒有阻塞和死鎖信息  '   as   message
 
 
--   循環開始 
 
while   @intCounter   <=   @intCountProperties 
 
begin 
 
--   取第一條記錄 
 
    select      @spid   =   spid,  @bl   =   bl
   
  from   #tmp_lock_who   where   Id   =   @intCounter   
  
  begin 
   
  if   @spid   =  0   
             
  select   '  引起數據庫死鎖的是:   '  +   CAST  (  @bl   AS   VARCHAR  (  10  ))   +   '  進程號,其執行的SQL語法如下  '
  
  else 
             
  select   '  進程號SPID:  '  +   CAST  (  @spid   AS   VARCHAR  (  10  ))  +   '    '   +   '  進程號SPID:  '  +   CAST  ( @bl   AS   VARCHAR  (  10  ))   +  '  阻塞,其當前進程執行的SQL語法如下  ' 
  
  DBCC   INPUTBUFFER (  @bl   )
  
  end   
 
 
--   循環指針下移 
 
  set   @intCounter   =   @intCounter   +   1 
 
end 
 
 
drop   table   #tmp_lock_who
 
 
return   0 
 
end 
殺死鎖和進程
如何去手動的殺死進程和鎖?最簡單的辦法,重新啟動服務。但是這里要介紹一個存儲過程,通過顯式的調用,可以殺死進程和鎖。
  use   master
 
go 
 
 
if   exists   (  select   *   from   dbo.sysobjects   where   id   =   object_id  (N  '  [dbo].[p_killspid]  '    and  OBJECTPROPERTY  (id, N  '  IsProcedure  '    =   1  )
 
drop   procedure   [  dbo  ]  .  [  p_killspid  ] 
 
GO 
 
 
create   proc   p_killspid
 
@dbname   varchar  (  200  )      --  要關閉進程的數據庫名 
 
as    
     
  declare   @sql      nvarchar  (  500  )  
     
  declare   @spid   nvarchar  (  20  )
 
     
  declare   #tb   cursor   for 
         
  select   spid  =  cast  (spid   as   varchar  (  20  ))   from   master..sysprocesses   where   dbid  =  db_id  (  @dbname  )
     
  open   #tb
     
  fetch   next   from   #tb   into   @spid 
     
  while   @@fetch_status  =  0 
     
  begin    
         
  exec  (  '  kill   '  +  @spid  )
         
  fetch   next   from   #tb   into   @spid 
     
  end    
     
  close   #tb
     
  deallocate   #tb
 
go 
 
 
--  用法   
 
exec   p_killspid    '  newdbpy  ' 
查看鎖信息
如何查看系統中所有鎖的詳細信息?在企業管理管理器中,我們可以看到一些進程和鎖的信息,這里介紹另外一種方法。
  --  查看鎖信息 
 
create   table   #t(req_spid   int  ,obj_name sysname)
 
 
declare   @s   nvarchar  (  4000  )
     ,
  @rid   int  ,  @dbname   sysname,  @id   int  ,  @objname   sysname
 
 
declare   tb   cursor   for   
     
  select   distinct   req_spid,dbname  =  db_name  (rsc_dbid),rsc_objid
     
  from   master..syslockinfo   where   rsc_type   in  (  4  ,  5  )
 
open   tb
 
fetch   next   from   tb   into   @rid  ,  @dbname  ,  @id 
 
while   @@fetch_status  =  0 
 
begin 
     
  set   @s  =  '  select @objname=name from [  '  +  @dbname  +  '  ]..sysobjects where id=@id  ' 
     
  exec   sp_executesql   @s  ,N  '  @objname sysname out,@id int  '  ,  @objname   out,  @id 
     
  insert   into   #t   values  (  @rid  ,  @objname  )
     
  fetch   next   from   tb   into   @rid  ,  @dbname  ,  @id 
 
end 
 
close   tb
 
deallocate   tb
 
 
select   進程id  =  a.req_spid
     ,數據庫
  =  db_name  (rsc_dbid)
     ,類型
  =  case   rsc_type   when   1   then   '  NULL 資源(未使用)  ' 
         
  when   2   then   '  數據庫  ' 
         
  when   3   then   '  文件  ' 
         
  when   4   then   '  索引  ' 
         
  when   5   then   '    ' 
         
  when   6   then   '    ' 
         
  when   7   then   '    ' 
         
  when   8   then   '  擴展盤區  ' 
         
  when   9   then   '  RID(行 ID)  ' 
         
  when   10   then   '  應用程序  ' 
     
  end 
     ,對象id
  =  rsc_objid
     ,對象名
  =  b.obj_name
     ,rsc_indid
  
  from   master..syslockinfo a   left   join   #t b   on   a.req_spid  =  b.req_spid
 
 
go 
 
drop   table   #t
總結
雖然不能完全避免死鎖,但我們可以將死鎖減至最少,並通過一定的方法來檢測死鎖。
-------------------------------------------------------------------------------------------------
                                                 SQL Server死鎖的分析
SQL Server數據庫發生死鎖時不會像ORACLE那樣自動生成一個跟蹤文件。有時可以在[管理]->[當前活動] 里看到阻塞信息(有時SQL Server企業管理器會因為鎖太多而沒有響應).

  設定跟蹤1204:

USE MASTER
DBCC TRACEON (
 1204 , - 1 )

  顯示當前啟用的所有跟蹤標記的狀態:

DBCC TRACESTATUS( - 1 )

  取消跟蹤1204:

DBCC TRACEOFF ( 1204 , - 1 )

  在設定跟蹤1204后,會在數據庫的日志文件里顯示SQL Server數據庫死鎖時一些信息。但那些信息很難看懂,需要對照SQL Server聯機叢書仔細來看。根據PAG鎖要找到相關數據庫表的方法:

DBCC TRACEON ( 3604 )
DBCC PAGE (db_id,file_id,page_no)
DBCC TRACEOFF (
 3604 )

  請參考sqlservercentral.com上更詳細的講解.但又從CSDN學到了一個找到死鎖原因的方法。我稍加修改, 去掉了游標操作並增加了一些提示信息,寫了一個系統存儲過程sp_who_lock.sql。代碼如下:

if exists (select * from dbo.sysobjects
where id 
= object_id(N ' [dbo].[sp_who_lock] ' )
and OBJECTPROPERTY(id, N
 ' IsProcedure ' = 1 )
drop procedure [dbo].[sp_who_lock]
GO
/* *******************************************************
//  學習到並改寫
//  說明 : 查看數據庫里阻塞和死鎖情況
*******************************************************
 */ 
use master
go
create procedure sp_who_lock
as 
begin
declare @spid 
int ,@bl int ,
@intTransactionCountOnEntry     
int ,
@intRowcount             
int ,
@intCountProperties         
int ,
@intCounter             
int 
create table #tmp_lock_who (
id 
int identity( 1 , 1 ),
spid smallint,
bl smallint)
IF @@ERROR
 <> 0 RETURN @@ERROR
insert into #tmp_lock_who(spid,bl) select  
0 ,blocked
from (select 
* from sysprocesses where  blocked > 0 ) a
where not exists(select 
* from (select * from sysprocesses
where  blocked
 > 0 ) b
where a.blocked
 = spid)
union select spid,blocked from sysprocesses where  blocked
 > 0 
IF @@ERROR
 <> 0 RETURN @@ERROR
-- 找到臨時表的記錄數
select     @intCountProperties 
= Count( * ),@intCounter = 1 
from #tmp_lock_who
IF @@ERROR
 <> 0 RETURN @@ERROR
if     @intCountProperties = 0 
select 
' 現在沒有阻塞和死鎖信息 ' as message
-- 循環開始
while @intCounter <= @intCountProperties
begin
-- 取第一條記錄
select     @spid 
= spid,@bl = bl
from #tmp_lock_who where Id 
= @intCounter
begin
if @spid = 0 
select 
' 引起數據庫死鎖的是: ' + CAST(@bl AS VARCHAR( 10 ))
+ ' 進程號,其執行的SQL語法如下 ' 
else 
select 
' 進程號SPID: ' + CAST(@spid AS VARCHAR( 10 )) + '  ' 
+ ' 進程號SPID: ' + CAST(@bl AS VARCHAR( 10 )) + ' 阻塞,其當前進程執行的SQL語法如下 ' 
DBCC INPUTBUFFER (@bl )
end
-- 循環指針下移
set @intCounter = @intCounter + 1 
end
drop table #tmp_lock_who
return 0 
end

  需要的時候直接調用:

sp_who_lock

  就可以查出引起死鎖的進程和SQL語句.

  SQL Server自帶的系統存儲過程sp_who和sp_lock也可以用來查找阻塞和死鎖, 但沒有這里介紹的方法好用。如果想知道其它tracenum參數的含義,請看http://www.sqlservercentral.com/ 文章

  我們還可以設置鎖的超時時間(單位是毫秒), 來縮短死鎖可能影響的時間范圍:

  例如:

use master
seelct @@lock_timeout
set lock_timeout 900000 
-- 15分鍾
seelct @@lock_timeout

其實所有的死鎖最深層的原因就是一個:資源競爭
表現一:
    一個用戶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 ....
 

如何將數據庫中被鎖表解鎖 
 
作者:佚名    文章來源:未知    點擊數:106    更新時間:2005-12-25 
我們在操作數據庫的時候,有時候會由於操作不當引起數據庫表被鎖定,這么我們經常不知所措,不知怎么給這些表解鎖,在pl/sql Developer工具的的菜單“tools”里面的“sessions”可以查詢現在存在的會話,但是我們很難找到那個會話被鎖定了,想找到所以被鎖的會話就更難了,下面這叫查詢語句可以查詢出所以被鎖的會話。如下: 
SELECT   sn.username, m.SID,sn.SERIAL#, m.TYPE,
         DECODE (m.lmode,
                 0, 'None',
                 1, 'Null',
                 2, 'Row Share',
                 3, 'Row Excl.',
                 4, 'Share',
                 5, 'S/Row Excl.',
                 6, 'Exclusive',
                 lmode, LTRIM (TO_CHAR (lmode, '990'))
                ) lmode,
         DECODE (m.request,
                 0, 'None',
                 1, 'Null',
                 2, 'Row Share',
                 3, 'Row Excl.',
                 4, 'Share',
                 5, 'S/Row Excl.',
                 6, 'Exclusive',
                 request, LTRIM (TO_CHAR (m.request, '990'))
                ) request,
         m.id1, m.id2
    FROM v$session sn, v$lock m
   WHERE (sn.SID = m.SID AND m.request != 0)         --存在鎖請求,即被阻塞
      OR (    sn.SID = m.SID                         --不存在鎖請求,但是鎖定的對象被其他會話請求鎖定
          AND m.request = 0
          AND lmode != 4
          AND (id1, id2) IN (
                        SELECT s.id1, s.id2
                          FROM v$lock s
                         WHERE request != 0 AND s.id1 = m.id1
                               AND s.id2 = m.id2)
         )
ORDER BY id1, id2, m.request;

通過以上查詢知道了sid和 SERIAL#就可以開殺了
   alter system kill session 'sid,SERIAL#';

=======================================================


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM