“死鎖”的定義:
當某組資源的兩個或多個線程之間有循環相關性時,將發生死鎖。
這里面有一個關鍵字需要注意——“循環”。也就是說,真正意義上的“死鎖”必然存在著循環引用。A需要的資源B正使用中,因此A不能提交;然而B也在等待著A提交以便釋放B所需要的資源,才會發生“死鎖”。其實這種循環引用不只是在關系數據庫管理系統中存在,操作系統中也同樣存在。而今天我們要討論的不是這種“死鎖”,而是“阻塞”。 也就是當一個事務鎖定了另一個事務需要的資源,第二個事務等待鎖被釋放,這種情況下,第二個事務是被“阻塞”了而不是形成了“死鎖”,但由于這種情況下,第二個以上的事務無法正常繼續運行,類似于“死鎖”的狀態,必然影響了程序正常運行。這時,如果打開SQL Server的企業管理器,依次展開服務器節點的“管理”-“當前活動”-“鎖/進程 ID”可以看到阻塞和被阻塞的進程上有醒目的紅色標記。這時,除非執行第一個事務的程序退出,否則第二個事務一直處于等待狀態,形同死機。我們的系統遇到的“死鎖”一般屬于這種情況。
根據上面的描述,我們不難理解“阻塞”的原因。不妨做個比喻,如果一個人(事務)要去上廁所,廁所里有一定數量的馬桶(表或其它資源),這個人上廁所占用了一個馬桶,這就是“鎖”定了一個“表”,那么后進來的人肯定不能使用該馬桶,除非等到這個人離開。如果這個人一直長時間不離開(有點可笑,但確實很多時候是由我們自己的失誤造成),那必然導致后續一系列矛盾沖突的發生了。由此可見,“鎖”是正常的,數據庫的機制決定了要保證數據的完整性就必然產生鎖定和釋放的現象,鎖并不可怕,鎖定表并沒有問題,但如果鎖定資源一直不釋放,那就有可能產生阻塞(甚至產生死鎖)。
阻塞是如何造成的
(1)程序的漏洞。在PB編程中,我們一般通過數據窗口與數據庫交互,有時也會直接使用嵌入式SQL語句直接操作數據庫。數據窗口執行了Update方法后,我們必須根據其返回值來決定是否進行提交(Commit)或回滾(Rollback)才能真正結束這個事務,釋放操作所鎖定的表或記錄。這是很明顯的道理,但在很多情況下,我們還是會犯這種似乎是不應該犯的錯誤。比如:
a.寫錯了事務對象。本來是應該提交A事務對象,寫成了B或者忘記寫(不寫PB會將其默認成SQLCA事務對象)。由于我們的518系統牽涉到三個事務對象(ERP_SYS_MESSAGE、ERP_SET_MESSAGE、SQLCA),因此如果不注意的話就會犯這種錯誤。
b.在函數嵌套情況下忽略了事務處理,或對IF判斷語句路徑的處理不嚴密。例如,在主程序中,我們調用了一個函數,此函數中有對數據庫進行Update的操作,依程序員本意來講,他沒有在函數中做提交或回滾,是希望在主程序中統一進行提交。不幸的是,主程序中卻是根據數據窗口是否有更新來決定是否提交的,如果數據窗口有更新情況下,Update,成功則提交,失敗則回滾。這似乎沒有問題,然而如果用戶一進來直接就執行主程序,并未對數據窗口操作呢?這樣自然在調用函數中執行了語句,鎖定了表,由于后面判斷數據窗口并未更改,因此也沒有提交或回滾操作。這是比較奇怪的鎖表現象之一。
c.函數阻斷執行問題。這也是比較常見的、初學者易犯的錯誤之一?;厩闆r是:判斷SQLCA的返回值是否為-1,如果為-1表示出錯,這時,如果不掌握事務的知識,就會寫一個MessageBox函數,提示用戶相關出錯信息,然后Rollback……一般情況下這不會出現問題,關鍵問題是,MessageBox是一個隔離型的函數,所謂隔離型是說如果Messagebox這個函數調出的對話框沒有被響應,那么它就會一直阻止程序向下執行。如果正好這個操作是一個耗時較長的操作(如物料需求運算),而用戶又離開了計算機,不能及時響應這個對話框。系統就會一直處于等待狀態,而不會回滾。相關的表資源也就一直處于占用狀態。再有第二個事務進入時,阻塞產生。
(2)SQL Server本身對鎖處理的問題。說到這里要談一下鎖的“粒度”。SQL Server具有多粒度鎖定,允許一個事務鎖定不同類型的資源。為了使鎖定的成本減至最少,SQL Server自動將資源鎖定在適合任務的級別。鎖定在較小的粒度(例如行)可以增加并發但需要較大的開銷,因為如果鎖定了許多行,則需要控制更多的鎖。鎖定在較大的粒度(例如表)就并發而言是相當昂貴的,因為鎖定整個表限制了其它事務對表中任意部分進行訪問,但要求的開銷較低,因為需要維護的鎖較少。按照粒度增加順序依次為:RID(行標識符)、鍵、頁、擴展盤區、表、DB。一般情況下,使用SQL語句讀取數據時(或使用數據窗口的Retrieve方法提取數據),用到的是頁級或行級鎖。也就是說它讀完一頁就會釋放再讀下一頁。但如果對一些數據量比較大的表,出現的鎖比較多,SQL Server會自動升級為表級鎖,對整個表進行鎖定。這也沒什么問題。關鍵是在有些情況下(目前不知道是哪個版本解決的),SQL Server會升級表鎖而用完不釋放!這就會產生問題,遇到這種情況,可考慮給SQL Server打SP4補丁,經試驗打上Sp4后不再出現讀取數據鎖表問題。
(3)隔離級別設置問題。事務準備接收不一致數據的級別稱為隔離級別。隔離級別是一個事務必須與其它事務進行隔離的程度。較低的隔離級別可以增加并發,但代價是降低數據的正確性。相反,較高的隔離級別可以確保數據的正確性,但可能對并發產生負面影響。應用程序要求的隔離級別確定了SQL Server使用的鎖定行為。SQL-92定義了“未提交讀”、“提交讀”、“可重復讀”、“可串行讀”四種隔離級別,SQL Server支持這四種級別(關于這些隔離級別的論述參考SQL Server的聯機叢書),并默認為“提交讀”。在這種隔離級別下,正常的讀取操作是不會鎖定表的,但是如果在SQLCA的Lock屬性中指定了更嚴格的隔離級別,就可能導致在讀取過程中一直鎖定整個表造成其它事務的等待(有時這也是必須的)。在Select語句后使用Holdlock關鍵字也會顯式地指定數據庫在讀取數據期間鎖定整個表。默認情況下,SQLCA的Lock屬性不用修改,但如果懷疑是這個屬性出了問題,可在PB中調試時檢驗Lock屬性值是不是被修改。(Lock=‘RC’是針對SQL Server的默認值)。
上文中總結了程序中可能遇到的幾種造成資源占用和阻塞的情況。對這些情況有了清晰的了解后我們就很容易進行防范了。首先還是編程的邏輯要嚴謹,避免一時疏忽造成的程序錯誤;其次,采用統一的編程風格、養成良好的編程習慣也有助于發現和解決鎖表問題。最后就是SQL Server數據庫的版本及事務對象的隔離級別設置(不太常見,但數據量較大時應當多加留意)。
新聞熱點
疑難解答