圖解數(shù)據(jù)庫臟讀、不可重復(fù)讀、幻讀
概述
分享之前關(guān)于數(shù)據(jù)庫臟讀、不可重復(fù)讀、幻讀的相關(guān)筆記,也做個備忘!
如果沒有事務(wù)隔離,會出現(xiàn)什么樣的情況?
假設(shè)我們現(xiàn)在有這樣一張表(T),里面記錄了很多牛人的名字,我們不進(jìn)行事務(wù)的隔離看看會發(fā)生什么呢?
***天,事務(wù)A訪問了數(shù)據(jù)庫,它干了一件事情,往數(shù)據(jù)庫里加上了新來的牛人的名字,但是沒有提交事務(wù)。
- insert into T values (4, '牛D');
這時,來了另一個事務(wù)B,他要查詢所有牛人的名字。
- select Name from T;
這時,如果沒有事務(wù)之間沒有有效隔離,那么事務(wù)B返回的結(jié)果中就會出現(xiàn)“牛D”的名字。這就是“臟讀(dirty read)”。
第二天,事務(wù)A訪問了數(shù)據(jù)庫,他要查看ID是1的牛人的名字,于是執(zhí)行了
- select Name from T where ID = 1;
這時,事務(wù)B來了,因?yàn)镮D是1的牛人改名字了,所以要更新一下,然后提交了事務(wù)。
- update T set Name = '不牛' where ID = 1;
接著,事務(wù)A還想再看看ID是1的牛人的名字,于是又執(zhí)行了
- select Name from T where ID = 1;
結(jié)果,兩次讀出來的ID是1的牛人名字竟然不相同,這就是不可重復(fù)讀(unrepeatable read)。
第三天,事務(wù)A訪問了數(shù)據(jù)庫,他想要看看數(shù)據(jù)庫的牛人都有哪些,于是執(zhí)行了
- select * from T;
這時候,事務(wù)B來了,往數(shù)據(jù)庫加入了一個新的牛人。
- insert into T values(4, '牛D');
這時候,事務(wù)A忘了剛才的牛人都有哪些了,于是又執(zhí)行了。
- select * from T;
結(jié)果,***次有三個牛人,第二次有四個牛人。
相信這個時候事務(wù)A就蒙了,剛才發(fā)生了什么?這種情況就叫“幻讀(phantom problem)”。
為了防止出現(xiàn)臟讀、不可重復(fù)讀、幻讀等情況,我們就需要根據(jù)我們的實(shí)際需求來設(shè)置數(shù)據(jù)庫的隔離級別。下面介紹下這方面內(nèi)容。
數(shù)據(jù)庫事務(wù)隔離級別
數(shù)據(jù)庫事務(wù)隔離級別分為四種(級別遞減):
1、Serializable (串行化):最嚴(yán)格的級別,事務(wù)串行執(zhí)行,資源消耗***;
2、REPEATABLE READ(重復(fù)讀) :保證了一個事務(wù)不會修改已經(jīng)由另一個事務(wù)讀取但未提交(回滾)的數(shù)據(jù)。避免了“臟讀取”和“不可重復(fù)讀取”的情況,但不能避免“幻讀”,但是帶來了更多的性能損失。
3、READ COMMITTED (提交讀):大多數(shù)主流數(shù)據(jù)庫的默認(rèn)事務(wù)等級,保證了一個事務(wù)不會讀到另一個并行事務(wù)已修改但未提交的數(shù)據(jù),避免了“臟讀取”,但不能避免“幻讀”和“不可重復(fù)讀取”。該級別適用于大多數(shù)系統(tǒng)。
4、Read Uncommitted(未提交讀) :事務(wù)中的修改,即使沒有提交,其他事務(wù)也可以看得到,會導(dǎo)致“臟讀”、“幻讀”和“不可重復(fù)讀取”。
臟讀、不可重復(fù)讀、幻讀:
一個數(shù)據(jù)庫可能擁有多個訪問客戶端,這些客戶端并發(fā)訪問數(shù)據(jù)庫時,若沒有采取必要的隔離措施,存在以下問題,這些問題分為5類,包括3類數(shù)據(jù)讀問題:臟讀、不可重復(fù)讀和幻讀。兩類數(shù)據(jù)更新問題:***類丟失更新、第二類丟失更新。
1、臟讀
A事務(wù)讀取B事務(wù)尚未提交的更改數(shù)據(jù),并在這個數(shù)據(jù)的基礎(chǔ)上進(jìn)行操作,這時候如果事務(wù)B回滾,那么A事務(wù)讀到的數(shù)據(jù)是不被承認(rèn)的。例如常見的取款事務(wù)和轉(zhuǎn)賬事務(wù):
2、不可重復(fù)讀
不可重復(fù)讀是指A事務(wù)讀取了B事務(wù)已經(jīng)提交的更改數(shù)據(jù)。假如A在取款事務(wù)的過程中,B往該賬戶轉(zhuǎn)賬100,A兩次讀取的余額發(fā)生不一致。
3、幻讀
A事務(wù)讀取B事務(wù)提交的新增數(shù)據(jù),會引發(fā)幻讀問題?;米x一般發(fā)生在計(jì)算統(tǒng)計(jì)數(shù)據(jù)的事務(wù)中,例如銀行系統(tǒng)在同一個事務(wù)中兩次統(tǒng)計(jì)存款賬戶的總金額,在兩次統(tǒng)計(jì)中,剛好新增了一個存款賬戶,存入了100,這時候兩次統(tǒng)計(jì)的總金額不一致。
注意:不可重復(fù)讀和幻讀的區(qū)別是:前者是指讀到了已經(jīng)提交的事務(wù)的更改數(shù)據(jù)(修改或刪除),后者是指讀到了其他已經(jīng)提交事務(wù)的新增數(shù)據(jù)。對于這兩種問題解決采用不同的辦法,防止讀到更改數(shù)據(jù),只需對操作的數(shù)據(jù)添加行級鎖,防止操作中的數(shù)據(jù)發(fā)生變化;二防止讀到新增數(shù)據(jù),往往需要添加表級鎖,將整張表鎖定,防止新增數(shù)據(jù)(oracle采用多版本數(shù)據(jù)的方式實(shí)現(xiàn))。
4、通俗解釋:
臟讀:所謂的臟讀,其實(shí)就是讀到了別的事務(wù)回滾前的臟數(shù)據(jù)。比如事務(wù)B執(zhí)行過程中修改了數(shù)據(jù)X,在未提交前,事務(wù)A讀取了X,而事務(wù)B卻回滾了,這樣事務(wù)A就形成了臟讀。
也就是說,當(dāng)前事務(wù)讀到的數(shù)據(jù)是別的事務(wù)想要修改成為的但是沒有修改成功的數(shù)據(jù)。
不可重復(fù)讀:事務(wù)A首先讀取了一條數(shù)據(jù),然后執(zhí)行邏輯的時候,事務(wù)B將這條數(shù)據(jù)改變了,然后事務(wù)A再次讀取的時候,發(fā)現(xiàn)數(shù)據(jù)不匹配了,就是所謂的不可重復(fù)讀了。
也就是說,當(dāng)前事務(wù)先進(jìn)行了一次數(shù)據(jù)讀取,然后再次讀取到的數(shù)據(jù)是別的事務(wù)修改成功的數(shù)據(jù),導(dǎo)致兩次讀取到的數(shù)據(jù)不匹配,也就照應(yīng)了不可重復(fù)讀的語義。
幻讀:事務(wù)A首先根據(jù)條件索引得到N條數(shù)據(jù),然后事務(wù)B改變了這N條數(shù)據(jù)之外的M條或者增添了M條符合事務(wù)A搜索條件的數(shù)據(jù),導(dǎo)致事務(wù)A再次搜索發(fā)現(xiàn)有N+M條數(shù)據(jù)了,就產(chǎn)生了幻讀。
也就是說,當(dāng)前事務(wù)讀***次取到的數(shù)據(jù)比后來讀取到數(shù)據(jù)條目少。
不可重復(fù)讀和幻讀比較:
兩者有些相似,但是前者針對的是update或delete,后者針對的insert。
為什么會出現(xiàn)“臟讀”?因?yàn)闆]有“select”操作沒有規(guī)矩。
為什么會出現(xiàn)“不可重復(fù)讀”?因?yàn)?ldquo;update”操作沒有規(guī)矩。
為什么會出現(xiàn)“幻讀”?因?yàn)?ldquo;insert”和“delete”操作沒有規(guī)矩。
“讀未提(Read Uncommitted)”能預(yù)防啥?啥都預(yù)防不了。
“讀提交(Read Committed)”能預(yù)防啥?使用“快照讀(Snapshot Read)”,避免“臟讀”,但是可能出現(xiàn)“不可重復(fù)讀”和“幻讀”。
“可重復(fù)讀(Repeated Red)”能預(yù)防啥?使用“快照讀(Snapshot Read)”,鎖住被讀取記錄,避免出現(xiàn)“臟讀”、“不可重復(fù)讀”,但是可能出現(xiàn)“幻讀”。
“串行化(Serializable)”能預(yù)防啥?排排坐,吃果果,有效避免“臟讀”、“不可重復(fù)讀”、“幻讀”,不過效果誰用誰知道。