摘 要:文章給出了在民族古籍?dāng)?shù)字化保護系統(tǒng)操作中典型的并發(fā)事件(丟失更新)解決的兩種封鎖機制。在操作時,采用適當(dāng)?shù)姆怄i機制,鎖定需要修改的“行”,防止并發(fā)事件的產(chǎn)生,以保證數(shù)據(jù)庫的完整性和一致性。
關(guān)鍵詞:民族古籍?dāng)?shù)字化保護系統(tǒng);并發(fā)控制;悲觀封鎖;樂觀封鎖
0 引言
在民族古籍?dāng)?shù)字化保護系統(tǒng)的數(shù)據(jù)庫中,多個用戶程序(如查詢和著錄)可以并行地存取數(shù)據(jù)庫,如果不對并發(fā)操作進行控制,會出現(xiàn)存取不正確數(shù)據(jù),或破壞數(shù)據(jù)庫數(shù)據(jù)的一致性的問題。
1 并發(fā)事件的產(chǎn)生
數(shù)據(jù)庫是一個共享資源,可為多個應(yīng)用程序共享。這些應(yīng)用程序可以串行運行,但在許多情況下,可能多個程序或一個程序的多個進程并行地運行,這就是數(shù)據(jù)庫的并行操作。并發(fā)操作帶來的數(shù)據(jù)不一致性包括三類:丟失修改、不可重復(fù)讀和讀“臟”數(shù)據(jù)。
例如:在古書籍子系統(tǒng)的修改操作中,下面的事件依次發(fā)生時會丟失一個更新操作。
(1)用戶1檢索一行數(shù)據(jù)。
(2)用戶2檢索相同的行。
(3)用戶1修改那個行,更新數(shù)據(jù)庫并提交。
(4)用戶2修改那個行,更新數(shù)據(jù)庫并提交。
具體地說,當(dāng)用戶1移動到屏幕上的“書籍版本(andoctype)”字段時,修改了版本信息,單擊保存,并獲得更新已經(jīng)成功的確認。但此時,另一個用戶2已經(jīng)早于用戶1五分鐘前就在查詢記錄了,而且屏幕上顯示的仍然是舊數(shù)據(jù)。用戶1隨后到來。用戶2隨后更新了“書籍標(biāo)題(title)”字段,也單擊了保存,用戶2完全沒有意識到他已經(jīng)重寫了用戶1對字段的更改,填寫了老數(shù)據(jù)。之所以可能發(fā)生此事,是因為應(yīng)用程序開發(fā)人員,發(fā)現(xiàn)更新所有列比更改其中一列更容易,寫出的程序中,在更改一個指定字段時,會刷新該記錄的所有字段。
防止并發(fā)事件的產(chǎn)生,最常見的措施是對數(shù)據(jù)進行封鎖控制。
2 封鎖
2.1 鎖的功能
Oracle通過使用一個內(nèi)部封鎖機制維護數(shù)據(jù)的完整性、并行性和一致陸。鎖用于限制其他用戶對數(shù)據(jù)的存取。Oracle通過獲得不同類型的鎖,允許或阻止其他用戶對相同資源的同時存取并確保不破壞數(shù)據(jù)的完整性,從而自動滿足了數(shù)據(jù)的完整性、并行性和一致性。
Oracle在兩個不同級亡提供讀取一致性:語句級讀取一致性和事務(wù)級讀取一致性。Oracle總是實施語句級一致陸保證單個查詢所返回的數(shù)據(jù)與查詢開始時刻的數(shù)據(jù)相一致。一個查詢不會看到在查詢過程中提交的其他事務(wù)所進行的任何修改。事務(wù)級讀取一致性是指同一個事務(wù)中的所有數(shù)據(jù)對時間點是一致的。
2.2 封鎖機制
丟失更新是一個常見助數(shù)據(jù)庫問題。很多工具,例如ORACLE表單工具(ORACLE FORMS),通過對記錄進行鎖定以保證記錄在查詢時不可更改,可以避免此類問題的發(fā)生,民族古籍?dāng)?shù)字化保護系統(tǒng)是基于J2EE平臺的,用JAVA語言編寫的系統(tǒng),做不到這一點。在后臺進行保護的工具所做的工作,或開發(fā)人員必須自己做的工作,是從兩種封鎖類型中選擇使用一種封鎖。
(1)悲觀封鎖
用戶在屏幕上修改值之前,這個鎖定方法就要起作用。例如,用戶計劃對他選擇的某個特定行執(zhí)行更新,如單擊屏幕上的“修改”按鈕,就會放上一個行鎖。
悲觀鎖定(pessimistic locking)僅用于有狀態(tài)(stateful)或有連接(connected)環(huán)境,即應(yīng)用程序與數(shù)據(jù)庫有一條連續(xù)的連接,并且至少在事務(wù)生存期中只有一個用戶使用這條連接。每個應(yīng)用都得到數(shù)據(jù)庫的一條直接連接,這條連接只能由該應(yīng)用實例使用。這種采用有狀態(tài)方式的連接方法已經(jīng)不太常見了,特別是隨著20世紀(jì)90年代中后期應(yīng)用服務(wù)器的出現(xiàn),有狀態(tài)連接更是少見。
假設(shè)使用的是一條有狀態(tài)連接,應(yīng)用可以查詢數(shù)據(jù)而不做任何鎖定:
andoc@ANDOCS>select andocid, andoctype, title fromancientdocs where andocid=10;
最后,用戶選擇他想更新的一行。在上面的查詢中,假如用戶選擇更新andooctype行。在這個時間點上(即用戶還沒有在屏幕上做任何修改,但是行已經(jīng)從數(shù)據(jù)庫中讀出一段時間了),應(yīng)用會綁定用戶選擇的值,從而查詢數(shù)據(jù)庫,并確保數(shù)據(jù)尚未修改。在SQL*Plus中,為了模擬可能執(zhí)行的綁定調(diào)用,可以執(zhí)行下面的命令:
andoc@ANDOCS> variable andocid number
andoc@ANDOCS> variable andoctype vachaer2(1000)
andoc@ANDOCS> variable title vachaer2(1000)
andoc@ANDOCS> exec:andocid:=10; :andocypte:=“藏文”,:title:=“懷念故鄉(xiāng)”;
PL/SQL procedure successfully completed.
下面,除了簡單地查詢值并驗證數(shù)據(jù)尚未修改外,要使用FOR UPDATE NOWAIT鎖定這一行。應(yīng)用要執(zhí)行一下查詢:
andoc@ANDOCS> select andocid, andoctype, title
2 from ancientdocs
3 where andocid=:andocid
4 and andoctype=:andoctype
5 and title=:title
6 for update nowait
7/
根據(jù)要查詢的where條件,應(yīng)用將提供綁定變量的值,然后重新從數(shù)據(jù)庫查詢這一行,這一次會鎖定這一行,不允許其他會話更新。這種方法稱為悲觀鎖定(pessimistic locking)。
所有表都應(yīng)該有一個主鍵,而且主鍵是不可變的。以上代碼運行中,可能出現(xiàn)三種情況:
(1)如果底層數(shù)據(jù)沒有改變,就會再次得到標(biāo)題(titel)為“懷念故鄉(xiāng)”這一行,而且這一行會被鎖定,不允許其他會話更新,但是允許其他會話讀。
(2)如果另一個用戶正在更新這一行,就會得到一個ORA-00054:resource busy(ORA-00054:資源忙)錯誤。相應(yīng)地,必須等待更新這一行的用戶執(zhí)行完工作。
(3)在選擇數(shù)據(jù)和計劃更新之間,如果有人已經(jīng)修改了這一行,就會得到。行。這說明,屏幕數(shù)據(jù)是過時的。為了避免丟失更新情況,應(yīng)用程序需要重新查詢(requery),并允許在最終用戶修改之前鎖定數(shù)據(jù)。有了悲觀鎖定,用戶2試圖更新“書籍標(biāo)題(title)”字段時,應(yīng)用程序會識別出“書籍版本(andoctype)”字段已經(jīng)修改,所以會重新查詢數(shù)據(jù)。因此,用戶2不會用這個字段的舊數(shù)據(jù)覆蓋用戶1的修改。
當(dāng)成功地鎖定了這一行,應(yīng)用程序就會綁定新值,執(zhí)行更新命令后,提交所做的修改:
andoc@ANDOCS> update ancientdocs
2 set andoctype=:andoctype, title=:title
3 where andocid=:andocid;
andoc@ANDOCS> commit;
Commit complete.
現(xiàn)在就可以安全地修改這一行了。它不可能覆蓋其他人所做的修改,因為已經(jīng)驗證了在最初讀出數(shù)據(jù)之后以及對數(shù)據(jù)鎖定之前數(shù)據(jù)沒有改變。
(2)樂觀封鎖
第二種方法稱為樂觀鎖定(optimistic locking),即把所有鎖定都延遲到即將執(zhí)行更新之前才做。
這種鎖定方法在所有環(huán)境下都行得通,但是采用這種方法,執(zhí)行更新的用戶“失敗”的可能性會加大。當(dāng)這個用戶要更新他的數(shù)據(jù)行時,發(fā)現(xiàn)數(shù)據(jù)已經(jīng)修改過,就必須從頭再來。
可以在應(yīng)用程序中同時保留舊值和新值,然后在更新數(shù)據(jù)時使用下面的更新語句,這是樂觀鎖定常用的一種實現(xiàn)形式:
Update table
Set column1=:new_column1, column2=: new_column2,…
where primary_key=:primary_key
and column1=:old_column1
and column2=:old_column2…
此時,我們樂觀地認為數(shù)據(jù)沒有修改。在這中情況下,如果更新語句更新了一行,那么更新成功,這說明在讀數(shù)據(jù)和提交更新之間,數(shù)據(jù)沒有改變。但是如果更新了0行,則更新操作失敗,有另外用戶已經(jīng)修改了數(shù)據(jù)?,F(xiàn)在必須確定應(yīng)用中下一步要做什么,是讓最終用戶查詢這一行現(xiàn)在的新值,然后再重新開始事務(wù)呢?還是根據(jù)業(yè)務(wù)規(guī)則解決更新沖突,試圖合并兩個更新的值?
實際上,前面的UPDATE能避免丟失更新,但是有可能被阻塞,在等待另一個會話執(zhí)行對這一行的UPDATE時,它會掛起。如果所有的會話都使用樂觀鎖定,那么使用直接的UPDATE一般能成功,因為執(zhí)行更新并提交時,行只會被鎖定很短的時間。但是,如果某些會話使用了悲觀鎖定,它會在一段相對較長的時間內(nèi)持有行上的鎖,可能就會考慮使用SELECTFOR UPDATE NOWAIT,以此來驗證行是否未被修改,并在即將更新操作之前鎖定以避免另一個會話阻塞。
3 結(jié)束語
本文給出了在民族古籍?dāng)?shù)字化保護系統(tǒng)操作中典型的解決并發(fā)事件(丟失更新)的兩種封鎖機制。在實際工作中,可選用適當(dāng)?shù)姆怄i機制,鎖定需要修改的“行”,防止并發(fā)事件的產(chǎn)生,以保證數(shù)據(jù)庫的完整性和一致性。