日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

并发事务正确性的准则 可串行化_从0到1理解数据库事务(上):并发问题与隔离级别...

發(fā)布時間:2023/12/10 数据库 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发事务正确性的准则 可串行化_从0到1理解数据库事务(上):并发问题与隔离级别... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

最近準備寫一篇關于Spanner事務的分享,所以先分享一些基礎知識,涉及ACID、隔離級別、MVCC、鎖,由于太長,只好拆分成上下兩篇:

  • 上:并發(fā)問題與隔離級別

主要講事務所要解決的問題、思路,先理解為什么需要事務以及事務并發(fā)控制中面臨的問題。

  • 下:隔離級別實現(xiàn)——MVCC與鎖

隔離性是為了更好地做到并發(fā)控制,事務的并發(fā)表現(xiàn)會對業(yè)務有直接影響,所以這篇會詳細講如何實現(xiàn)隔離,主要是講兩種主流技術方案——MVCC與鎖,理解了MVCC與鎖,就可以舉一反三地看各種數(shù)據(jù)庫并發(fā)控制方案,并理解每種實現(xiàn)能解決的問題以及需要開發(fā)者自己注意的并發(fā)問題,以更好支撐業(yè)務開發(fā)。

文章開始前先給一個小思考,考慮一個情況:
像下面這樣實現(xiàn)User提現(xiàn)100元,是否一定不會出問題?
Start Transaction
SELECT balance FROM users WHERE user_name=x; (此次讀取在Transaction中)
在代碼中判斷balance是否大于等于100
如果小于100元,End Transaction并且返回余額不足提現(xiàn)失敗
如果大于等于100元,則 UPDATE users SET balance = balance - 100 WHERE user_name=x; 然后Commit Transaction,返回提現(xiàn)成功

如果你已經(jīng)很理解數(shù)據(jù)庫事務了,一定知道什么情況有問題,以及為什么出現(xiàn)這個問題,這篇文章對你太入門,不用繼續(xù)看。如果不太清楚,那希望你看完上、下兩篇就非常理解了,否則就是我寫得太爛。

一、重新理解 ACID

1. 數(shù)據(jù)操作中面臨的問題

技術中的所有方案必定是為了解決特定問題,先理解問題再看方案,學起來更簡單、理解更深入,所以先從數(shù)據(jù)庫面臨的問題說起。

首先,要理解為什么數(shù)據(jù)庫會有事務的需求,先理解數(shù)據(jù)庫要解決的根本問題不是存儲,存儲問題已經(jīng)被文件系統(tǒng)解決了,數(shù)據(jù)庫的目的是如何幫助開發(fā)者更可靠、更快速、更便利地使用存儲,更好地幫助開發(fā)者完成業(yè)務,業(yè)務中一個高頻需求是:有一批連續(xù)的操作,這一批操作要么全部成功,要么就可以像沒有發(fā)生過一樣,不要由于部分未成功而導致臟數(shù)據(jù)產(chǎn)生。如果沒有事務,我們處理用戶下單的業(yè)務場景,就要超級多的代碼去handle各種錯誤、清理各種臟數(shù)據(jù)、避免可能的bug,比如下單成功卻由于數(shù)據(jù)庫宕機導致沒有扣款。為了提高開發(fā)效率、降低開發(fā)成本,就需要數(shù)據(jù)庫能提供一種保證:將一組操作看作一個單元,這一單元可以全部成功,在部分失敗的情況下,可以完全回滾,就像沒有發(fā)生,這一組操作稱為事務(Transaction)。

但是僅僅做到上面那一點是不夠的,因為這一個簡單需求,其實引入了另一個問題,請注意重點——“一組操作”,事務中可能存在著多個獨立操作,他們組合為一組操作,理解多線程編程的同學一定會馬上想到,這就會出現(xiàn)經(jīng)典的并發(fā)問題,多個事務間如果不進行并發(fā)控制,就會產(chǎn)生各種意外結果,這不是使用者想要的。

總結一下,數(shù)據(jù)操作中面臨的問題:
如何將一組操作看作一個整體,要么全部成功,要么全部回滾。
如何在滿足上一條需求的情況下,能夠對它進行并發(fā)控制,保證不要出現(xiàn)意外結果。

