您好,登錄后才能下訂單哦!
這篇文章主要講解了“什么是臟讀與幻讀”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“什么是臟讀與幻讀”吧!
select @@tx_isolation;
MySQL就包含4種隔離級別,隔離的當然是數據。要修改隔離級別的話,可以使用下面的SQL語句。
set session transaction isolation level read uncommitted; set session transaction isolation level read committed; set session transaction isolation level repeatable read; set session transaction isolation level serializable;
ok,我們創建一張小小的測試表,來看一下并發環境下的魔幻效果。
CREATE TABLE `xjjdog_tx` ( `id` INT(11) NOT NULL, `name` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci', `money` BIGINT(20) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ) COLLATE='utf8_general_ci' ENGINE=InnoDB ; INSERT INTO `xjjdog_tx` (`id`, `name`, `money`) VALUES (2, 'xjjdog1', 100); INSERT INTO `xjjdog_tx` (`id`, `name`, `money`) VALUES (1, 'xjjdog0', 100);
臟讀,意思就是讀出了臟數據。啥叫臟數據?就是另外一個事務還沒有提交的數據。在read uncommitted隔離級別下,就會出現臟讀。比如下面這個時序
事務 A:set session transaction isolation level read uncommitted; 事務 B:set session transaction isolation level read uncommitted; 事務 A:START TRANSACTION ; 事務 B:START TRANSACTION ; 事務 A:UPDATE xjjdog_tx SET money=money+100 WHERE NAME='xjjdog0'; 事務 B:UPDATE xjjdog_tx SET money=money+100 WHERE NAME='xjjdog0'; 事務 A:ROLLBACK ; 事務 B:COMMIT ; 事務 B:SELECT * FROM xjjdog_tx ;
在這個場景下,money的原始值為100,分別在兩個session中進行了加100的操作,然后回滾了其中的一個session事務。結果,經過查詢,發現money的值保持100不變。也就是其中一次加100的操作被覆蓋掉了。
所以臟讀發生有幾個條件。
高并發場景,在一個事務A開始之后還沒結束之前,有另外一個事務參與了事務A所涉及的數據行讀寫
事務隔離級別處于最低的讀未提交
在你使用到這些數據之后,事務A回滾,造成你之前拿到的數據已經不再存在
解決方式,只需要設置成隔離級別比read uncommitted高即可。
把隔離級別設置成read committed即可避免臟讀,這其實非常好理解。臟讀產生的根本原因就是在事務的執行期間有別的操作亂入,這個隔離級別要求事務A提交之后,修改后的值,才能被事務B讀到,所以臟讀是不可能會發生的,從根本上杜絕了。
但read commited會發生不可重復讀的情況。
顧名思義,就是在一個事務周期內,對于一個值的讀取,產生了兩個結果。
不可重復讀,證明了世界并不是總圍繞著你轉的。在你的事務執行期間,會有無數的其他事務執行,如果你的事務持續時間超過了這些事務,那么你就可能讀到兩個或者更多的值。
讓我來給你講一個故事。
從前,有一顆桃樹,長了12棵桃子。有一只猴子,叫做xjjdog,它想吃上面的桃子,但桃子還不熟。
第二天去看的時候,它發現桃子少了一個,變成了11個,經過仔細打聽,原來是被猴子A搶先吃掉一個。
第二天去看的時候,桃子又少了一個,變成了10個,原來是被饞嘴的猴子B吃掉一個。
如此這般,桃子一天天少了下去,只剩下最后的2個了,但桃子還是沒熟。
再不摘桃子就沒了,xjjdog摘下了最后的2個桃子,正打算大快朵頤,結果跳出一只猴子X,說我盯著這些桃子已經1年了...
在這故事中,猴子A、B的事務持續周期是1天;xjjdog的事務持續周期是直到桃子成熟;猴子X的持續周期更長,可能是一年。它們每天看到的桃子,并不總是12個。今天的桃子,可能被其他的猴子(事務)給吃掉了,造成了觀測的結果是不一樣的,這就是不可重復讀的概念。
有時候,即使讀到的值是一樣的,也不能證明沒問題。比如有財務挪用了2億去炒股,然后在月底把2億還了回來,雖然最終的金額都是一致的,但由于你的對賬周期長,就發現不了這種差異。
如何解決不可重復讀呢?先要看一下不可重復讀是不是問題。
有的系統,要求的就是這樣的邏輯,每次在事務中讀取到不一樣的值,它是可以忍受的。但如果你想要在桃子成熟之前,桃子的數量都在你的掌控之中,那不可重復讀就是一種問題。
一種非常好的方式,就是xjjdog一直站在桃樹地下。當有別的猴子想要摘桃,就把它趕走。這種方式可行,但在數據庫中非常低效,這是serializable級別的做法。
MySQL有一個默認的事務隔離級別,叫做repeatable read,使用了MVCC的方式(innodb),要更輕量級一些。
這就是MVCC(Multi-Version Concurrency Control)的功勞了,它有三個特點。
每行數據都存在一個版本,每次數據更新時都更新該版本
修改時,拷貝一份,當前版本隨意修改,事務之間無干擾
保存時比較版本號,如果成功commit覆蓋原記錄,失敗則rollback
MVCC在InnoDB中的實現主要是為了提高數據庫并發性能,用更好的方式去處理讀-寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞并發讀。它的實現關鍵也有三項技術:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
3個隱式字段:DB_TRX_ID,最近修改它的事務ID;DB_ROLL_PTR,回滾指針,指向上一個版本;DB_ROW_ID,隱藏主鍵
undo日志:的對同一記錄的修改,會生成針對此記錄的版本變更鏈表
read view:快照讀操作的時候,產生的讀視圖。除了使用上面的額外信息,它也會維護一個活躍的事務ID集合
一切的關鍵,就在于快照這兩個字上面。
比如事務A對某個記錄進行了快照讀,那么在快照讀的這一刻,就生成了一個Read View。在這一刻,事務B和C,還沒有commit,事務D和E,在建立ReadView那一刻之前,commit完成,那么這個Read View,就不能夠讀到B和C的修改。
但可惜的是,可重復讀,只能解決快照讀的不可重復讀,快照讀的時機,也會影響讀取的準確程度。請看下面兩種情況。
下面這種情況讀到的是500。
事務A | 事務B |
---|---|
開啟事務 | 開啟事務 |
快照讀(無影響)查詢金額為500 | 快照讀查詢金額為500 |
更新金額為400 | |
提交事務 | |
select 快照讀 金額為500 | |
select lock in share mode當前讀 金額為400 |
下面這種情況讀到的是400。
事務A | 事務B |
---|---|
開啟事務 | 開啟事務 |
快照讀(無影響)查詢金額為500 | |
更新金額為400 | |
提交事務 | |
select 快照讀 金額為400 | |
select lock in share mode當前讀 金額為400 |
(表格來自[SnailMann]的博客)。
幻讀,這個詞本身就非常的迷幻。在RU、RC、RR級別下,都會出現幻讀。
拿一個最簡單的例子來說。讓你select一條記錄是否存在然后打算進行后續插入時,如果這條記錄不存在,然后你執行了插入操作,但在實際執行插入操作的時候,結果卻報錯了,這條記錄已經存在了,這就是幻讀。
首先,確認目前時可重復讀級別。如果不是,則修改之。
SELECT @@tx_isolation # set session transaction isolation level repeatable read
讓我們來看一下這個靈異過程。
有5個步驟,我都給你標好了。下面一一介紹。
鴻蒙官方戰略合作共建——HarmonyOS技術社區
事務A使用begin開啟一個事務,然后查詢id為3的記錄,此時不存在。但由于快照讀開啟了一個針對于id為3的記錄的read view,所以在這個事務自始至終都不能夠讀到為3的記錄。很好,這就是我們不可重復讀所需要的
接下來,事務B插入了一條id為3的記錄,并提交成功
事務A此時也想插入這條記錄,于是執行了相同的插入操作,結果數據庫報錯,顯示這條記錄已經存在
事務A此時一臉懵逼,想看一下這條記錄到底是啥,但當它再次執行select語句的時候,卻查不到這條記錄
但在其他事務中,是可以看到這條記錄的,因為它已經正確提交
這就是幻讀。
幻讀有錯么?多數情況下沒錯,就是報錯怪異了些。要防止幻讀,需要開啟FOR UPDATE這樣高強度的鎖定,實際情況是非常少用。
為什么上面的操作,insert能報錯,但select卻無法查到數據呢?這就不得不提一下數據庫讀的兩種模式:
快照讀:普通的select操作,是從read view中讀取數據,讀取的可能是歷史數據
當前讀:insert、update、delete、select..for update這種操作,讀取的總是當前的最新數據
對于當前讀,你讀取的行,以及行的間隙都會被加鎖,直到事務提交時才會釋放,其他的事務無法進行修改,所以也不會出現不可重復讀、幻讀的情形。所以insert能夠發現沖突,而普通select卻不可以。要想解決幻讀,就需要加X鎖。在上面這種情況,就可以在事務A中執行:
SELECT * FROM xjjdog_tx WHERE id=3 FOR UPDATE
當這么做的時候,即使id為3的記錄不存在,它也會創建鎖(在背后可能根據記錄的存在與否加行X鎖或者next-key lock間隙x鎖)。
感謝各位的閱讀,以上就是“什么是臟讀與幻讀”的內容了,經過本文的學習后,相信大家對什么是臟讀與幻讀這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。