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