2. 我們需要什么:ACID

ACID 是為了解決上述問題所總結出,為保證事務是正確可靠的,所必須具備的四個特性:

1. 原子性(Atomicity)

事務中的原子性是一個常常被大家誤解的特性,因為這個原子性的意思和我們通常語境下的原子性不太一樣,大多數(shù)時候原子性是指一條不可再分割、不會被中斷影響的指令,比如讀取一個內(nèi)存地址的值、將值寫回內(nèi)存地址、redis的SETNX(set if not exists),這些操作都符合我們常說的原子性。

可是事務中的原子性,并不是指事務具有不被中斷影響的特點,它僅僅是指,事務中的所有操作應該被看作不可分割的一組指令,任何一個指令不能獨立存在,要么全部成功執(zhí)行,要么全部不發(fā)生(也就是回滾)。

還有很多同學對這里所說的“成功執(zhí)行”有誤解,成功執(zhí)行是指數(shù)據(jù)庫層面的,而不是業(yè)務層面的,舉個例子,客戶購買商品A,可是在購買時,商家剛好下架了商品,那么此時執(zhí)行 update products where product_id=A and status=銷售中 ,由于product的status已經(jīng)變成“下架”,導致被更新的行數(shù)為0,這個算成功執(zhí)行嗎?算!數(shù)據(jù)庫不報錯、不宕機、正常運行就是成功,更新行數(shù)為0是數(shù)據(jù)庫的正常返回結果,這在業(yè)務上是失敗,在數(shù)據(jù)庫層面是成功,這種情況數(shù)據(jù)庫不會執(zhí)行回滾,需要程序員判斷更新行數(shù),如果為0,手動回滾。

如果數(shù)據(jù)庫由于硬件或者系統(tǒng)問題發(fā)生宕機、報錯,這樣才算是指令執(zhí)行失敗,此時數(shù)據(jù)庫會重試或者直接回滾,然后將錯誤返回給開發(fā)者。

原子性不止為開發(fā)者保證了事務的可靠性(不會因為數(shù)據(jù)庫出錯而產(chǎn)生臟數(shù)據(jù)),還能讓開發(fā)者手動回滾,提供了業(yè)務的便利性。

2. 一致性(Consistency)

這個名詞也是相當令人困惑,與數(shù)據(jù)庫主從復制中所說的“一致性”不同,主從復制的一致性是指多個副本間是否完成同步、數(shù)據(jù)相同,而這里的一致性是指事務是否產(chǎn)生非預期中間狀態(tài)或結果。比如臟讀和不可重復讀,產(chǎn)生了非預期中間狀態(tài),臟寫與丟失修改則產(chǎn)生了非預期結果。一致性實際上是由后面的隔離性去進一步保證的,隔離性達到要求,則可以滿足一致性。也就是說,隔離不足會導致事務不滿足一致性要求,所以務必理解各個隔離級別,才能少寫B(tài)ug。

3. 隔離性(Isolation)

簡單來說,隔離性就是多個事務互不影響,感覺不到對方存在,這個特性就是為了做并發(fā)控制。在多線程編程中,如果大家都讀寫同一塊數(shù)據(jù),那么久可能出現(xiàn)最終數(shù)據(jù)不一致,也就是每條線程都可能被別的線程影響了。按理說,最嚴格的隔離性實現(xiàn)就是完全感知不到其他并發(fā)事務的存在,多個并發(fā)事務無論如何調度,結果都與串行執(zhí)行一樣。為了達到串行效果,目前采用的方式一般是兩階段加鎖(Two Phase Locking),但是讀寫都加鎖效率非常低,讀寫之間只能排隊執(zhí)行,有時候為了效率,原則是可以妥協(xié)的,于是隔離性并不嚴格,它被分為了多種級別,從高到低分別為:

  • ??可串行化(Serializable)
  • ??可重復讀(Read Repeatable)
  • ??已提交讀(Read Committed)
  • ??未提交讀(Read Uncommitted)

每一個級別都只是指導標準,每個數(shù)據(jù)庫對其的實現(xiàn)都有差異,有的數(shù)據(jù)庫在Read Committed級別時,就已經(jīng)實現(xiàn)了Read Repeatable的效果,有的數(shù)據(jù)庫干脆不提供Read Uncommitted級別。

