幻讀是指:在一個事務(wù)中,讀取到了其他已經(jīng)提交的事務(wù)插入的數(shù)據(jù)行。
專注于為中小企業(yè)提供成都網(wǎng)站建設(shè)、成都做網(wǎng)站服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)阿城免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了成百上千企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
MySQL在解決臟讀、不可重復(fù)的讀時候,使用了MVCC一致性視圖,同時配合行鎖來解決。
至于幻讀的解決方式,MySQL引入了臨鍵鎖,通過間隙鎖可以避免在兩個行之間插入數(shù)據(jù),從而避免了一個事務(wù)在讀取的過程中,讀取到其他事務(wù)插入的數(shù)據(jù)行。
今天我們來看看多個事務(wù)對緩存頁里的同一條數(shù)據(jù)同時進行更新或者查詢,此時會產(chǎn)生哪些問題?這里實際會涉及到 臟寫、臟讀、不可重復(fù)讀、幻讀, 四中問題。
這個臟寫的話,它的意思是說有兩個事務(wù),事務(wù)A和事務(wù)B同時在更新一條數(shù)據(jù),事務(wù)A先把它更新為A值,事務(wù)B緊接著就把它更新為B值。事務(wù)A是先更新的,它在更新之前,這行數(shù)據(jù)的值為NULL,所以此時事務(wù)A的undo log日志大概是這樣的:更新之前這行數(shù)據(jù)的值為NULL,主鍵為XX 。
那么此時事務(wù)B更新完了數(shù)據(jù)的值為B,結(jié)果此時事務(wù)A突然回滾了,那么就會用它的undo log日志去回滾。此時事務(wù)A一回滾,直接就會把那行數(shù)據(jù)的值更新回之前的NULL值。所以對于事務(wù)B看到的場景,就是自己明明更新了,結(jié)果值卻沒了,這就是 臟寫。
假設(shè)事務(wù)A更新了一行數(shù)據(jù)的值為A,此時事務(wù)B去查詢了一些這行數(shù)據(jù)的值,看到的值是A,然后事務(wù)B拿著剛查詢到的A值去處理各種業(yè)務(wù)。但是此時不幸的事情發(fā)生了,事務(wù)A突然回滾了,導(dǎo)致它剛才更新的A值沒了,此時那行數(shù)據(jù)的值回滾為NULL值。這就是所謂的 臟讀。 它的本質(zhì)是事務(wù)B去查詢了事務(wù)A修改過的數(shù)據(jù),但是此時事務(wù)A還沒有提交,事務(wù)A隨時會回滾導(dǎo)致事務(wù)B查詢了一個不存在的值。
接著我們來看一下 的問題,假設(shè)我們有一個事務(wù)A開啟了,在這個事務(wù)A里會多次對一條數(shù)據(jù)進行查詢。然后另外有兩個事務(wù),一個是事務(wù)B,一個是事務(wù)C,它們都是對一條數(shù)據(jù)進行更新的。假設(shè)緩存頁里一條數(shù)據(jù)原來的值是A值,此時事務(wù)A開啟之后,第一次查詢這條數(shù)據(jù),讀取到的是A值。接著事務(wù)B更新了那行數(shù)據(jù)的值為B,同時提交事務(wù),然后事務(wù)A第二次查詢該行數(shù)據(jù),此時查到的是事務(wù)B修改過的值B 。接著事務(wù)C更新了那行數(shù)據(jù)的值為C,同時提交事務(wù),然后事務(wù)A第三次查詢該行數(shù)據(jù),此時查到的是事務(wù)C修改過的值C值。
那么上面的場景有什么問題呢?其實要說沒問題也是可以的,畢竟事務(wù)B和C都提交事務(wù)了。但是要說有問題也是可以的,就是事務(wù)A可能第一次查詢到的是A值,那么它可能希望的是在事務(wù)執(zhí)行期間,如果多次查詢數(shù)據(jù),都是同樣的一個A值。但是該場景下,A值明顯不是可重復(fù)讀的。
這種情況算不算一個問題呢?其實這是根據(jù)你的業(yè)務(wù)決定的。有的業(yè)務(wù)要的是可重復(fù)讀,而有的業(yè)務(wù)卻需要不可重復(fù)讀。
假設(shè)一個事務(wù)A先發(fā)送一條SQL語句,里面有一個條件,要查詢一批數(shù)據(jù)出來,比如“select * from table where id 10”,類似這種SQL,它一開始查詢出了10條數(shù)據(jù)。然后事務(wù)B往表里插入了幾條數(shù)據(jù),而且事務(wù)B還提交了。此時事務(wù)A再次查詢,由于事務(wù)B插入了幾條數(shù)據(jù),導(dǎo)致這次它查詢出來了12條數(shù)據(jù)。同樣的SQL語句,兩次的查詢結(jié)果卻不一樣,所以開始懷疑自己是不是出現(xiàn)了幻覺?導(dǎo)致剛才幻讀了?這就是幻讀一詞的由來。
在SQL標準中規(guī)定了4種事務(wù)隔離級別,就是說多個事務(wù)并發(fā)運行的時候,互相是如何隔離的,從而避免一些事務(wù)并發(fā)問題。這4種級別包括了: read uncommitted(讀未提交)、read committed(讀已提交)、repeatable read(可重復(fù)讀)、serializable(串行化) 。
第一個read uncommitted隔離級別是不允許發(fā)生臟寫的。也就是說,不可能兩個事務(wù)在沒提交的情況下去更新同一行數(shù)據(jù)的值,但是在這種隔離級別下,可能發(fā)生臟讀、不可重復(fù)讀、幻讀。所以一般來說,是沒有人做系統(tǒng)開發(fā)的時候把事務(wù)隔離級別設(shè)置為讀未提交這個級別的。
第二個是read committed隔離級別,也就是俗稱的RC級別,這個級別不會發(fā)生臟寫和臟讀。也就是說,別的事務(wù)沒提交的情況下修改的值,你是絕對讀不到的。但是,可能會發(fā)生不可重復(fù)讀和幻讀問題。
第三個是repeatable read隔離級別,也就是俗稱的RR級別,就是可重復(fù)讀級別。這個級別下,不會發(fā)生臟寫、臟讀、不可重復(fù)讀的問題。事務(wù)一旦開啟,多次查詢一個值,會一直讀到同一個值。但是它會發(fā)生幻讀的問題。
最后一個隔離級別,就是serializable級別,這種級別,根本不允許多個事務(wù)并發(fā)執(zhí)行,只能串行執(zhí)行,所以不可能有幻讀問題。但是這種級別一般除非腦子壞了,否則不可能設(shè)置這種級別。
MySQL默認設(shè)置的事務(wù)隔離級別都是RR級別的,而且MySQL的RR級別是可以避免幻讀發(fā)生的。
下面的命令可以修改MySQL的默認事務(wù)隔離級別:
另外,給大家一個彩蛋,假設(shè)你在開發(fā)業(yè)務(wù)系統(tǒng)的時候,比如用spring里的@Transaction注解來做事務(wù)這塊,假設(shè)某個事務(wù)你就是有點手癢,想搞成RC級別,那么沒問題,在@Transaction注解里是有一個isolation參數(shù)的,里面是可以設(shè)置事務(wù)隔離級別的,具體的設(shè)置方式如下:
@Transaction(isolation=Isolation.DEFAULT),默認的就是DEFAULT值,這個就是MySQL默認支持什么隔離就是什么隔離級別。但是你可以手動改成其它的隔離級別,比如,isolation = Isolation.READ_COMMITTED級別,此時你就可以讀取到其它事務(wù)已提交的數(shù)據(jù)。
簡單來說,我們每條數(shù)據(jù)其實都有兩個隱藏字段,一個是trx_id,一個是roll_pointer,這個trx_id就是最近一次更新這條數(shù)據(jù)的事務(wù)id,roll_pointer就是指向了你更新這個事務(wù)之前生成的undo log,關(guān)于undo log之前都講過了。
舉個例子,假設(shè)有一個事務(wù)A(id=50),插入了一條數(shù)據(jù),那么此時這條數(shù)據(jù)的隱藏字段以及指向的undo log如下圖所示:
插入的這條數(shù)據(jù)的值是A,因為事務(wù)A的id是50,所以這條數(shù)據(jù)的trx_id就是50,roll_pointer指向一個空的undo log,因為之前這條數(shù)據(jù)是沒有的。接著有一個事務(wù)B修改了一下這條數(shù)據(jù),把值改成了B,事務(wù)B的id是58,那么此時更新之前會生成一個undo log記錄之前的值,然后會讓roll_pointer指向這個實際的undo log回滾日志,如下圖所示:
3). 幻讀 :
是指當(dāng)事務(wù)不是獨立執(zhí)行時發(fā)生的一種現(xiàn)象,例如第一個事務(wù)對一個表中的數(shù)據(jù)進行了修改,這種修改涉及到表中的全部數(shù)據(jù)行。同時,第二個事務(wù)也修改這個表中的數(shù)據(jù),這種修改是向表中插入一行新數(shù)據(jù)。那么,以后就會發(fā)生操作第一個事務(wù)的用戶發(fā)現(xiàn)表中還有沒有修改的數(shù)據(jù)行,就好象發(fā)生了幻覺一樣。例如,一個編輯人員更改作者提交的文檔,但當(dāng)生產(chǎn)部門將其更改內(nèi)容合并到該文檔的主復(fù)本時,發(fā)現(xiàn)作者已將未編輯的新材料添加到該文檔中。如果在編輯人員和生產(chǎn)部門完成對原始文檔的處理之前,任何人都不能將新材料添加到文檔中,則可以避免該問題。
學(xué)習(xí)msyql隔離級別,事務(wù),行排它鎖,行共享鎖,樂觀鎖,悲觀鎖即可
這個過程實際上會涉及到 臟寫、臟讀、不可重復(fù)讀、幻讀 ,四種問題。
MySQL默認的事務(wù)隔離級別是RR(可重復(fù)讀),而且 MySQL的RR級別是可以避免幻讀發(fā)生 。也就是說,MySQL里執(zhí)行的事務(wù),默認情況下不會發(fā)生臟寫、臟讀、不可重復(fù)讀和幻讀的問題。
如何修改MySQL隔離級別?
Spring中默認隔離級別與MySQL一致,Spring中如何修改?
簡單來說,就是執(zhí)行一個事務(wù)的時候,就生成一個ReadView,里面比較關(guān)鍵的東西有4個:
示例:
通過undo log多版本鏈條,加上你開啟事務(wù)時候生產(chǎn)的一個ReadView,然后再有一個查詢的時候,根據(jù)ReadView進行判斷的機制,你就知道你應(yīng)該讀取哪個版本的數(shù)據(jù)。
首先我們先要明白,多個事務(wù)并發(fā)運行的時候,同時讀寫一個數(shù)據(jù),可能會出現(xiàn)臟寫、臟讀、不可重復(fù)讀、幻讀幾個問題。
針對這些問題,所以才有RU、RC、RR和串行四個隔離級別。
然后MySQL實現(xiàn)MVCC機制的時候,是 基于undo log多版本鏈條+ReadView機制 來做的,默認的RR隔離級別,就是基于這套機制來實現(xiàn)的,依托這套機制實現(xiàn)了RR級別,除了避免臟寫、臟讀、不可重復(fù)讀,還能避免幻讀問題。因此一般來說我們都用默認的RR隔離級別就好了。
程序中添加事物處理,同表操作添加鎖,這樣可以防止MySQL出現(xiàn)臟數(shù)據(jù)。