產(chǎn)生死鎖的四個(gè)必要條件:
(1) 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用种蘸。
(2) 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)孙技,對(duì)已獲得的資源保持不放。
(3) 不剝奪條件:進(jìn)程已獲得的資源忍饰,在末使用完之前卡辰,不能強(qiáng)行剝奪蛆橡。
(4) 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系钠右。
二?鎖的分類(lèi)
鎖的類(lèi)別有兩種分法:
1.?從數(shù)據(jù)庫(kù)系統(tǒng)的角度來(lái)看:分為獨(dú)占鎖(即排它鎖)赋元,共享鎖和更新鎖
MS-SQL?Server?使用以下資源鎖模式。
鎖模式?描述
共享?(S) :讀鎖爬舰,用于不更改或不更新數(shù)據(jù)的操作(只讀操作)们陆,如?SELECT?語(yǔ)句寒瓦。
更新?(U) :(介于共享和排它鎖之間)情屹,可以讓其他程序在不加鎖的條件下讀,但本程序可以隨時(shí)更改杂腰。
讀取表時(shí)使用更新鎖垃你,而不使用共享鎖,并將鎖一直保留到語(yǔ)句或事務(wù)的結(jié)束喂很。UPDLOCK 的優(yōu)點(diǎn)是允許您讀取數(shù)據(jù)(不阻塞其它事務(wù))并在以后更新數(shù)據(jù)惜颇,同時(shí)確保自從上次讀取數(shù)據(jù)后數(shù)據(jù)沒(méi)有被更改。當(dāng)我們用UPDLOCK來(lái)讀取記錄時(shí)可以對(duì)取到的記錄加上更新鎖少辣,從而加上鎖的記錄在其它的線(xiàn)程中是不能更改的只能等本線(xiàn)程的事務(wù)結(jié)束后才能更改凌摄,我如下示例:
BEGIN TRANSACTION --開(kāi)始一個(gè)事務(wù)
SELECT Qty
FROM myTable WITH (UPDLOCK)
WHERE Idin(1,2,3)
UPDATE myTable SET Qty= Qty -A.Qty
FROM myTable? AS A
INNER JOIN? @_Table AS B ON A.ID=B.ID
COMMIT TRANSACTION--提交事務(wù)
這樣在更新時(shí)其它的線(xiàn)程或事務(wù)在這些語(yǔ)句執(zhí)行完成前是不能更改ID是1,2漓帅,3的記錄的.其它的都可以修改和讀锨亏,1,2忙干,3的只能讀器予,要是修改的話(huà)只能等這些語(yǔ)句完成后才能操作.從而保證的數(shù)據(jù)的修改正確.
排它?(X):寫(xiě)鎖。 用于數(shù)據(jù)修改操作捐迫,例如?INSERT乾翔、UPDATE?或?DELETE。確保不會(huì)同時(shí)同一資源進(jìn)行多重更新施戴。
意向鎖?用于建立鎖的層次結(jié)構(gòu)反浓。意向鎖的類(lèi)型為:意向共享?(IS)、意向排它?(IX)?以及與意向排它共享?(SIX)赞哗。
架構(gòu)鎖?在執(zhí)行依賴(lài)于表架構(gòu)的操作時(shí)使用雷则。架構(gòu)鎖的類(lèi)型為:架構(gòu)修改?(Sch-M)?和架構(gòu)穩(wěn)定性?(Sch-S)。
大容量更新?(BU)?向表中大容量復(fù)制數(shù)據(jù)并指定了?TABLOCK?提示時(shí)使用懈玻。
共享鎖
共享?(S)?鎖允許并發(fā)事務(wù)讀取?(SELECT)?一個(gè)資源寸莫。資源上存在共享?(S)?鎖時(shí),任何其它事務(wù)都不能修改數(shù)據(jù)狗准。一旦已經(jīng)讀取數(shù)據(jù)踢械,便立即釋放資源上的共享?(S)?鎖,除非將事務(wù)隔離級(jí)別設(shè)置為可重復(fù)讀或更高級(jí)別飒炎,或者在事務(wù)生存周期內(nèi)用鎖定提示保留共享?(S)?鎖。
更新鎖
更新?(U)?鎖可以防止通常形式的死鎖。一般更新模式由一個(gè)事務(wù)組成诅妹,此事務(wù)讀取記錄,獲取資源(頁(yè)或行)的共享?(S)?鎖毅人,然后修改行吭狡,此操作要求鎖轉(zhuǎn)換為排它?(X)?鎖。如果兩個(gè)事務(wù)獲得了資源上的共享模式鎖丈莺,然后試圖同時(shí)更新數(shù)據(jù)划煮,則一個(gè)事務(wù)嘗試將鎖轉(zhuǎn)換為排它?(X)?鎖。共享模式到排它鎖的轉(zhuǎn)換必須等待一段時(shí)間缔俄,因?yàn)橐粋€(gè)事務(wù)的排它鎖與其它事務(wù)的共享模式鎖不兼容弛秋;發(fā)生鎖等待。第二個(gè)事務(wù)試圖獲取排它?(X)?鎖以進(jìn)行更新俐载。由于兩個(gè)事務(wù)都要轉(zhuǎn)換為排它?(X)?鎖蟹略,并且每個(gè)事務(wù)都等待另一個(gè)事務(wù)釋放共享模式鎖,因此發(fā)生死鎖遏佣。
若要避免這種潛在的死鎖問(wèn)題挖炬,請(qǐng)使用更新?(U)?鎖。一次只有一個(gè)事務(wù)可以獲得資源的更新?(U)?鎖状婶。如果事務(wù)修改資源意敛,則更新?(U)?鎖轉(zhuǎn)換為排它?(X)?鎖。否則太抓,鎖轉(zhuǎn)換為共享鎖空闲。
排它鎖
排它?(X)?鎖可以防止并發(fā)事務(wù)對(duì)資源進(jìn)行訪(fǎng)問(wèn)。其它事務(wù)不能讀取或修改排它?(X)?鎖鎖定的數(shù)據(jù)走敌。
意向鎖
意向鎖表示?SQL?Server?需要在層次結(jié)構(gòu)中的某些底層資源上獲取共享?(S)?鎖或排它?(X)?鎖碴倾。例如,放置在表級(jí)的共享意向鎖表示事務(wù)打算在表中的頁(yè)或行上放置共享?(S)?鎖掉丽。在表級(jí)設(shè)置意向鎖可防止另一個(gè)事務(wù)隨后在包含那一頁(yè)的表上獲取排它?(X)?鎖跌榔。意向鎖可以提高性能,因?yàn)?SQL?Server?僅在表級(jí)檢查意向鎖來(lái)確定事務(wù)是否可以安全地獲取該表上的鎖捶障。而無(wú)須檢查表中的每行或每頁(yè)上的鎖以確定事務(wù)是否可以鎖定整個(gè)表僧须。
意向鎖包括意向共享?(IS)、意向排它?(IX)?以及與意向排它共享?(SIX)项炼。
死鎖原理
根據(jù)操作系統(tǒng)中的定義:死鎖是指在一組進(jìn)程中的各個(gè)進(jìn)程均占有不會(huì)釋放的資源担平,但因互相申請(qǐng)被其他進(jìn)程所站用不會(huì)釋放的資源而處于的一種永久等待狀態(tài)示绊。
死鎖的四個(gè)必要條件:
互斥條件(Mutual exclusion):資源不能被共享,只能由一個(gè)進(jìn)程使用暂论。
請(qǐng)求與保持條件(Hold and wait):已經(jīng)得到資源的進(jìn)程可以再次申請(qǐng)新的資源面褐。
非剝奪條件(No pre-emption):已經(jīng)分配的資源不能從相應(yīng)的進(jìn)程中被強(qiáng)制地剝奪。
循環(huán)等待條件(Circular wait):系統(tǒng)中若干進(jìn)程組成環(huán)路取胎,該環(huán)路中每個(gè)進(jìn)程都在等待相鄰進(jìn)程正占用的資源展哭。
對(duì)應(yīng)到SQL Server中,當(dāng)在兩個(gè)或多個(gè)任務(wù)中闻蛀,如果每個(gè)任務(wù)鎖定了其他任務(wù)試圖鎖定的資源匪傍,此時(shí)會(huì)造成這些任務(wù)永久阻塞,從而出現(xiàn)死鎖觉痛;這些資源可能是:?jiǎn)涡?RID役衡,堆中的單行)、索引中的鍵(KEY秧饮,行鎖)映挂、頁(yè)(PAG,8KB)盗尸、區(qū)結(jié)構(gòu)(EXT,連續(xù)的8頁(yè))帽撑、堆或B樹(shù)(HOBT) 泼各、表(TAB,包括數(shù)據(jù)和索引)亏拉、文件(File扣蜻,數(shù)據(jù)庫(kù)文件)、應(yīng)用程序?qū)S觅Y源(APP)及塘、元數(shù)據(jù)(METADATA)莽使、分配單元(Allocation_Unit)、整個(gè)數(shù)據(jù)庫(kù)(DB)笙僚。一個(gè)死鎖示例如下圖所示:
說(shuō)明:T1芳肌、T2表示兩個(gè)任務(wù);R1和R2表示兩個(gè)資源肋层;由資源指向任務(wù)的箭頭(如R1->T1亿笤,R2->T2)表示該資源被改任務(wù)所持有;由任務(wù)指向資源的箭頭(如T1->S2栋猖,T2->S1)表示該任務(wù)正在請(qǐng)求對(duì)應(yīng)目標(biāo)資源净薛;
其滿(mǎn)足上面死鎖的四個(gè)必要條件:
(1).互斥:資源S1和S2不能被共享,同一時(shí)間只能由一個(gè)任務(wù)使用蒲拉;
(2).請(qǐng)求與保持條件:T1持有S1的同時(shí)肃拜,請(qǐng)求S2痴腌;T2持有S2的同時(shí)請(qǐng)求S1;
(3).非剝奪條件:T1無(wú)法從T2上剝奪S2燃领,T2也無(wú)法從T1上剝奪S1衷掷;
(4).循環(huán)等待條件:上圖中的箭頭構(gòu)成環(huán)路,存在循環(huán)等待柿菩。
2.死鎖排查
(1). 使用SQL Server的系統(tǒng)存儲(chǔ)過(guò)程sp_who和sp_lock戚嗅,可以查看當(dāng)前數(shù)據(jù)庫(kù)中的鎖情況;進(jìn)而根據(jù)objectID(@objID)(SQL Server 2005)/ object_name(@objID)(Sql Server 2000)可以查看哪個(gè)資源被鎖枢舶,用dbcc ld(@blk)懦胞,可以查看最后一條發(fā)生給SQL Server的Sql語(yǔ)句;
CREATE?Table?#Who(spid?int,
ecid?int,
status?nvarchar(50),
loginname?nvarchar(50),
hostname?nvarchar(50),
blk?int,
dbname?nvarchar(50),
cmd?nvarchar(50),
request_ID?int);
CREATE?Table?#Lock(spid?int,
dpid?int,
objid?int,
indld?int,
[Type]?nvarchar(20),
Resource?nvarchar(50),
Mode?nvarchar(10),
Status?nvarchar(10)
);
INSERT?INTO?#Who
EXEC?sp_who?active??--看哪個(gè)引起的阻塞凉泄,blk
INSERT?INTO?#Lock
EXEC?sp_lock??--看鎖住了那個(gè)資源id躏尉,objid
DECLARE?@DBName?nvarchar(20);
SET?@DBName='NameOfDataBase'
SELECT?#Who.*?FROM?#Who?WHERE?dbname=@DBName
SELECT?#Lock.*?FROM?#Lock
JOIN?#Who
ON?#Who.spid=#Lock.spid
AND?dbname=@DBName;
--最后發(fā)送到SQL?Server的語(yǔ)句
DECLARE?crsr?Cursor?FOR
SELECT?blk?FROM?#Who?WHERE?dbname=@DBName?AND?blk<>0;
DECLARE?@blk?int;
open?crsr;
FETCH?NEXT?FROM?crsr?INTO?@blk;
WHILE?(@@FETCH_STATUS=0)
BEGIN;
dbcc?inputbuffer(@blk);
FETCH?NEXT?FROM?crsr?INTO?@blk;
END;
close?crsr;
DEALLOCATE?crsr;
--鎖定的資源
SELECT?#Who.spid,hostname,objid,[type],mode,object_name(objid)?as?objName?FROM?#Lock
JOIN?#Who
ON?#Who.spid=#Lock.spid
AND?dbname=@DBName
WHERE?objid<>0;
DROP?Table?#Who;
DROP?Table?#Lock;
(2). 使用 SQL Server Profiler 分析死鎖: 將 Deadlock graph 事件類(lèi)添加到跟蹤。此事件類(lèi)使用死鎖涉及到的進(jìn)程和對(duì)象的 XML 數(shù)據(jù)填充跟蹤中的 TextData 數(shù)據(jù)列后众。SQL Server 事件探查器?可以將 XML 文檔提取到死鎖 XML (.xdl) 文件中胀糜,以后可在 SQL Server Management Studio 中查看該文件。
3.避免死鎖
上面1中列出了死鎖的四個(gè)必要條件蒂誉,我們只要想辦法破其中的任意一個(gè)或多個(gè)條件教藻,就可以避免死鎖發(fā)生,一般有以下幾種方法(FROM Sql Server 2005聯(lián)機(jī)叢書(shū)):
(1).按同一順序訪(fǎng)問(wèn)對(duì)象右锨。(注:避免出現(xiàn)循環(huán))
(2).避免事務(wù)中的用戶(hù)交互括堤。(注:減少持有資源的時(shí)間,較少鎖競(jìng)爭(zhēng))
(3).保持事務(wù)簡(jiǎn)短并處于一個(gè)批處理中绍移。(注:同(2)悄窃,減少持有資源的時(shí)間)
(4).使用較低的隔離級(jí)別。(注:使用較低的隔離級(jí)別(例如已提交讀)比使用較高的隔離級(jí)別(例如可序列化)持有共享鎖的時(shí)間更短蹂窖,減少鎖競(jìng)爭(zhēng))
(5).使用基于行版本控制的隔離級(jí)別:2005中支持快照事務(wù)隔離和指定READ_COMMITTED隔離級(jí)別的事務(wù)使用行版本控制轧抗,可以將讀與寫(xiě)操作之間發(fā)生的死鎖幾率降至最低:
SET ALLOW_SNAPSHOT_ISOLATION ON --事務(wù)可以指定 SNAPSHOT 事務(wù)隔離級(jí)別;
SET READ_COMMITTED_SNAPSHOT ON? --指定 READ_COMMITTED 隔離級(jí)別的事務(wù)將使用行版本控制而不是鎖定。默認(rèn)情況下(沒(méi)有開(kāi)啟此選項(xiàng)瞬测,沒(méi)有加with nolock提示)横媚,SELECT語(yǔ)句會(huì)對(duì)請(qǐng)求的資源加S鎖(共享鎖);而開(kāi)啟了此選項(xiàng)后涣楷,SELECT不會(huì)對(duì)請(qǐng)求的資源加S鎖分唾。
注意:設(shè)置 READ_COMMITTED_SNAPSHOT 選項(xiàng)時(shí),數(shù)據(jù)庫(kù)中只允許存在執(zhí)行 ALTER DATABASE 命令的連接狮斗。在 ALTER DATABASE 完成之前绽乔,數(shù)據(jù)庫(kù)中決不能有其他打開(kāi)的連接。數(shù)據(jù)庫(kù)不必一定要處于單用戶(hù)模式中碳褒。
(6).使用綁定連接折砸。(注:綁定會(huì)話(huà)有利于在同一臺(tái)服務(wù)器上的多個(gè)會(huì)話(huà)之間協(xié)調(diào)操作看疗。綁定會(huì)話(huà)允許一個(gè)或多個(gè)會(huì)話(huà)共享相同的事務(wù)和鎖(但每個(gè)回話(huà)保留其自己的事務(wù)隔離級(jí)別),并可以使用同一數(shù)據(jù)睦授,而不會(huì)有鎖沖突两芳。可以從同一個(gè)應(yīng)用程序內(nèi)的多個(gè)會(huì)話(huà)中創(chuàng)建綁定會(huì)話(huà)去枷,也可以從包含不同會(huì)話(huà)的多個(gè)應(yīng)用程序中創(chuàng)建綁定會(huì)話(huà)怖辆。在一個(gè)會(huì)話(huà)中開(kāi)啟事務(wù)(begin tran)后,調(diào)用exec sp_getbindtoken @Token out;來(lái)取得Token删顶,然后傳入另一個(gè)會(huì)話(huà)并執(zhí)行EXEC sp_bindsession @Token來(lái)進(jìn)行綁定(最后的示例中演示了綁定連接)竖螃。
4.死鎖處理方法:
(1). 根據(jù)2中提供的sql,查看那個(gè)spid處于wait狀態(tài)逗余,然后用kill spid來(lái)干掉(即破壞死鎖的第四個(gè)必要條件:循環(huán)等待)特咆;當(dāng)然這只是一種臨時(shí)解決方案,我們總不能在遇到死鎖就在用戶(hù)的生產(chǎn)環(huán)境上排查死鎖录粱、Kill sp腻格,我們應(yīng)該考慮如何去避免死鎖。
(2). 使用SET LOCK_TIMEOUT timeout_period(單位為毫秒)來(lái)設(shè)定鎖請(qǐng)求超時(shí)啥繁。默認(rèn)情況下菜职,數(shù)據(jù)庫(kù)沒(méi)有超時(shí)期限(timeout_period值為-1,可以用SELECT @@LOCK_TIMEOUT來(lái)查看該值输虱,即無(wú)限期等待)些楣。當(dāng)請(qǐng)求鎖超過(guò)timeout_period時(shí),將返回錯(cuò)誤宪睹。timeout_period值為0時(shí)表示根本不等待,一遇到鎖就返回消息蚕钦。設(shè)置鎖請(qǐng)求超時(shí)亭病,破環(huán)了死鎖的第二個(gè)必要條件(請(qǐng)求與保持條件)。
服務(wù)器:?消息?1222嘶居,級(jí)別?16罪帖,狀態(tài)?50,行?1
已超過(guò)了鎖請(qǐng)求超時(shí)時(shí)段邮屁。
(3). SQL Server內(nèi)部有一個(gè)鎖監(jiān)視器線(xiàn)程執(zhí)行死鎖檢查整袁,鎖監(jiān)視器對(duì)特定線(xiàn)程啟動(dòng)死鎖搜索時(shí),會(huì)標(biāo)識(shí)線(xiàn)程正在等待的資源佑吝;然后查找特定資源的所有者坐昙,并遞歸地繼續(xù)執(zhí)行對(duì)那些線(xiàn)程的死鎖搜索,直到找到一個(gè)構(gòu)成死鎖條件的循環(huán)芋忿。檢測(cè)到死鎖后炸客,數(shù)據(jù)庫(kù)引擎?選擇運(yùn)行回滾開(kāi)銷(xiāo)最小的事務(wù)的會(huì)話(huà)作為死鎖犧牲品疾棵,返回1205 錯(cuò)誤,回滾死鎖犧牲品的事務(wù)并釋放該事務(wù)持有的所有鎖痹仙,使其他線(xiàn)程的事務(wù)可以請(qǐng)求資源并繼續(xù)運(yùn)行是尔。
5.兩個(gè)死鎖示例及解決方法
5.1 SQL死鎖
(1).測(cè)試用的基礎(chǔ)數(shù)據(jù):
CREATE?TABLE?Lock1(C1?int?default(0));
CREATE?TABLE?Lock2(C1?int?default(0));
INSERT?INTO?Lock1?VALUES(1);
INSERT?INTO?Lock2?VALUES(1);
(2).開(kāi)兩個(gè)查詢(xún)窗口,分別執(zhí)行下面兩段sql
--Query?1
Begin?Tran
Update?Lock1?Set?C1=C1+1;
WaitFor?Delay?'00:01:00';
SELECT?*?FROM?Lock2
Rollback?Tran;
--Query?2
Begin?Tran
Update?Lock2?Set?C1=C1+1;
WaitFor?Delay?'00:01:00';
SELECT?*?FROM?Lock1
Rollback?Tran;
上面的SQL中有一句WaitFor Delay '00:01:00'开仰,用于等待1分鐘拟枚,以方便查看鎖的情況。
(3).查看鎖情況
在執(zhí)行上面的WaitFor語(yǔ)句期間众弓,執(zhí)行第二節(jié)中提供的語(yǔ)句來(lái)查看鎖信息:
Query1中恩溅,持有Lock1中第一行(表中只有一行數(shù)據(jù))的行排他鎖(RID:X),并持有該行所在頁(yè)的意向更新鎖(PAG:IX)田轧、該表的意向更新鎖(TAB:IX)暴匠;Query2中,持有Lock2中第一行(表中只有一行數(shù)據(jù))的行排他鎖(RID:X)傻粘,并持有該行所在頁(yè)的意向更新鎖(PAG:IX)每窖、該表的意向更新鎖(TAB:IX);
執(zhí)行完Waitfor弦悉,Query1查詢(xún)Lock2窒典,請(qǐng)求在資源上加S鎖,但該行已經(jīng)被Query2加上了X鎖稽莉;Query2查詢(xún)Lock1瀑志,請(qǐng)求在資源上加S鎖,但該行已經(jīng)被Query1加上了X鎖污秆;于是兩個(gè)查詢(xún)持有資源并互不相讓?zhuān)瑯?gòu)成死鎖劈猪。
(4).解決辦法
a).SQL Server自動(dòng)選擇一條SQL作死鎖犧牲品:運(yùn)行完上面的兩個(gè)查詢(xún)后,我們會(huì)發(fā)現(xiàn)有一條SQL能正常執(zhí)行完畢良拼,而另一個(gè)SQL則報(bào)如下錯(cuò)誤:
服務(wù)器:?消息?1205战得,級(jí)別?13,狀態(tài)?50庸推,行?1
事務(wù)(進(jìn)程?ID??xx)與另一個(gè)進(jìn)程已被死鎖在??lock?資源上常侦,且該事務(wù)已被選作死鎖犧牲品。請(qǐng)重新運(yùn)行該事務(wù)贬媒。
這就是上面第四節(jié)中介紹的鎖監(jiān)視器干活了聋亡。
b).按同一順序訪(fǎng)問(wèn)對(duì)象:顛倒任意一條SQL中的Update與SELECT語(yǔ)句的順序。例如修改第二條SQL成如下:
--Query2
Begin?Tran
SELECT?*?FROM?Lock1--在Lock1上申請(qǐng)S鎖
WaitFor?Delay?'00:01:00';
Update?Lock2?Set?C1=C1+1;--Lock2:RID:X
Rollback?Tran;
當(dāng)然這樣修改也是有代價(jià)的际乘,這會(huì)導(dǎo)致第一條SQL執(zhí)行完畢之前坡倔,第二條SQL一直處于阻塞狀態(tài)。單獨(dú)執(zhí)行Query1或Query2需要約1分鐘,但如果開(kāi)始執(zhí)行Query1時(shí)致讥,馬上同時(shí)執(zhí)行Query2仅仆,則Query2需要2分鐘才能執(zhí)行完;這種按順序請(qǐng)求資源從一定程度上降低了并發(fā)性垢袱。
c).SELECT語(yǔ)句加With(NoLock)提示:默認(rèn)情況下SELECT語(yǔ)句會(huì)對(duì)查詢(xún)到的資源加S鎖(共享鎖)墓拜,S鎖與X鎖(排他鎖)不兼容;但加上With(NoLock)后请契,SELECT不對(duì)查詢(xún)到的資源加鎖(或者加Sch-S鎖咳榜,Sch-S鎖可以與任何鎖兼容);從而可以是這兩條SQL可以并發(fā)地訪(fǎng)問(wèn)同一資源爽锥。當(dāng)然涌韩,此方法適合解決讀與寫(xiě)并發(fā)死鎖的情況,但加With(NoLock)可能會(huì)導(dǎo)致臟讀氯夷。
SELECT?*?FROM?Lock2?WITH(NOLock)
SELECT?*?FROM?Lock1?WITH(NOLock)
d).使用較低的隔離級(jí)別臣樱。SQL Server 2000支持四種事務(wù)處理隔離級(jí)別(TIL),分別為:READ UNCOMMITTED腮考、READ COMMITTED雇毫、REPEATABLE READ、SERIALIZABLE踩蔚;SQL Server 2005中增加了SNAPSHOT TIL棚放。默認(rèn)情況下,SQL Server使用READ COMMITTED TIL馅闽,我們可以在上面的兩條SQL前都加上一句SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED飘蚯,來(lái)降低TIL以避免死鎖;事實(shí)上福也,運(yùn)行在READ UNCOMMITTED TIL的事務(wù)局骤,其中的SELECT語(yǔ)句不對(duì)結(jié)果資源加鎖或加Sch-S鎖,而不會(huì)加S鎖暴凑;但還有一點(diǎn)需要注意的是:READ UNCOMMITTED TIL允許臟讀庄涡,雖然加上了降低TIL的語(yǔ)句后,上面兩條SQL在執(zhí)行過(guò)程中不會(huì)報(bào)錯(cuò)搬设,但執(zhí)行結(jié)果是一個(gè)返回1,一個(gè)返回2撕捍,即讀到了臟數(shù)據(jù)拿穴,也許這并不是我們所期望的。
e). 在SQL前加SET LOCK_TIMEOUT timeout_period忧风,當(dāng)請(qǐng)求鎖超過(guò)設(shè)定的timeout_period時(shí)間后默色,就會(huì)終止當(dāng)前SQL的執(zhí)行,犧牲自己狮腿,成全別人腿宰。
f).使用基于行版本控制的隔離級(jí)別(SQL Server 2005支持):開(kāi)啟下面的選項(xiàng)后呕诉,SELECT不會(huì)對(duì)請(qǐng)求的資源加S鎖,不加鎖或者加Sch-S鎖吃度,從而將讀與寫(xiě)操作之間發(fā)生的死鎖幾率降至最低甩挫;而且不會(huì)發(fā)生臟讀。啊
SET?ALLOW_SNAPSHOT_ISOLATION?ON
SET?READ_COMMITTED_SNAPSHOT?ON
g). 使用綁定連接(使用方法見(jiàn)下一個(gè)示例椿每。)
5.2程序死鎖(SQL阻塞)
看一個(gè)例子:一個(gè)典型的數(shù)據(jù)庫(kù)操作事務(wù)死鎖分析伊者,按照我自己的理解,我覺(jué)得這應(yīng)該算是C#程序中出現(xiàn)死鎖间护,而不是數(shù)據(jù)庫(kù)中的死鎖亦渗;下面的代碼模擬了該文中對(duì)數(shù)據(jù)庫(kù)的操作過(guò)程:
//略去的無(wú)關(guān)的code
SqlConnection?conn?=?new?SqlConnection(connectionString);
conn.Open();
SqlTransaction?tran?=?conn.BeginTransaction();
string?sql1?=?"Update?Lock1?SET?C1=C1+1";
string?sql2?=?"SELECT?*?FROM?Lock1";
ExecuteNonQuery(tran,?sql1);?//使用事務(wù):事務(wù)中Lock了Table
ExecuteNonQuery(null,?sql2);?//新開(kāi)一個(gè)connection來(lái)讀取Table
public?static?void?ExecuteNonQuery(SqlTransaction?tran,?string?sql)
{
SqlCommand?cmd?=?new?SqlCommand(sql);
if?(tran?!=?null)
{
cmd.Connection?=?tran.Connection;
cmd.Transaction?=?tran;
cmd.ExecuteNonQuery();
}
else
{
using?(SqlConnection?conn?=?new?SqlConnection(connectionString))
{
conn.Open();
cmd.Connection?=?conn;
cmd.ExecuteNonQuery();
}
}
}
執(zhí)行到ExecuteNonQuery(null, sql2)時(shí)拋出SQL執(zhí)行超時(shí)的異常,下圖從數(shù)據(jù)庫(kù)的角度來(lái)看該問(wèn)題:
代碼從上往下執(zhí)行汁尺,會(huì)話(huà)1持有了表Lock1的X鎖法精,且事務(wù)沒(méi)有結(jié)束,回話(huà)1就一直持有X鎖不釋放痴突;而會(huì)話(huà)2執(zhí)行select操作搂蜓,請(qǐng)求在表Lock1上加S鎖,但S鎖與X鎖是不兼容的苞也,所以回話(huà)2的被阻塞等待洛勉,不在等待中,就在等待中獲得資源如迟,就在等待中超時(shí)收毫。。殷勘。從中我們可以看到此再,里面并沒(méi)有出現(xiàn)死鎖,而只是SELECT操作被阻塞了玲销。也正因?yàn)椴皇菙?shù)據(jù)庫(kù)死鎖输拇,所以SQL Server的鎖監(jiān)視器無(wú)法檢測(cè)到死鎖。
我們?cè)購(gòu)腃#程序的角度來(lái)看該問(wèn)題:
C#程序持有了表Lock1上的X鎖贤斜,同時(shí)開(kāi)了另一個(gè)SqlConnection還想在該表上請(qǐng)求一把S鎖策吠,圖中已經(jīng)構(gòu)成了環(huán)路;太貪心了瘩绒,結(jié)果自己把自己給鎖死了猴抹。。锁荔。
雖然這不是一個(gè)數(shù)據(jù)庫(kù)死鎖蟀给,但卻是因?yàn)閿?shù)據(jù)庫(kù)資源而導(dǎo)致的死鎖,上例中提到的解決死鎖的方法在這里也基本適用,主要是避免讀操作被阻塞跋理,解決方法如下:
a).把SELECT放在Update語(yǔ)句前:SELECT不在事務(wù)中择克,且執(zhí)行完畢會(huì)釋放S鎖;
b).把SELECT也放加入到事務(wù)中:ExecuteNonQuery(tran, sql2);
c).SELECT加With(NOLock)提示:可能產(chǎn)生臟讀前普;
d).降低事務(wù)隔離級(jí)別:SELECT語(yǔ)句前加SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED肚邢;同上,可能產(chǎn)生臟讀汁政;
e).使用基于行版本控制的隔離級(jí)別(同上例)道偷。
g).使用綁定連接:取得事務(wù)所在會(huì)話(huà)的token,然后傳入新開(kāi)的connection中记劈;執(zhí)行EXEC sp_bindsession @Token后綁定了連接勺鸦,最后執(zhí)行exec sp_bindsession null;來(lái)取消綁定;最后需要注意的四點(diǎn)是:
(1). 使用了綁定連接的多個(gè)connection共享同一個(gè)事務(wù)和相同的鎖目木,但各自保留自己的事務(wù)隔離級(jí)別换途;
(2). 如果在sql3字符串的“exec sp_bindsession null”換成“commit tran”或者“rollback tran”,則會(huì)提交整個(gè)事務(wù)刽射,最后一行C#代碼tran.Commit()就可以不用執(zhí)行了(執(zhí)行會(huì)報(bào)錯(cuò)军拟,因?yàn)槭聞?wù)已經(jīng)結(jié)束了-,-)。
(3). 開(kāi)啟事務(wù)(begin tran)后誓禁,才可以調(diào)用exec sp_getbindtoken @Token out來(lái)取得Token懈息;如果不想再新開(kāi)的connection中結(jié)束掉原有的事務(wù),則在這個(gè)connection close之前摹恰,必須執(zhí)行“exec sp_bindsession null”來(lái)取消綁定連接辫继,或者在新開(kāi)的connectoin close之前先結(jié)束掉事務(wù)(commit/tran)。
(4). (Sql server 2005 聯(lián)機(jī)叢書(shū))后續(xù)版本的 Microsoft SQL Server 將刪除該功能俗慈。請(qǐng)避免在新的開(kāi)發(fā)工作中使用該功能姑宽,并著手修改當(dāng)前還在使用該功能的應(yīng)用程序。 請(qǐng)改用多個(gè)活動(dòng)結(jié)果集 (MARS) 或分布式事務(wù)闺阱。
tran?=?connection.BeginTransaction();
string?sql1?=?"Update?Lock1?SET?C1=C1+1";
ExecuteNonQuery(tran,?sql1);?//使用事務(wù):事務(wù)中Lock了測(cè)試表Lock1
string?sql2?=?@"DECLARE?@Token?varchar(255);
exec?sp_getbindtoken?@Token?out;
SELECT?@Token;";
string?token?=?ExecuteScalar(tran,?sql2).ToString();
string?sql3?=?"EXEC?sp_bindsession?@Token;Update?Lock1?SET?C1=C1+1;exec?sp_bindsession?null;";
SqlParameter?parameter?=?new?SqlParameter("@Token",?SqlDbType.VarChar);
parameter.Value?=?token;
ExecuteNonQuery(null,?sql3,?parameter);?//新開(kāi)一個(gè)connection來(lái)操作測(cè)試表Lock1
tran.Commit();