在本文中,我們將了解MySQL幻讀出現的原因和解決方案

Mondo 科技 更新 2024-01-29

mysql 事務隔離有四個級別:

閱讀未提交:當乙個事務尚未提交時,該事務所做的更改可以被其他事務看到。 此隔離級別將包含髒讀取和不可重複讀取。

閱讀提交的內容:提交事務後,事務中所做的更改將被其他事務看到。 此隔離級別可解決髒讀問題,這將導致不可重複的讀取。

可重複的可讀性:指在交易執行過程中看到的資料,始終與交易開始時看到的資料一致。 此隔離級別解決了不可重複讀取的問題,這將導致幻像讀取。

序列化:對於同一行記錄,新增寫鎖和讀鎖。 如果發生讀/寫鎖衝突,稍後訪問的事務必須等待前乙個事務執行,然後才能繼續執行。 此隔離級別可解決幻像讀取問題。

髒讀:指讀取其他事務未提交的資料的事務。

不可重複讀取:指多次讀取相同的資料,結果集不一致(即在其他事務提交後讀取資料)。

幻像讀取是指事務對同一範圍進行兩次查詢,而後面的查詢看到的是上一次查詢中未看到的行。

建立乙個 t user 表。

-- 建立表 T 使用者 (使用者 ID bigint not null comment.)'使用者 ID', user_name varchar(100) null comment '使用者名稱', user_age int null, primary key (`user_id`),key `idx_user_name`(`user_name`))engine=innodb comment '使用者資訊表';--插入 t 個使用者值(1,'nq1',37),(5,'nq5',32),(10,'nq10',20),(15,'nq25',25);
哪裡:

使用者 ID 是主鍵。

使用者名字段建立索引。

使用者年齡欄位未建立索引。

執行語句:

begin; select * from t_user where user_age=37 for update;commit;
因此,在執行 SELECT 語句後,會在使用者 ID=1 的行中新增乙個寫鎖,並且根據兩階段鎖協議,在執行 commit 語句時會釋放這個寫鎖。

情景分析

在可重複隔離級別,假定 select * from t user where user age=37 for update 語句僅在 user age=37 行上鎖定。

如圖所示:

在會話 A 中執行三個查詢:步驟 1、步驟 2 和步驟 3。 第三個查詢語句是 select * from t where user age=37 for update:使用當前讀+寫鎖查詢使用者 age=37 的所有行。

查詢結果:在 t1 處,step1 僅返回使用者 id=1 的行記錄 (1,)'nq1',37)。

在 T2 時,會話 B 將使用者 id=5 行的使用者年齡值更改為 37,因此在 T3 時,step2 的查詢結果為使用者 id=1 和使用者 id=5 行(1,'nq1',37)、(5,'nq5',32)。

T4,會話 C 將一行 (2,) 插入到 T User 表中'nq2',37),所以在 T5 處,step3 的查詢結果為使用者 id=1、使用者 id=5、使用者 id=2'nq1',37)、(5,'nq5',32)、(2'nq2',37)。

在查詢結果中,使用者 id=2 的記錄稱為幻讀。

特別說明:在可重複讀隔離級別下,普通查詢是快照讀取,看不到其他事務插入的資料。 因此,只有在執行當前讀取時才會出現幻像讀取。

上述 ** 中會話 B 的修改結果,在會話 A 的當前讀取中看到時,不能稱為幻讀。 幻像讀取僅指新插入的行。

在上面的**中,會議A中的陳述:

select * from t where user_age=37 for update;
含義:查詢時,使用 for update 鎖定使用者 age=37 記錄。 在這種情況下,請向會話 B 和會話 C 新增兩個更新語句。

如圖所示:

其中,B場的第二份發言:

update t_user set user_name = 'nq55555' where user_id = 5;
語義如下:將 user id=5 和 user age=37 行中的使用者名字段的值更改為 nq55555。

在 T1 時,會話 A 只在 User ID=1 行中新增了乙個行鎖,因此會話 B 可以在時間 T2 執行 ** 中的兩個 update 語句,這會破壞會話 A 中鎖定 User age=37 的所有行的 Step 1 語句的鎖語義。 會話 C 與會話 B 一樣,破壞了會話 A 中 step1 語句的鎖定語義,該語句鎖定使用者年齡=37 的所有行。

鎖旨在確保資料一致性。 這種一致性不僅是資料庫內資料狀態的一致性,也是資料和日誌的邏輯一致性。

在上面的**中,將以下語句新增到會話 A:

update t_user set user_age = 40 where user_age = 37;
如圖所示:

