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