:點(diǎn)晴OA工作流查詢待辦工作流時(shí),偶爾出現(xiàn)報(bào)錯(cuò)提示:事務(wù)(進(jìn)程ID)與另一個(gè)進(jìn)程已被死鎖在lock通信緩沖區(qū)資源上,并且已被選作死鎖犧牲品。請(qǐng)重新運(yùn)行該事務(wù),如何解決?
QQ圖片20161217144149.png)

出現(xiàn)這個(gè)錯(cuò)誤的原因是一個(gè)用戶訪問(wèn)該頁(yè)面,觸發(fā)了更改系統(tǒng)相關(guān)數(shù)據(jù)庫(kù)表的動(dòng)作(可能是系統(tǒng)后臺(tái)自動(dòng)刷新用戶已讀狀態(tài)、或者用戶提交數(shù)據(jù)等),為了確保數(shù)據(jù)庫(kù)表更新成功,系統(tǒng)會(huì)自動(dòng)鎖住該表不被其他用戶更改,這時(shí)候另外一個(gè)用戶也要訪問(wèn)該頁(yè)面進(jìn)行操作,也要更新數(shù)據(jù)庫(kù)表,此時(shí),因?yàn)閿?shù)據(jù)庫(kù)表已被同時(shí)訪問(wèn)的用戶鎖定了,就會(huì)導(dǎo)致本用戶無(wú)法更新,其結(jié)果就會(huì)報(bào)以上錯(cuò)誤。簡(jiǎn)言之,這是系統(tǒng)中該頁(yè)面同時(shí)訪問(wèn)用戶太多,產(chǎn)生了并發(fā)操作造成的。
其實(shí)所有的死鎖最深層的原因就是一個(gè):資源競(jìng)爭(zhēng),程序設(shè)計(jì)開(kāi)發(fā)時(shí)對(duì)大量用戶并發(fā)操作的處理考慮不周。
表現(xiàn)一:
一個(gè)用戶A 訪問(wèn)表A(鎖住了表A),然后又訪問(wèn)表B;
另一個(gè)用戶B 訪問(wèn)表B(鎖住了表B),然后企圖訪問(wèn)表A。
這時(shí)用戶A由于用戶B已經(jīng)鎖住表B,它必須等待用戶B釋放表B,才能繼續(xù),好了他老人家就只好老老實(shí)實(shí)在這等了;
同樣用戶B要等用戶A釋放表A才能繼續(xù)這就死鎖了。
解決方法:
這種死鎖是由于你的程序的BUG產(chǎn)生的,除了調(diào)整你的程序的邏輯別無(wú)他法,仔細(xì)分析你程序的邏輯:
1:盡量避免同時(shí)鎖定兩個(gè)資源;
2: 必須同時(shí)鎖定兩個(gè)資源時(shí),要保證在任何時(shí)刻都應(yīng)該按照相同的順序來(lái)鎖定資源。
表現(xiàn)二:
用戶A讀一條紀(jì)錄,然后修改該條紀(jì)錄;
這時(shí)用戶B修改該條紀(jì)錄。
這里用戶A的事務(wù)里鎖的性質(zhì)由共享鎖企圖上升到獨(dú)占鎖(for update),而用戶B里的獨(dú)占鎖由于A有共享鎖存在所以必須等A釋放掉共享鎖,而A由于B的獨(dú)占鎖而無(wú)法上升的獨(dú)占鎖也就不可能釋放共享鎖,于是出現(xiàn)了死鎖。
這種死鎖比較隱蔽,但其實(shí)在稍大點(diǎn)的項(xiàng)目中經(jīng)常發(fā)生。
解決方法:
讓用戶A的事務(wù)(即先讀后寫(xiě)類型的操作),在select 時(shí)就是用Update lock
語(yǔ)法如下:
select * from table1 with(updlock) where ....
==========================
在聯(lián)機(jī)事務(wù)處理(OLTP)的數(shù)據(jù)庫(kù)應(yīng)用系統(tǒng)中,多用戶、多任務(wù)的并發(fā)性是系統(tǒng)最重要的技術(shù)指標(biāo)之一。為了提高并發(fā)性,目前大部分RDBMS都采用加鎖技術(shù)。然而由于現(xiàn)實(shí)環(huán)境的復(fù)雜性,使用加鎖技術(shù)又不可避免地產(chǎn)生了死鎖問(wèn)題。因此如何合理有效地使用加鎖技術(shù),最小化死鎖是開(kāi)發(fā)聯(lián)機(jī)事務(wù)處理系統(tǒng)的關(guān)鍵。
死鎖產(chǎn)生的原因
在聯(lián)機(jī)事務(wù)處理系統(tǒng)中,造成死機(jī)主要有兩方面原因。一方面,由于多用戶、多任務(wù)的并發(fā)性和事務(wù)的完整性要求,當(dāng)多個(gè)事務(wù)處理對(duì)多個(gè)資源同時(shí)訪問(wèn)時(shí),若雙方已鎖定一部分資源但也都需要對(duì)方已鎖定的資源時(shí),無(wú)法在有限的時(shí)間內(nèi)完全獲得所需的資源,就會(huì)處于無(wú)限的等待狀態(tài),從而造成其對(duì)資源需求的死鎖。
另一方面,數(shù)據(jù)庫(kù)本身加鎖機(jī)制的實(shí)現(xiàn)方法不同,各數(shù)據(jù)庫(kù)系統(tǒng)也會(huì)產(chǎn)生其特殊的死鎖情況。如在Sybase SQL Server 11中,最小鎖為2K一頁(yè)的加鎖方法,而非行級(jí)鎖。如果某張表的記錄數(shù)少且記錄的長(zhǎng)度較短(即記錄密度高,如應(yīng)用系統(tǒng)中的系統(tǒng)配置表或系統(tǒng)參數(shù)表就屬于此類表),被訪問(wèn)的頻率高,就容易在該頁(yè)上產(chǎn)生死鎖。
幾種死鎖情況及解決方法
清算應(yīng)用系統(tǒng)中,容易發(fā)生死鎖的幾種情況如下:
● 不同的存儲(chǔ)過(guò)程、觸發(fā)器、動(dòng)態(tài)SQL語(yǔ)句段按照不同的順序同時(shí)訪問(wèn)多張表;
● 在交換期間添加記錄頻繁的表,但在該表上使用了非群集索引(non-clustered);
● 表中的記錄少,且單條記錄較短,被訪問(wèn)的頻率較高;
● 整張表被訪問(wèn)的頻率高(如代碼對(duì)照表的查詢等)。
以上死鎖情況的對(duì)應(yīng)處理方法如下:
● 在系統(tǒng)實(shí)現(xiàn)時(shí)應(yīng)規(guī)定所有存儲(chǔ)過(guò)程、觸發(fā)器、動(dòng)態(tài)SQL語(yǔ)句段中,對(duì)多張表的操作總是使用同一順序。如:有兩個(gè)存儲(chǔ)過(guò)程proc1、proc2,都需要訪問(wèn)三張表zltab、z2tab和z3tab,如果proc1按照zltab、z2tab和z3tab的順序進(jìn)行訪問(wèn),那么,proc2也應(yīng)該按照以上順序訪問(wèn)這三張表。
● 對(duì)在交換期間添加記錄頻繁的表,使用群集索引(clustered),以減少多個(gè)用戶添加記錄到該表的最后一頁(yè)上,在表尾產(chǎn)生熱點(diǎn),造成死鎖。這類表多為往來(lái)賬的流水表,其特點(diǎn)是在交換期間需要在表尾追加大量的記錄,并且對(duì)已添加的記錄不做或較少做刪除操作。
● 對(duì)單張表中記錄數(shù)不太多,且在交換期間select或updata較頻繁的表可使用設(shè)置每頁(yè)最大行的辦法,減少數(shù)據(jù)在表中存放的密度,模擬行級(jí)鎖,減少在該表上死鎖情況的發(fā)生。這類表多為信息繁雜且記錄條數(shù)少的表。
如:系統(tǒng)配置表或系統(tǒng)參數(shù)表。在定義該表時(shí)添加如下語(yǔ)句:
with max_rows_per_page=1
● 在存儲(chǔ)過(guò)程、觸發(fā)器、動(dòng)態(tài)SQL語(yǔ)句段中,若對(duì)某些整張表select操作較頻繁,則可能在該表上與其他訪問(wèn)該表的用戶產(chǎn)生死鎖。對(duì)于檢查賬號(hào)是否存在,但被檢查的字段在檢查期間不會(huì)被更新等非關(guān)鍵語(yǔ)句,可以采用在select命令中使用at isolation read uncommitted子句的方法解決。該方法實(shí)際上降低了select語(yǔ)句對(duì)整張表的鎖級(jí)別,提高了其他用戶對(duì)該表操作的并發(fā)性。在系統(tǒng)高負(fù)荷運(yùn)行時(shí),該方法的效果尤為顯著。
例如:
select*from titles at isolation read uncommitted
● 對(duì)流水號(hào)一類的順序數(shù)生成器字段,可以先執(zhí)行updata流水號(hào)字段+1,然后再執(zhí)行select獲取流水號(hào)的方法進(jìn)行操作。
小結(jié)
筆者對(duì)同城清算系統(tǒng)進(jìn)行壓力測(cè)試時(shí),分別對(duì)采用上述優(yōu)化方法和不采用優(yōu)化方法的兩套系統(tǒng)進(jìn)行測(cè)試。在其他條件相同的情況下,相同業(yè)務(wù)筆數(shù)、相同時(shí)間內(nèi),死鎖發(fā)生的情況如下:
采用優(yōu)化方法的系統(tǒng): 0次/萬(wàn)筆業(yè)務(wù);
不采用優(yōu)化方法的系統(tǒng):50~200次/萬(wàn)筆業(yè)務(wù)。
所以,使用上述優(yōu)化方法后,特別是在系統(tǒng)高負(fù)荷運(yùn)行時(shí)效果尤為顯著??傊?,在設(shè)計(jì)、開(kāi)發(fā)數(shù)據(jù)庫(kù)應(yīng)用系統(tǒng),尤其是OLTP系統(tǒng)時(shí),應(yīng)該根據(jù)應(yīng)用系統(tǒng)的具體情況,依據(jù)上述原則對(duì)系統(tǒng)分別優(yōu)化,為開(kāi)發(fā)一套高效、可靠的應(yīng)用系統(tǒng)打下良好的基礎(chǔ)。
============
--轉(zhuǎn)
/********************************************************
// 創(chuàng)建 :
// 日期 :
// 修改 :
//
// 說(shuō)明 : 查看數(shù)據(jù)庫(kù)里阻塞和死鎖情況
********************************************************/
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
-- 找到臨時(shí)表的記錄數(shù)
select @intCountProperties = Count(*),@intCounter = 1
from #tmp_lock_who
IF @@ERROR<>0 RETURN @@ERROR
if @intCountProperties=0
select ''現(xiàn)在沒(méi)有阻塞和死鎖信息'' as message
-- 循環(huán)開(kāi)始
while @intCounter <= @intCountProperties
begin
-- 取第一條記錄
select @spid = spid,@bl = bl
from #tmp_lock_who where Id = @intCounter
begin
if @spid =0
select ''引起數(shù)據(jù)庫(kù)死鎖的是: ''+ CAST(@bl AS VARCHAR(10))
+ ''進(jìn)程號(hào),其執(zhí)行的SQL語(yǔ)法如下''
else
select ''進(jìn)程號(hào)SPID:''+ CAST(@spid AS VARCHAR(10))+ ''被''
+ ''進(jìn)程號(hào)SPID:''+ CAST(@bl AS VARCHAR(10)) +''阻塞,其當(dāng)前進(jìn)程執(zhí)行的SQL語(yǔ)法如下''
DBCC INPUTBUFFER (@bl )
end
-- 循環(huán)指針下移
set @intCounter = @intCounter + 1
end
drop table #tmp_lock_who
return 0
end
GO
==========================
呵呵,解決死鎖,光查出來(lái)沒(méi)有多大用處,我原來(lái)也是用這個(gè)存儲(chǔ)過(guò)程來(lái)清理死鎖的
我解決死鎖的方式主要用了:
1 優(yōu)化索引
2 對(duì)所有的報(bào)表,非事務(wù)性的select 語(yǔ)句 在from 后都加了 with (nolock) 語(yǔ)句
3 對(duì)所有的事務(wù)性更新盡量使用相同的更新順序來(lái)執(zhí)行
現(xiàn)在已解決了死鎖的問(wèn)題,希望能對(duì)你有幫助
with (nolock)的用法很靈活 可以說(shuō)只要有 from的地方都可以加 with (nolock) 標(biāo)記來(lái)取消產(chǎn)生意象鎖,這里 可以用在 delete update,select 以及 inner join 后面的from里,對(duì)整個(gè)系統(tǒng)的性能提高都很有幫助
==========================
use master --必須在master數(shù)據(jù)庫(kù)中創(chuàng)建
go
if exists (select * from dbo.sysobjects where id = object_id(N [dbo].[p_lockinfo] ) and OBJECTPROPERTY(id, N IsProcedure ) = 1)
drop procedure [dbo].[p_lockinfo]
GO
/*--處理死鎖
查看當(dāng)前進(jìn)程,或死鎖進(jìn)程,并能自動(dòng)殺掉死進(jìn)程
因?yàn)槭轻槍?duì)死的,所以如果有死鎖進(jìn)程,只能查看死鎖進(jìn)程
當(dāng)然,你可以通過(guò)參數(shù)控制,不管有沒(méi)有死鎖,都只查看死鎖進(jìn)程
--鄒建 2004.4--*/
/*--調(diào)用示例
exec p_lockinfo
--*/
create proc p_lockinfo
@kill_lock_spid bit=1, --是否殺掉死鎖的進(jìn)程,1 殺掉, 0 僅顯示
@show_spid_if_nolock bit=1 --如果沒(méi)有死鎖的進(jìn)程,是否顯示正常進(jìn)程信息,1 顯示,0 不顯示
as
declare @count int,@s nvarchar(1000),@i int
select id=identity(int,1,1),標(biāo)志,
進(jìn)程ID=spid,線程ID=kpid,塊進(jìn)程ID=blocked,數(shù)據(jù)庫(kù)ID=dbid,
數(shù)據(jù)庫(kù)名=db_name(dbid),用戶ID=uid,用戶名=loginame,累計(jì)CPU時(shí)間=cpu,
登陸時(shí)間=login_time,打開(kāi)事務(wù)數(shù)=open_tran, 進(jìn)程狀態(tài)=status,
工作站名=hostname,應(yīng)用程序名=program_name,工作站進(jìn)程ID=hostprocess,
域名=nt_domain,網(wǎng)卡地址=net_address
into #t from(
select 標(biāo)志=''死鎖的進(jìn)程'',
spid,kpid,a.blocked,dbid,uid,loginame,cpu,login_time,open_tran,
status,hostname,program_name,hostprocess,nt_domain,net_address,
s1=a.spid,s2=0
from master..sysprocesses a join (
select blocked from master..sysprocesses group by blocked
)b on a.spid=b.blocked where a.blocked=0
union all
select ''│_犧牲品_>'',
spid,kpid,blocked,dbid,uid,loginame,cpu,login_time,open_tran,
status,hostname,program_name,hostprocess,nt_domain,net_address,
s1=blocked,s2=1
from master..sysprocesses a where blocked<>0
)a order by s1,s2
select @count=@@rowcount,@i=1
if @count=0 and @show_spid_if_nolock=1
begin
insert #t
select 標(biāo)志=''正常的進(jìn)程'',
spid,kpid,blocked,dbid,db_name(dbid),uid,loginame,cpu,login_time,
open_tran,status,hostname,program_name,hostprocess,nt_domain,net_address
from master..sysprocesses
set @count=@@rowcount
end
if @count>0
begin
create table #t1(id int identity(1,1),a nvarchar(30),b Int,EventInfo nvarchar(255))
if @kill_lock_spid=1
begin
declare @spid varchar(10),@標(biāo)志 varchar(10)
while @i<=@count
begin
select @spid=進(jìn)程ID,@標(biāo)志=標(biāo)志 from #t where id=@i
insert #t1 exec(''dbcc inputbuffer(''+@spid+'')'')
if @標(biāo)志=''死鎖的進(jìn)程'' exec(''kill ''+@spid)
set @i=@i+1
end
end
else
while @i<=@count
begin
select @s=''dbcc inputbuffer(''+cast(進(jìn)程ID as varchar)+'')'' from #t where id=@i
insert #t1 exec(@s)
set @i=@i+1
end
select a.*,進(jìn)程的SQL語(yǔ)句=b.EventInfo
from #t a join #t1 b on a.id=b.id
end
GO
該文章在 2016/12/17 14:53:43 編輯過(guò)