如何验证 MySQL 的 InnoDB 在可重复读下依然会有幻影行问题及其原因
如何驗(yàn)證 MySQL 的 InnoDB 在可重復(fù)讀下依然會(huì)有幻影行問(wèn)題及其原因
- 驗(yàn)證的流程
- 自助驗(yàn)證
- 為什么 MySQL 的 InnoDB 在可重復(fù)讀下依然會(huì)有幻影行問(wèn)題
??很多人都知道,MySQL 的 InnoDB 在事務(wù)隔離級(jí)別 REPEATABLE READ 下解決了不可重復(fù)讀的問(wèn)題,但是依然有幻影行問(wèn)題。不過(guò)很多人都不知道這是為什么,也有很多錯(cuò)誤的解釋與驗(yàn)證。
??下面開(kāi)始驗(yàn)證。首先要區(qū)分兩個(gè)概念,正在觀察的事務(wù)、其它事務(wù)。正在觀察的事務(wù)指的是用于界定事務(wù)是否發(fā)生幻影行的事務(wù),正在觀察的事務(wù)只存在一個(gè)。其它事務(wù)是會(huì)對(duì)正在觀察的事務(wù)的操作進(jìn)行干擾的事務(wù),其它事務(wù)可不唯一。
驗(yàn)證的流程
驗(yàn)證的算法如下:
在 MySQL 創(chuàng)建任意一個(gè)數(shù)據(jù)庫(kù)、表,將引擎設(shè)為 InnoDB。然后在表中初始化任意的數(shù)據(jù)。
在不同的客戶端下分別開(kāi)啟對(duì)同一個(gè) MySQL 數(shù)據(jù)庫(kù)的連接。不妨將名字設(shè)為客戶端 A,客戶端 B。其中,客戶端 A 是進(jìn)行正在觀察的事務(wù)的客戶端,客戶端 B 是進(jìn)行其它事務(wù)的客戶端。
在客戶端 A、B 中設(shè)置會(huì)話范圍的可重復(fù)讀(REPEATABLE READ)事務(wù)隔離級(jí)別。
分別在客戶端 A,客戶端 B 中開(kāi)始事務(wù)。不妨將名字分別設(shè)為事務(wù) a,事務(wù) b。
分別在客戶端 A,客戶端 B 查詢?nèi)頂?shù)據(jù)。此時(shí),它們的查詢結(jié)果應(yīng)該是一樣的。
在事務(wù) b 中插入一個(gè)新的數(shù)據(jù)行,但不提交。
在事務(wù) a 中查詢?nèi)頂?shù)據(jù),此時(shí)的查詢結(jié)果應(yīng)該沒(méi)有變化,因?yàn)檫@是不會(huì)發(fā)生臟讀的特性。
在事務(wù) b 中提交事務(wù),事務(wù) b 結(jié)束。
在事務(wù) a 中查詢?nèi)頂?shù)據(jù),此時(shí)的查詢結(jié)果應(yīng)該仍然沒(méi)有變化,因?yàn)檫@是可重復(fù)讀的特性。
在事務(wù) a 中更新那個(gè)在事務(wù) b 被插入的數(shù)據(jù)行。雖然這個(gè)數(shù)據(jù)行沒(méi)有在前面事務(wù) a 的查詢中出現(xiàn),但此時(shí)在事務(wù) a 中卻顯示受到影響的行數(shù)為 1,這說(shuō)明更新操作成功。
在事務(wù) a 中查詢?nèi)頂?shù)據(jù),此時(shí)會(huì)發(fā)現(xiàn)在事務(wù) b 中被插入的那個(gè)行。這說(shuō)明出現(xiàn)了幻影行。因?yàn)槭聞?wù) a 并沒(méi)有插入任何數(shù)據(jù),它只是更新了一個(gè)在它眼里本來(lái)就不會(huì)存在的數(shù)據(jù)。如果沒(méi)有出現(xiàn)幻影行,那么事務(wù) a 中更新數(shù)據(jù)的時(shí)候,受到影響的行數(shù)應(yīng)該是 0。
至此,驗(yàn)證結(jié)束。
【驗(yàn)證的誤區(qū)】
以下情況下,不能算是驗(yàn)證了幻影行。
-
在客戶端 A 中,事務(wù) a 查詢數(shù)據(jù)后,提交了本事務(wù)(事務(wù) a 結(jié)束),然后又開(kāi)始查詢,并發(fā)現(xiàn)了數(shù)據(jù)的變化。
-
在客戶端 A 中,沒(méi)有顯式地開(kāi)始事務(wù),然后發(fā)現(xiàn)了兩次查詢結(jié)果之間的差異。
-
在事務(wù) a 中插入數(shù)據(jù)之后的查詢中發(fā)現(xiàn)了剛剛由事務(wù) a 插入的數(shù)據(jù)。
自助驗(yàn)證
為了便于讀者自行快速驗(yàn)證,這里給出了示例 MySQL 代碼,僅供參考。
# 建表 CREATE TABLE test(id INT,username VARCHAR(20) )ENGINE=InnoDB;# 初始化表中數(shù)據(jù) INSERT INTO test VALUES(1,'a'), (2,'b'),(3,'c'),(4,'d');# 設(shè)置會(huì)話范圍的可重復(fù)讀事務(wù)隔離級(jí)別 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;# 設(shè)置全局范圍的可重復(fù)讀事務(wù)隔離級(jí)別 SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;# 查看事務(wù)隔離級(jí)別 select @@transaction_isolation;# 開(kāi)始事務(wù) START TRANSACTION;# 查找全表數(shù)據(jù) SELECT * FROM test;# 更新表中數(shù)據(jù) UPDATE test SET username='fff' WHERE id=5;# 提交 COMMIT;# 回滾 ROLLBACK;為什么 MySQL 的 InnoDB 在可重復(fù)讀下依然會(huì)有幻影行問(wèn)題
??前面已經(jīng)驗(yàn)證了 MySQL 的 InnoDB 在可重復(fù)讀下依然會(huì)有幻影行問(wèn)題,現(xiàn)在來(lái)談?wù)勥@種情況為什么會(huì)發(fā)生。
??可重復(fù)讀是 InnoDB 通過(guò) MVCC(多版本并發(fā)控制,Multi-Version Concurrency Control)機(jī)制來(lái)實(shí)現(xiàn)的。InnoDB 會(huì)為每個(gè)數(shù)據(jù)行記錄行的創(chuàng)建時(shí)間、過(guò)期時(shí)間。以及每次查詢的行的版本號(hào)、查詢所在事務(wù)的版本號(hào)。這樣一來(lái),只要本事務(wù)沒(méi)有變更數(shù)據(jù),那么連續(xù)同條件的查詢結(jié)果應(yīng)該是一樣的。
??但是,這種機(jī)制只適用于查詢,更新數(shù)據(jù)不會(huì)從中獲益。更新數(shù)據(jù)時(shí),就算是更新前面查詢中不存在的數(shù)據(jù),這種更新也不會(huì)引發(fā)異常,甚至更新成功了,這就會(huì)導(dǎo)致 InnoDB 對(duì)行的版本進(jìn)行更新。由于這個(gè)更新是在本事務(wù)中進(jìn)行的,因此在更新之后的下一次查詢中將會(huì)出現(xiàn)這些數(shù)據(jù),也就是幻影行。
總結(jié)
以上是生活随笔為你收集整理的如何验证 MySQL 的 InnoDB 在可重复读下依然会有幻影行问题及其原因的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 剖析数据库中重要而又常被曲解的概念
- 下一篇: linux cmake编译源码,linu