會話 A 中步驟 1 的新增語句:

update t_user set user_age = 40 where user_age = 37;
語義如下:鎖定語句 user age=37,並將 user age 的值更新為 40。

處理步驟:1) 在 T1 時,會話 A 將使用者 id=1 記錄更新為 (1,)。'nq1',40),更新結果終於在T6時刻正式提交。

2) 在 T2 時,會話 B 將使用者 id=5 行更新為 (5,'nq55555',37)。

3) 在 T4 時,會話 C 向表中新增一行新的記錄 (2,)'nq55555',37)。

4) 其他行與此執行順序無關,保持不變。

上述語句執行完畢後,binlog 中的執行順序為:

/*(5,'nq5',37)*/update t_user set user_age = 37 where user_id = 5;/*(5,'nq55555',37)*/update t_user set user_name = 'nq55555' where user_id = 5;/*(2,'nq2',37)*/insert into t_user values(2,'nq2',37);/*(2,'nq55555',37)*/update t_user set user_name = 'nq55555' where user_id = 2;* 使用者年齡=37 的所有行都更新為 40* 更新使用者設定使用者年齡=40,其中使用者年齡=37;
最終,表中使用者 ID 為 2 和 5 的行的使用者年齡字段值更新為 40,並且出現資料不一致。

上述分析基於 select * from t user where user age=37 for update 語句,該語句僅鎖定使用者 age=37(即使用者 id=1)行。 因此,這種情況顯然是不合理的。

假設 select * from t 使用者掃瞄的所有行,其中 user age=37 for update 語句被鎖定。

如圖所示:

由於會話 A 在它掃瞄的所有行上都有寫鎖,因此會話 B 在執行第乙個更新語句時會進入阻塞狀態。 在 T6 提交會話 A 之前,會話 B 無法繼續。

上述語句執行完畢後,binlog 中的執行順序為:

/*(2,'nq2',37)*/insert into t_user values(2,'nq2',37);/*(2,'nq55555',37)*/update t_user set user_name = 'nq55555' where user_id = 2;* 使用者年齡=37 的所有行都更新為 40* 更新使用者設定使用者年齡=40,其中使用者年齡=37;/*(5,'nq5',37)*/update t_user set user_age = 37 where user_id = 5;/*(5,'nq55555',37)*/update t_user set user_name = 'nq55555' where user_id = 5;
如您所見,表中的行 user id=5 已正確執行,但資料庫中記錄的行 user id=2 的結果是 (2,'nq55555',37),binlog 中的結果是 (2,'nq55555',40),仍然存在幻象讀取問題。

幻像讀取的原因仍然發生:在 t3 處,當所有掃瞄的行都被語句 select * from t user (其中使用者年齡=37 更新)鎖定時,使用者 id=2 的記錄尚不存在。 因此,即使所有掃瞄的記錄都被鎖定,也不會阻止插入新記錄(即幻像讀取)。

基於以上分析,幻讀的原因是行鎖只能鎖住行,而不能鎖住行間隙。 為了解決幻象讀取問題,InnoDB引入了一種新的鎖,即間隙鎖。

間隙鎖定,用於鎖定兩個值之間的間隙。 例如,當 t user 初始化時,(1,'nq1',37),(5,'nq5',32),(10,'nq10',20),(15,'nq25',25)等,共產生5個缺口。

如圖所示

執行 select * from t user where user age=37 for update 語句時,將行鎖新增到四個初始化記錄中,並將間隙新增到與這四個記錄對應的五個間隙中。

其中:與行鎖衝突的那個是另乙個行鎖。

與間隙鎖衝突的是將記錄插入間隙的操作,間隙鎖之間沒有衝突。

縫隙鎖的影響

間隙鎖的引入解決了可重複隔離級別的幻象讀取問題,但它也有一些影響。

情景分析

程式: 1) 會話 A 執行 select ....對於 update 語句,由於行 user id=8 不存在,因此將新增乙個間隙鎖 (5,10)。

2) 執行會話 B 以選擇 ....對於更新語句,還會新增間隙鎖(5 和 10),間隙鎖之間不會相互衝突,因此可以成功執行語句。

3) 會話 B 嘗試在表中插入一行 (8,)'nq8',28),由於會話 A 中存在間隙鎖定,插入語句只能進入等待狀態。

4) 會話 A 嘗試在表中插入一行 (8,)'nq8',28),並且由於會話 B 中存在間隙鎖定,插入語句只能進入等待狀態。

