oracle 更新丟失

zhangsharp20發表於2014-09-16
丟失更新是資料中一個比較常見的經典問題,在做專案時我們有時可能會沒有注意到這個問題,但這個問題相當重要,有時會帶來比較嚴重的結果。下面我們就來討論下這個丟失更新。 

  一、什麼是丟失更新: 

  用一個操作過程來說明: 

  (1)會話Session1 中的一個事務獲取(查詢)一行資料,並顯示給一個使用者User1. 

  (2)會話Session2 中的另一個事務也獲取這一行,但是將資料顯示給另一個使用者User2. 

  (3)User1 使用應用修改了這一行,讓應用更新資料庫並提交。會話Session1 的事務執行完畢。 

  (4)User2 也修改這一行,讓應用更新資料庫並提交。會話Session2 的事務執行完畢。 

  這個過程就叫做"丟失更新",因為第(3)步做的操作會全部丟失(被第4步操作覆蓋),最終資料庫只會儲存第(4)步的更新結果。這個情況在有些系統中可能不會有影響,但在有些系統中可能就影響很大了,舉個簡單的例子: 

  財務系統加工資,若公司本次調薪決定給員工張三加1k人民幣,財務部兩名操作人員A和B,過程情況若是這樣的: 

  1)A操作員在應用系統的頁面上查詢出張三的薪水資訊,然後選擇薪水記錄進行修改,開啟修改頁面但A突然有事離開了,頁面放在那沒有做任何的提交。 

  2)這時候B操作員同樣在應用中查詢出張三的薪水資訊,然後選擇薪水記錄進行修改,錄入增加薪水額1000,然後提交了。 

  3)這時候A操作員回來了,在自己之前開啟的薪水修改頁面上也錄入了增加薪水額1000,然後提交了。 

  其實上面例子操作員A和B只要一前一後做提交,悲劇就出來了。後臺修改薪水的sql:update 工資表 set salary = salary + 增加薪水額 where staff_id = '員工ID'。這個過程走下來後結果是:張三開心了這次漲了2k,操作員A和B都鬱悶了。 

  二、解決思路: 

  基本兩種思路,一種是悲觀鎖,另外一種是樂觀鎖; 簡單的說就是一種假定這樣的問題是高機率的,最好一開始就鎖住,免得更新老是失敗;另外一種假定這樣的問題是小機率的,最後一步做更新的時候再鎖住,免得鎖住時間太長影響其他人做有關操作。 

  三、解決方案1(悲觀鎖) a)傳統的悲觀鎖法(不推薦): 

  以上面的例子來說明,在彈出修改工資的頁面初始化時(這種情況下一般會去從資料庫查詢出來),在這個初始化查詢中使用select ……for update, 透過新增for update nowait語句,將這條記錄鎖住,避免其他使用者更新,從而保證後續的更新是在正確的狀態下更新的。然後在保持這個連結的狀態下,在做更新提交。當然這個有個前提就是要保持連結,就是要對連結要佔用較長時間,這個在現在web系統高併發高頻率下顯然是不現實的。 

  b)現在的悲觀鎖法(推薦優先使用): 

  在修改工資這個頁面做提交時先查詢下,當然這個查詢必須也要加鎖(select ……for update nowait),有人會說,在這裡做個查詢確認記錄是否有改變不就行了嗎,是的,是要做個確認,只是你不加for update就不能保證你在查詢到更新提交這段時間裡這條記錄沒有被其他會話更新過,所以這種方式也需要在查詢時鎖定記錄,保證在這條記錄沒有變化的基礎上再做更新,若有變化則提示告知使用者。 

  四、解決方案2(樂觀鎖) 

  a)舊值條件(前映象)法: 

  就是在sql更新時使用舊的狀態值做條件,SQL大致如下 Update table set col1 = newcol1value, col2 = newcol2value…。 where col1 = oldcol1value and col2 = oldcol2value…。,在上面的例子中我們就可以把當前工資作為條件進行更新,如果這條記錄已經被其他會話更新過,則本次更新了0行,這裡我們應用系統一般會做個提示告知使用者重新查詢更新。這個取哪些舊值作為條件更新視具體系統實際情況而定。(這種方式有可能發生阻塞,如果應用其他地方使用悲觀鎖法長時間鎖定了這條記錄,則本次會話就需要等待,所以使用這種方式時最好統一使用樂觀鎖法。) 

  b)使用版本列法(推薦優先使用): 

  其實這種方式是一個特殊化的前映象法,就是不需要使用多箇舊值做條件,只需要在表上加一個版本列,這一列可以是NUMBER或 DATE/TIMESTAMP列,加這列的作用就是用來記錄這條資料的版本(在表設計時一般我們都會給每個表增加一些NUMBER型和DATE型的冗餘欄位,以便擴充套件使用,這些冗餘欄位完全可以作為版本列用),在應用程式中我們每次操作對版本列做維護即可。在更新時我們把上次版本作為條件進行更新。 

  c)使用校驗和法(不推薦) 

  d)使用ORA_ROWSCN法(不推薦) 

  五、結論: 

  綜上所述,我們對丟失更新問題建議採取上面的悲觀鎖b方法或樂觀鎖b方法(藍色字型已標註),其實這兩種方式的本質都一樣,都是在更新提交時做一次查詢確認在更新提交,我個人覺得都是樂觀的做法,區別在於悲觀鎖b方法是透過select……for update方式,這個可能會導致其他會話的阻塞,而樂觀鎖b方法需要多一個版本列的維護。 

  個人建議:在使用者併發數比較少且衝突比較嚴重的應用系統中選擇悲觀鎖b方法,其他情況首先樂觀鎖版本列法。 

  原文出自【位元網】,轉載請保留原文連結:/database/156/.sh tml

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29802484/viewspace-1270794/,如需轉載,請註明出處,否則將追究法律責任。

相關文章