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的領域習,關注【南秋】帶你一起學習成長