qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

          Oracle 丟失更新問題的解決方案

           丟失更新是數(shù)據(jù)中一個(gè)比較常見的經(jīng)典問題,在做項(xiàng)目時(shí)我們有時(shí)可能會(huì)沒有注意到這個(gè)問題,但這個(gè)問題相當(dāng)重要,有時(shí)會(huì)帶來比較嚴(yán)重的結(jié)果。下面我們就來討論下這個(gè)丟失更新。

            一、什么是丟失更新:

            用一個(gè)操作過程來說明:

           ?。?)會(huì)話Session1 中的一個(gè)事務(wù)獲?。ú樵儯┮恍袛?shù)據(jù),并顯示給一個(gè)用戶User1。

           ?。?)會(huì)話Session2 中的另一個(gè)事務(wù)也獲取這一行,但是將數(shù)據(jù)顯示給另一個(gè)用戶User2。

            (3)User1 使用應(yīng)用修改了這一行,讓應(yīng)用更新數(shù)據(jù)庫(kù)并提交。會(huì)話Session1 的事務(wù)執(zhí)行完畢。

            (4)User2 也修改這一行,讓應(yīng)用更新數(shù)據(jù)庫(kù)并提交。會(huì)話Session2 的事務(wù)執(zhí)行完畢。

            這個(gè)過程就叫做“丟失更新”,因?yàn)榈冢?)步做的操作會(huì)全部丟失(被第4步操作覆蓋),最終數(shù)據(jù)庫(kù)只會(huì)保存第(4)步的更新結(jié)果。這個(gè)情況在有些系統(tǒng)中可能不會(huì)有影響,但在有些系統(tǒng)中可能就影響很大了,舉個(gè)簡(jiǎn)單的例子:

            財(cái)務(wù)系統(tǒng)加工資,若公司本次調(diào)薪?jīng)Q定給員工張三加1k人民幣,財(cái)務(wù)部?jī)擅僮魅藛TA和B,過程情況若是這樣的:

            1)A操作員在應(yīng)用系統(tǒng)的頁面上查詢出張三的薪水信息,然后選擇薪水記錄進(jìn)行修改,打開修改頁面但A突然有事離開了,頁面放在那沒有做任何的提交。

            2)這時(shí)候B操作員同樣在應(yīng)用中查詢出張三的薪水信息,然后選擇薪水記錄進(jìn)行修改,錄入增加薪水額1000,然后提交了。

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

            其實(shí)上面例子操作員A和B只要一前一后做提交,悲劇就出來了。后臺(tái)修改薪水的sql:update 工資表 set salary = salary + 增加薪水額 where staff_id = ‘員工ID’。這個(gè)過程走下來后結(jié)果是:張三開心了這次漲了2k,操作員A和B都郁悶了。

            二、解決思路:

            基本兩種思路,一種是悲觀鎖,另外一種是樂觀鎖; 簡(jiǎn)單的說就是一種假定這樣的問題是高概率的,最好一開始就鎖住,免得更新老是失??;另外一種假定這樣的問題是小概率的,最后一步做更新的時(shí)候再鎖住,免得鎖住時(shí)間太長(zhǎng)影響其他人做有關(guān)操作。

            三、解決方案1(悲觀鎖)

            a)傳統(tǒng)的悲觀鎖法(不推薦):

            以上面的例子來說明,在彈出修改工資的頁面初始化時(shí)(這種情況下一般會(huì)去從數(shù)據(jù)庫(kù)查詢出來),在這個(gè)初始化查詢中使用select ...for update nowait, 通過添加for update nowait語句,將這條記錄鎖住,避免其他用戶更新,從而保證后續(xù)的更新是在正確的狀態(tài)下更新的。然后在保持這個(gè)鏈接的狀態(tài)下,在做更新提交。當(dāng)然這個(gè)有個(gè)前提就是要保持鏈接,就是要對(duì)鏈接要占用較長(zhǎng)時(shí)間,這個(gè)在現(xiàn)在web系統(tǒng)高并發(fā)高頻率下顯然是不現(xiàn)實(shí)的。

            b)現(xiàn)在的悲觀鎖法(推薦優(yōu)先使用):

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

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

            a)舊值條件(前鏡像)法:

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

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

            其實(shí)這種方式是一個(gè)特殊化的前鏡像法,就是不需要使用多個(gè)舊值做條件,只需要在表上加一個(gè)版本列,這一列可以是NUMBER或 DATE/TIMESTAMP列,加這列的作用就是用來記錄這條數(shù)據(jù)的版本(在表設(shè)計(jì)時(shí)一般我們都會(huì)給每個(gè)表增加一些NUMBER型和DATE型的冗余字段,以便擴(kuò)展使用,這些冗余字段完全可以作為版本列用),在應(yīng)用程序中我們每次操作對(duì)版本列做維護(hù)即可。在更新時(shí)我們把上次版本作為條件進(jìn)行更新。

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

            d)使用ORA_ROWSCN法(不推薦)

            五、結(jié)論:

            綜上所述,我們對(duì)丟失更新問題建議采取上面的悲觀鎖b方法或樂觀鎖b方法(藍(lán)色字體已標(biāo)注),其實(shí)這兩種方式的本質(zhì)都一樣,都是在更新提交時(shí)做一次查詢確認(rèn)在更新提交,我個(gè)人覺得都是樂觀的做法,區(qū)別在于悲觀鎖b方法是通過select..for update方式,這個(gè)可能會(huì)導(dǎo)致其他會(huì)話的阻塞,而樂觀鎖b方法需要多一個(gè)版本列的維護(hù)。

            個(gè)人建議:在用戶并發(fā)數(shù)比較少且沖突比較嚴(yán)重的應(yīng)用系統(tǒng)中選擇悲觀鎖b方法,其他情況首先樂觀鎖版本列法。


          posted on 2011-11-17 16:02 順其自然EVO 閱讀(215) 評(píng)論(0)  編輯  收藏 所屬分類: 數(shù)據(jù)庫(kù)

          <2011年11月>
          303112345
          6789101112
          13141516171819
          20212223242526
          27282930123
          45678910

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 兴和县| 永嘉县| 藁城市| 年辖:市辖区| 南投县| 焉耆| 台中县| 通化市| 南城县| 上林县| 蓬安县| 青神县| 班戈县| 广平县| 札达县| 新竹县| 新龙县| 江孜县| 仁布县| 内黄县| 双城市| 庆阳市| 思南县| 金寨县| 姚安县| 黄山市| 佛坪县| 莆田市| 托克逊县| 浠水县| 白朗县| 新民市| 南木林县| 松溪县| 梓潼县| 东乌| 景泰县| 偏关县| 莱芜市| 菏泽市| 贵阳市|