在隔離級別為Serializable時,就會感覺到事務像一個完完全全的原子操作,不被任何中斷、并發(fā)所影響。

很多開發(fā)者理解的事務可能就在Serializable級別,大家誤以為事務都是可串行化的,其實并不是,大多數(shù)的數(shù)據(jù)庫默認隔離級別都不是可串行化,大多數(shù)在Read Repeatable或者Read Committed,要是按照可串行化的思維去編程,卻用著低于可串行化的隔離級別,就很容易寫出導致數(shù)據(jù)在業(yè)務層面不一致的代碼,所以開發(fā)者一定要理解各個隔離級別及其原理,更好地支撐業(yè)務開發(fā),下面會仔細地講隔離級別及其實現(xiàn)。

4. 持久性(Duration)

這是ACID中最好理解的,即事務成功提交后,對數(shù)據(jù)的修改永久的,即使系統(tǒng)發(fā)生故障,也不會丟失,這里所說的故障,也只是一般錯誤比如宕機、系統(tǒng)Bug、斷電,如果是硬盤損毀,那就沒辦法,數(shù)據(jù)一定會丟失。

二、并發(fā)問題與隔離級別

在討論各個隔離級別的實現(xiàn)之前,先看一下在事務并發(fā)執(zhí)行時,隔離不足會導致的問題。

臟寫(Dirty Write)

還未提交的事務寫了另一個未提交事務所寫過的數(shù)據(jù),稱為臟寫,比如:

兩個并發(fā)執(zhí)行的事務A、B,A寫了x,在A還未提交前,B也寫了x,然后A提交,此時雖然B還沒有提交,但是A也會發(fā)現(xiàn)自己寫的x不見了。

很多地方用“覆蓋”去形容臟寫,但是我覺得不太適合,因為覆蓋暗示了一種先后鏈條,某個事務寫了數(shù)據(jù),在昨天就提交了,今天有事務來寫同一個數(shù)據(jù),可以稱之為覆蓋,昨天的數(shù)據(jù)成為歷史,但這不是臟寫,所以更適合的形容可能是“擦除”,事務發(fā)現(xiàn)自己的提交被別人擦除,好像不存在。

臟寫是事務一定不允許發(fā)生的,所以不管是哪個隔離級別都一定不允許臟寫。

臟讀(Dirty Read)

由于事務的可回滾特性,因此commit前的任何讀寫,都有被撤銷的可能,假如某個事物讀取了還未commit事務的寫數(shù)據(jù),后來對方回滾了,那么讀到的就是臟數(shù)據(jù),因為它已經(jīng)不存在了。

避免臟讀可以采用加鎖或者快照讀的解決方案。在已提交讀(Read Committed)級別就可以避免臟讀,因為讀到的一定是已經(jīng)Commit的數(shù)據(jù)。在業(yè)務開發(fā)中,雖然有未提交讀(Read Uncommitted),但是幾乎是沒有人會用的,讀到臟數(shù)據(jù)一般對業(yè)務是很大的傷害,所以有的數(shù)據(jù)庫干脆都不支持未提交讀,比如PostgreSQL。

不可重復讀(Non-Repeatable Read)

事務A讀取一個值,但是沒有對它進行任何修改,另一個并發(fā)事務B修改了這個值并且提交了,事務A再去讀,發(fā)現(xiàn)已經(jīng)不是自己第一次讀到的值了,是B修改后的值,就是不可重復讀。

簡單來說就是第一次讀的值,啥都沒做,下次讀它也有可能發(fā)生變化。

一般數(shù)據(jù)庫使用MVCC,在事務的第一條語句開始時生成Read View,事務之后的所有讀取,都是基于同一個Read View,以此避免不可重復讀問題。

幻讀(Phantom)

與不可重復讀非常類似,事務A查詢一個范圍的值,另一個并發(fā)事務B往這個范圍中插入了數(shù)據(jù)并提交,然后事務A再查詢相同范圍,發(fā)現(xiàn)多了一條記錄,或者某條記錄被別的事務刪除,事務A發(fā)現(xiàn)少了一條記錄。

