在MySQL中,使用MVCC來實現REPEATABLE-READ隔離級別,由于SELECT操作不會對數據加鎖,其他回話可以修改當前回話所讀取過的數據而不會被阻塞,因此讀寫不沖突。
在MVCC并發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,并且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再并發修改這條記錄。(抄自MySQL 加鎖處理分析)
當事務中進行查詢時,MySQL會把快照讀和當前讀的結果進行合并再返回給客戶端,而這個合并可能導致一些奇特的結果。
生成測試數據:
drop table tb002; create table tb002(id int primary key,c2 int,unique index uni_c2(c2)); begin; insert into tb002(id,c2) select 1,1; insert into tb002(id,c2) select 2,2; insert into tb002(id,c2) select 4,4; commit;
假設有回話A和回話B,均使用REPEATABLE-READ隔離級別
##========================================================##
首先回話A執行SQL:
begin; select * from tb002;
返回結果如下:
##========================================================##
然后回話B執行SQL:
begin; delete from tb002 where id=2; commit;
由于回話A沒有加鎖,所以回話B能順利完成刪除并提交事務,當前數據庫中無C2=2的記錄,且會話B提交事務釋放鎖。
##========================================================##
回到回話A執行SQL:
insert into tb002(id,c2) select 3,2;
由于當前數據庫中無C2=2的記錄,且其他回話沒有在此C2=2的范圍上加鎖,因此回話A可以完成C2=2的數據插入。
在回話A上再次進行查詢:
select * from tb002;
返回結果如:
C2上有唯一索引,但為什么查詢結果中仍包含兩條C2=2的記錄呢?ID=2的記錄屬于快照讀的數據,ID=3的記錄數據當前讀的數據,MySQL將當前讀和快照讀的數據進行簡單的合并后返回給客戶端,并不檢查“結果數據是否滿足唯一索引”的要求。
##========================================================##
上面的測試針對唯一索引進行,那如果針對主鍵會有啥區別呢?
將插入SQL修改為:
insert into tb002(id,c2) select 2,3;
即回話B刪除的ID值和回話A新插入的ID值相同情況下,最后的查詢結果并不會包含兩條相同ID的記錄,對于“快照讀”和“當前讀”兩個結果集存在"主鍵沖突“的情況,最終返回客戶端的結果會”丟棄“快照讀中的”老版本“記錄,保留最新版本的記錄。
可見對于主鍵不存在上述問題。
##========================================================##
總結:
在基于MVCC實現的REPEATABLE-READ隔離級別下,由于快照讀和當前讀的影響,會導致返回數據結果集超過”期望結果集“的情況,甚至返回結果集中包含重復的”唯一索引鍵“,但返回結果集中不會包含重復的“主鍵”(PS:單表查詢的前提下)。
如果在事務中包含先插入后查詢的情況,應該考慮上述問題對業務的影響。
##========================================================##
文章列表