併發事務的丟失更新及其處理方式
在事務的隔離級別內容中,能夠了解到兩個不同的事務在併發的時候可能會發生資料的影響。細心的話可以發現事務隔離級別章節中,
髒讀、不可重複讀、幻讀
三個問題都是由事務a對資料進行修改、增加,事務b總是在做讀操作。如果兩事務都在對資料進行修改則會導致另外的問題:丟失更新。
1
、丟失更新的定義及產生原因。
第一類丟失更新(
回滾丟失,lostupdate)(通過設定隔離級別可以防止 repeatable read)
a事務撤銷時,把已經提交的b事務的更新資料覆蓋了。這種錯誤可能造成很嚴重的問題,通過下面的賬戶取款轉賬就可以看出來:
時間取款事務a
轉賬事務b
t1開始事務
t2開始事務
t3查詢賬戶餘額為1000元
t4查詢賬戶餘額為1000元
t5匯入100元把餘額改為1100元
t6提交事務
t7取出100元把餘額改為900元
t8撤銷事務
t9餘額恢復為1000元(丟失更新)
a事務在撤銷時,「不小心」將b事務已經轉入賬戶的金額給抹去了。
sql92
沒有定義這種現象,標準定義的所有隔離界別都不允許第一類丟失更新發生。
第二類丟失更新(
覆蓋丟失/兩次更新問題,second lost update) a
事務覆蓋b事務已經提交的資料,造成b事務所做操作丟失:
時間轉賬事務a
取款事務b
t1
開始事務
t2開始事務
t3查詢賬戶餘額為1000元
t4查詢賬戶餘額為1000元
t5取出100元把餘額改為900元
t6提交事務
t7匯入100元
t8提交事務
t9把餘額改為1100元(丟失更新)
上面的例子裡由於支票轉賬事務覆蓋了取款事務對存款餘額所做的更新,導致銀行最後損失了100元,相反如果轉賬事務先提交,那麼使用者賬戶將損失100元。
第二類丟失更新,實際上和不可重複讀是同一種問題。
有些系統中第二類丟失更新可能就影響很大了,舉個簡單的例子:
財務系統加工資,若公司本次調薪決定給員工張三加1k人民幣,財務部兩名操作人員a和b,過程情況若是這樣的:
1)a操作員在應用系統的頁面上查詢出張三的薪水資訊,然後選擇薪水記錄進行修改,開啟修改頁面但a突然有事離開了,頁面放在那沒有做任何的提交。
2)這時候b操作員同樣在應用中查詢出張三的薪水資訊,然後選擇薪水記錄進行修改,錄入增加薪水額1000,然後提交了。
3)這時候a操作員回來了,在自己之前開啟的薪水修改頁面上也錄入了增加薪水額1000,然後提交了。
其實上面例子操作員a和b只要一前一後做提交,悲劇就出來了。後台修改薪水的sql:update 工資表 setsalary = salary + 增加薪水額 where staff_id = 『員工id』。這個過程走下來後結果是:張三開心了這次漲了2k,操作員a和b都鬱悶了。
解決思路:
基本兩種思路,一種是
悲觀鎖,另外一種是
樂觀鎖;
簡單的說就是一種假定這樣的問題是高概率的,最好一開始就鎖住,免得更新老是失敗;另外一種假定這樣的問題是小概率的,最後一步做更新的時候再鎖住,免得鎖住時間太長影響其他人做有關操作。
解決方案1(悲觀鎖)
a.傳統的悲觀鎖法(不推薦):
以上面的例子來說明,
在彈出修改工資的頁面初始化時(這種情況下一般會去從資料庫查詢出來),在這個初始化查詢中使用select ...for update nowait, 通過新增for update nowait語句,將這條記錄鎖住,避免其他使用者更新,從而保證後續的更新是在正確的狀態下更新的。然後在保持這個鏈結的狀態下,在做更新提交。當然這個有個前提就是要保持鏈結,就是要對鏈結要占用較長時間,這個在現在web系統高併發高頻率下顯然是不現實的。
在修改工資這個頁面做提交時先查詢下,當然這個查詢必須也要加鎖(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方法,其他情況首先樂觀鎖版本列法。
Spring事務的更新丟失
更新丟失 如果多個執行緒操作,基於同乙個查詢結構對錶中的記錄進行修改,那麼後修改的記錄將會覆蓋前面修改的記錄,前面的修改就丟失掉了,這就叫做更新丟失。serializable可以防止更新丟失問題的發生。其他的三個隔離級別都有可能發生更新丟失問題。serializable雖然可以防止更新丟失,但是效率...
事務的併發處理
事務特性 acid automic 原子性 consistent 一致性 itegrity 隔離性 durability 永續性 1 一致性 將事務中所做的操作 成乙個原子單元,即對於事務所進行的資料修改等操作,要麼全部執行,要麼全部不執行。2 原子性 事務在完成時,必須使所有的資料都保持一致狀態,...
mysql 因併發操作導致的丟失更新
丟失更新產生的情況 1.事務t1查詢出一行資料,並顯示給user1。2.事務t2也查詢該行資料,並顯示給user2。3.user1修改這行記錄,更新資料庫並提交。4.user2修改這行記錄,更新資料庫並提交。在這個過程中,使用者user1的修改更新操作 丟失 了。如何避免 要避免丟失更新,需要讓這種...