幻讀容易與不可重復讀混淆,區(qū)別它們只需要記住不可重復讀面向的是“同一條記錄”,而幻讀面向的是“同一個范圍”。

MVCC雖然使用快照的方式解決了不可重復讀,但是還是不能避免幻讀,幻讀需要通過范圍鎖解決,可能大家會覺得很奇怪,為什么快照讀無法避免幻讀,這個會在下一篇文章中詳細講。

SQL標準中有對于各個隔離級別所允許出現(xiàn)的問題作出規(guī)定:

除了以上4個問題外,下面還有3個問題,更偏向業(yè)務層面,不過也是由于隔離不足引起的:

讀偏斜(Read Skew)

Skew可以理解為不一致,因此讀偏斜可以理解為讀結果違反業(yè)務一致性,比如X、Y兩個賬戶余額都為50,他們總和為100,事務A讀X余額為50,然后事務B從X轉賬50到Y然后提交,事務A在B提交后讀Y發(fā)現(xiàn)余額為100,那么它們總和變成了150,此時違反業(yè)務一致性。

寫偏斜(Write Skew)

寫偏斜可以理解為事務commit之前寫前提被破壞,導致寫入了違反業(yè)務一致性的數(shù)據(jù),網(wǎng)上有個很好的簡稱為寫前提困境,也就是讀出某些數(shù)據(jù),作為另一些寫入的前提條件,但是在提交前,讀入的數(shù)據(jù)就已被別的事務修改并提交,這個事務并不知道,然后commit了自己的另一些寫入,寫前提在commit前就被修改,導致寫入結果違反業(yè)務一致性。

寫偏斜發(fā)生在寫前提與寫入目標不相同的情境下。

這是業(yè)務開發(fā)中最容易出錯地方,如果開發(fā)者不太理解隔離級別,也不知道目前使用的是哪個隔離級別,很可能寫出有寫偏斜的代碼,造成業(yè)務不一致。

舉個例子:

信用卡系統(tǒng)對不同等級的會員有積分加成,3級會員則每次都3倍積分,同時,會有定時任務檢查當積分不滿足要求時,就會降級。

首先,會員進行了刷卡消費,此時要計算積分,開啟了事務A,讀到會員等級為3,與此同時定時任務也開始了,讀到會員積分為2800,已經(jīng)不滿足3000分應該降級為2級,然后將會員等級降級為2并且commit,由于事務A讀到的等級為3,它還是按照3倍積分為會員增加了積分,會員賺了,多虧那個程序員不理解他使用的事務隔離級別,出現(xiàn)了業(yè)務不一致。

丟失更新(Lost Updates)

由于未提交事務之間看不到對方的修改,因此都以一個舊前提去更新同一個數(shù)據(jù),導致最后的提交結果是錯誤值。

假設有支付寶賬戶X,余額100元,事務A、B同時向X分別充值10元、20元,最后結果應該為130元,但是由于丟失更新,最后是110元。

丟失更新與寫偏斜很相似,都是由于寫前提被改變,他們區(qū)別是,丟失更新是在同一個數(shù)據(jù)的最終不一致,而寫偏斜的沖突不在同一個數(shù)據(jù),在不同數(shù)據(jù)中的最終不一致。

這一篇講到的所有問題都會在下一篇講隔離級別實現(xiàn)中得到解決,理解隔離級別實現(xiàn),有助于選擇合適的隔離級別,或者在代碼層面有意識地避免隔離級別不足所帶來的問題。

參考資料

  • 《MySQL 是怎樣運行的:從根兒上理解 MySQL》
  • 《數(shù)據(jù)庫事務處理的藝術:事務管理與并發(fā)控制》
  • 《數(shù)據(jù)庫事務、隔離級別和鎖》(博客)
  • 《SQL隔離級別》(博客)
  • 《開發(fā)者都應該了解的數(shù)據(jù)庫隔離級別》(博客)
  • 《A beginner’s guide to read and write skew phenomena》(博客)

總結

以上是生活随笔為你收集整理的并发事务正确性的准则 可串行化_从0到1理解数据库事务(上):并发问题与隔离级别...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。