此時,兩個會話將進入相互等待的狀態,形成僵局。 INNODB 的死鎖檢測檢測死鎖關係,因此 Ssession A 的 insert 語句返回錯誤資訊,並返回相應的訊息。

間隙鎖定的引入可能會導致同一語句鎖定更大的範圍,這必然會影響表操作的併發性。 當然,間隙鎖只在可重複的讀隔離級別生效,所以如果將隔離級別設定為讀提交,就不會有間隙鎖,但需要解決讀提交級別可能出現的資料和日誌不一致的問題,即將binlog格式設定為row。

間隙鎖和行鎖統稱為下鍵鎖,每個下鍵鎖都是乙個前開後閉段。 初始化表t使用者後,如果使用select * from t user for update語句鎖定整個表中的所有記錄,則會形成五個next鍵鎖:(1]、(1,5]、(5,10]、(10,15]、(15,+supremum)。 其中 supremum 是指 innodb 新增到每個索引中不存在的最大值,因此它符合開閉區間的定義。

幻影閱讀的主要內容:

1)幻讀定義:幻讀是指當乙個事務查詢同一範圍兩次,後面的查詢看到前乙個查詢沒有看到的行時。

2)出現幻象讀數的場景:幻象讀數只會出現在當前讀數下方,幻象讀數僅指新插入的行。

3)幻讀問題:語義問題和資料一致性問題。

4)幻像讀問題解決方案:在目前的讀條件下,MySQL通過引入間隙鎖來解決幻象讀問題。但是,間隙鎖可能會對錶操作的併發性產生影響。 間隙鎖只在可重複的讀隔離級別生效,所以如果將隔離級別設定為讀提交,則不會有間隙鎖,但需要解決讀提交級別可能出現的資料和日誌不一致問題,即將binlog格式設定為row。

閱讀推薦]更多精彩內容,如:

Redis 系列。

資料結構和演算法。

NACOS系列。

MySQL系列。

JVM 系列。

卡夫卡系列。

請移至【南秋】個人主頁參考。 內容不斷更新。

關於作者]熱愛科技、熱愛生活的老寶貝,專注J**A的領域習,關注【南秋】帶你一起學習成長

相關問題答案

    植物檢測鑑定,一文搞定!

    你有沒有想過你工作的工廠真的安全嗎?其結構是否有潛在的危險正在悄悄消失?廠房的檢查鑑定,揭開了這棟樓的秘密,讓我們玩得開心。什麼是工廠檢驗和鑑定?工廠檢查和評估是對工業工廠進行全面審查和評估的過程。這不僅包括對物理結構的檢查,還包括建築材料的狀況 裝置的執行以及潛在風險的識別。這是對工作環境的保證和...

    在一篇文章中了解它!奇瑞發現06城市版和悅眼版哪個更適合你?

    今年月,奇瑞推出Discovery Yueye版。作為Discovery系列的首款車型,它以全新的造型專注於輕型越野市場。緊接著,奇瑞推出了探索都市版,乙個強調更多城市場景 不同產品形象的版本,也是奇瑞在海外推出的探索版,通過全面構建 城市與荒野 的產品布局,進一步完善了探索的產品布局。為什麼硬核S...

    在一篇文章中了解國債 什麼是國債?有什麼特點?

    意義 國債是指國家根據其信用向社會發行的一種債務憑證,承諾償還本息。 產品特點 .國債是一種低風險的投資工具,因為它們具有最高的信用評級,違約的可能性非常低。它被稱為 鍍金 .國債可以提供穩定的利息收入,通常高於銀行的利率。.國債可以在 市場交易,可以根據市場變化實現資金的靈活配置和增值。點選新增說...

    一文搞定,2024年最新養老金規則及計算方法!

    年最新的養老金規則和計算方法已經出台,這直接關係到我們每個人的切身利益。讓我們來看看新規則的主要變化。這次養老金規則主要有三點調整 接收它的條件更加嚴格。為了領取養老金,您必須繳納 年的養老保險並達到退休年齡。這有效地防止了那些想提前退休或 吃空工資 的人。計算方法更科學。新規採用了更加科學的計算方...

    了解路由器和貓的功能在一篇文章中選擇和使用

    哈嘍大家好,今天就來看看我們在日常生活中經常接觸到的兩種裝置,但可能不太了解 路由器和貓咪。.路由器 家庭網路的橋梁路由器,我們每天使用的這個裝置,你真的了解它嗎?路由器就像一座橋梁,連線著不同的網路,無論是家庭網路還是廣域網,它將網路連線在一起,使它們能夠相互通訊。想象一下,您家中有多個裝置需要連...