mysql 快照讀和當前讀

2021-10-05 22:08:25 字數 3858 閱讀 4391

以下都是在可重複讀隔離級別情況下的:

快照讀:普通的不加鎖的select就是快照讀。通過readview實現,可重複讀級別時,整個事務的普通select都是使用同乙個readview。readview相關請看文章:

總之,可以理解為當前事務建立後,會立即生成乙個快照,查詢的結果都是基於這個快照。新的其他事務產生的資料不會影響到這個快照。

注意:資料庫事務版本號生成並不是在執行了 start transaction就會生成,而是在該事務執行第一條sql時才生成事務版本。為什麼要專門注意這個問題呢?因為之前我以為a事務執行完start transaction後(且沒有執行其他sql時),b事務再執行start transaction,並且緊接著插入一條資料並提交事務,此時我認為b事務的版本號是大於a事務的版本號的,在a事務中快照讀應該是不能查詢出b事務的資料的(快照讀只能查版本小於等於的資料)。但出乎意料的是a事務查到了b事務插入並提交的資料。。。所以才有上面的注意提示

當前讀:讀取最新版本已提交的資料。就算其他事務版本大於當前事務,但其他事務只要提交了的資料,當前事務使用當前讀是可以讀取到其他事務提交的資料的。而快照讀則無法讀取到其他新版事務提交的資料。。比如在a事務先開啟,但b事務insert資料且比a事務先提交時,b事務新插入的資料在a事務中也會被當前讀的方式查詢到。(即,當前讀可以讀比當前事務更新的事務所提交的資料)

當前讀包含了select..for update 、select ...lock in share mode、update、delete、replate into、insert等。這些都會查詢最新版本的資料。這些熟悉得人應該知道,都是加了鎖的。

如果a事務執行的比較慢,而b事務晚於a事務開啟,又早於a事務提交。在b事務提交後,如果a事務再次使用快照讀的情況,是讀取不到b事務提交的資料的。但這並沒有實際上解決幻讀問題。

舉個例子:

以這四條記錄為原始資料:

首先開啟a事務,然後使用快照讀查詢結果:

start transaction;

select * from test_2020_5_8 where name = '周利東';

上述查詢結果如下:

切換到b事務視窗:

開啟新事務、插入name為"周利東"的資料、提交事務

start transaction;

insert into test_2020_5_8 values(7,'周利東',15,'156***xx');

commit;

再切換回a事務視窗:

再執行一次快照讀:

select * from test_2020_5_8 where name = '周利東';
該快照讀結果如下(與b事務插入之前結果一直,符合可重複讀):

然後再試試當前讀,條件與之前的查詢一直,不過加了共享鎖:

select * from test_2020_5_8 where name = '周利東' lock in share mode;
結果如下:

前面快照讀只能查出事務b插入之前的兩條資料。。而當前讀則把事務b插入並提交後的最新資料也查出來了,不論b事務記錄版本號是否大於a事務,共查出三條結果。

其實這不就是一種幻讀麼?

幻讀的定義是:乙個事務(同乙個read view)在前後兩次查詢同一範圍的時候,後一次查詢看到了前一次查詢沒有看到的行。

如果前後兩次查詢都是使用快照讀,那麼讀取資料是一致的,這在某些場景下的確像是規避了幻讀。因為此時b事務新插入/刪除資料沒有影響到兩次查詢的結果。

但是如果a事務中不可避免的用了加了鎖的select、update、delete等操作中,使用的就會是當前讀,那麼這些操作中間接或者直接的查詢操作都是當前讀,會查詢到b事務插入並提交的資料。這就導致了幻讀。。

這時如果b事務新插入並提交的資料在a事務的update、delete等操作的篩選條件範圍內的話,那麼一種場景下會出現問題:

即a事務開啟事務後,b事務插入並提交了新資料,然後a事務再次快照讀的時候是查詢不到b事務新插入的資料,導致在**中做出一些認為資料不存在的操作(例如查詢資料不存在時,則新插入資料,這就會導致插入一條和b事務已經插入的重複資料或者報錯)。

這個場景演示如下:

還是這四條記錄為例:

開啟a事務,並快照讀一次:

start transaction;

select * from test_2020_5_8 where name = '周利東';

查詢結果如下:

假設此時a事務中某些邏輯執行的很慢,有乙個新的b事務開啟執行並提交:

切換新視窗,開啟b事務,插入name為周利東的資料,並提交b事務:

start transaction;

insert into test_2020_5_8 values(7,'周利東',15,'156***xx');

commit;

切回a事務,a事務繼續執行,再次快照讀:

select * from test_2020_5_8 where name = '周利東';
查詢結果如下(還是兩條):

此時a事務認為資料庫中資料還是這兩條,並不知道事務b又插入了一條。

在業務中經常會有查詢資料,如果不存在則新插入一條。所以,a事務查詢到這兩條資料後,發現沒有name為周利東的資料中沒有id為7的資料,a事務自己插入一條。語句如下

insert into test_2020_5_8 values(7,'周利東',15,'156***xx');
此時會報錯。。因為insert是當前讀,肯定能讀取到b事務插入並提交的id為7的資料,所以報錯如下:

這就是幻讀導致的問題。。

怎麼解決呢?

有兩種方式,

一、隔離級別設定為serializable序列執行,但資源消耗最大。

二、在a事務中不使用快照讀,也就說不使用不加鎖的select,給相關的select加上lock in share mode就好了,至於for update,這個看情況吧。讀寫鎖使用看場景決定。這樣也可以解決幻讀。。不過加鎖也還是會消耗效能的,並且小心死鎖。

注意:以下為猜測:

新增的資料在事務未提交之前還不會儲存當前版本號,當事務提交時,則把當前事務版本號儲存到新增記錄的隱藏列中

Mysql快照讀和當前讀

讀取的是記錄資料的可見版本 可能是過期的資料 不用加鎖 讀取的是記錄資料的最新版本,並且當前讀返回的記錄都會加上鎖,保證其他事務不會再併發的修改這條記錄 概念說的比較虛,也不好理解,接著舉乙個例子吧,假設你開啟了兩個事務,分別是a和b,這裡有個張表,user表,裡面有四條資料 x表示是排它鎖 exc...

MySql快照讀和當前讀

顧名思義,就是從當前狀態複製乙份快照出來,之後可以從快照裡讀取資料,但是並不是真正將整份資料複製的,而是利用了版本機制實現。簡單 select 不加鎖的語句就是快照讀。select from t user select from t user where id 1 全程在最新版本裡讀取資料。加鎖的 ...

當前讀和快照讀

在mvcc併發控制中,讀操作可以分成兩類 快照讀 snapshot read 與當前讀 current read 快照讀,讀取的是記錄的可見版本 有可能是歷史版本 不用加鎖。當前讀,讀取的是記錄的最新版本,並且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再併發修改這條記錄。innodb的預設事務...