MySQL的可重複讀級別能解決幻讀嗎

2021-10-21 03:21:54 字數 4663 閱讀 1342

之前在深入了解資料庫理論的時候,了解到事物的不同隔離級別可能存在的問題。為了更好的理解所以在mysql資料庫中測試復現這些問題。關於髒讀和不可重複讀在相應的隔離級別下都很容易的復現了。但是對於幻讀,我發現在可重複讀的隔離級別下沒有出現,當時想到難道是mysql對幻讀做了什麼處理?

測試:建立一張測試用的表dept:

create table `dept` (

`id` int(11) not null auto_increment,

`name` varchar(20) default null,

primary key (`id`)

) engine=innodb auto_increment=12 default charset=utf8

insert into dept(name) values("後勤部")

事物 1

事物 2

begin

begin

select * from dept

-insert into dept(name) values("研發部")

-commit

select * from dept

commit

根據上面的流程執行,預期來說應該是事物1的第一條select查詢出一條資料,第二個select查詢出兩條資料(包含事物2提交的資料)。

但是在實際測試中發現第二條select實際上也只查詢處理一條資料。這是但是根據資料庫理論的可重複讀的實現(排他鎖和共享鎖)這是不應該的情況。

在了解實際原因前我們先複習下事物的相關理論。

事物事務(transaction),一般是指要做的或所做的事情。在計算機術語中是指訪問並可能更新資料庫中各種資料項的乙個程式執行單元(unit)。事務由事務開始(begin transaction)和事務結束(end transaction)之間執行的全體操作組成。在關聯式資料庫中,乙個事務可以是一組sql語句或整個程式。

為什麼要有事物

乙個資料庫事務通常包含對資料庫進行讀或寫的乙個操作序列。它的存在包含有以下兩個目的:

為資料庫操作提供了乙個從失敗中恢復到正常狀態的方法,同時提供了資料庫在異常狀態下仍能保持一致性的方法。

當多個應用程式在併發訪問資料庫時,可以在這些應用程式之間提供乙個隔離方法,保證彼此的操作互相干擾。

事物特性

事務具有4個特性:原子性、一致性、隔離性、永續性。這四個屬性通常稱為 acid 特性。

事物之間的幾個特性並不是一組同等的概念:

如果在任何時刻都只有乙個事物,那麼其天然是具有隔離性的,這時只要保證原子性就能具有一致性。

如果存在併發的情況下,就需要保證原子性和隔離性才能保證一致性。

資料庫併發事物中存在的問題

如果不考慮事務的隔離性,會發生以下幾種問題:

排他鎖,共享鎖

排它鎖(exclusive),又稱為x 鎖,寫鎖。

共享鎖(shared),又稱為s 鎖,讀鎖。

讀寫鎖之間有以下的關係:

即讀寫鎖之間的關係可以概括為:多讀單寫

事物的隔離級別

在事物中存在以下幾種隔離級別:

上面的內容解釋了一些資料庫理論的概念,但是在mysql、oracle這樣的資料庫中,為了效能的考慮並不是完全按照上面介紹的理論來實現的。

mvcc

多版本併發控制(multi-version concurrency control, mvcc)是mysql中基於樂觀鎖理論實現隔離級別的方式,用於實現讀已提交和可重複讀取隔離級別的實現。

實現(隔離級別為可重複讀)

在說到如何實現前先引入兩個概念:

系統版本號:乙個遞增的數字,每開始乙個新的事務,系統版本號就會自動遞增。

事務版本號:事務開始時的系統版本號。

在mysql中,會在表中每一條資料後面新增兩個字段:

建立版本號:建立一行資料時,將當前系統版本號作為建立版本號賦值

刪除版本號:刪除一行資料時,將當前系統版本號作為刪除版本號賦值

select

select時讀取資料的規則為:建立版本號<=當前事務版本號,刪除版本號為空或》當前事務版本號。

建立版本號<=當前事務版本號保證取出的資料不會有後啟動的事物中建立的資料。這也是為什麼在開始的示例中我們不會查出後來新增的資料的原因

刪除版本號為空或》當前事務版本號保證了至少在該事物開啟之前資料沒有被刪除,是應該被查出來的資料。

insert

insert時將當前的系統版本號賦值給建立版本號字段。

update

插入一條新紀錄,儲存當前事務版本號為行建立版本號,同時儲存當前事務版本號到原來刪除的行,實際上這裡的更新是通過delete和insert實現的。

delete

刪除時將當前的系統版本號賦值給刪除版本號字段,標識該行資料在那乙個事物中會被刪除,即使實際上在位commit時該資料沒有被刪除。根據select的規則後開啟懂資料也不會查詢到該資料。

mvcc真的解決了幻讀?

從最開始我們的測試示例和上面的理論支援來看貌似在mysql中通過mvcc就解決了幻讀的問題,那既然這樣序列化讀貌似就沒啥意義了,帶著疑問繼續測試。

測試前資料:

事物 1

事物 2

begin

begin

select * from dept

-insert into dept(name) values("研發部")

-commit

update dept set name="財務部"(工作中如果不想被辭退一定要寫where條件)

commit

根據上面的結果我們期望的結果是這樣的:

id  name

1 財務部

2 研發部

但是實際上我們的經過是:

本來我們希望得到的結果只是第一條資料的部門改為財務,但是結果確實兩條資料都被修改了。這種結果告訴我們其實在mysql可重複讀的隔離級別中並不是完全解決了幻讀的問題,而是解決了讀資料情況下的幻讀問題。而對於修改的操作依舊存在幻讀問題,就是說mvcc對於幻讀的解決時不徹底的。

快照讀和當前讀

出現了上面的情況我們需要知道為什麼會出現這種情況。在查閱了一些資料後發現在rr級別中,通過mvcc機制,雖然讓資料變得可重複讀,但我們讀到的資料可能是歷史資料,不是資料庫最新的資料。這種讀取歷史資料的方式,我們叫它快照讀 (snapshot read),而讀取資料庫最新版本資料的方式,叫當前讀 (current read)。

select 快照讀

當執行select操作是innodb缺省會執行快照讀,會記錄下這次select後的結果,之後select 的時候就會返回這次快照的資料,即使其他事務提交了不會影響當前select的資料,這就實現了可重複讀了。快照的生成當在第一次執行select的時候,也就是說假設當a開啟了事務,然後沒有執行任何操作,這時候b insert了一條資料然後commit,這時候a執行 select,那麼返回的資料中就會有b新增的那條資料。之後無論再有其他事務commit都沒有關係,因為快照已經生成了,後面的select都是根據快照來的。

當前讀對於會對資料修改的操作(update、insert、delete)都是採用當前讀的模式。在執行這幾個操作時會讀取最新的記錄,即使是別的事務提交的資料也可以查詢到。假設要update一條記錄,但是在另乙個事務中已經delete掉這條資料並且commit了,如果update就會產生衝突,所以在update的時候需要知道最新的資料。也正是因為這樣所以才導致上面我們測試的那種情況。

select的當前讀需要手動的加鎖:

select * from table where ? lock in share mode;

select * from table where ? for update;

有個問題說明下

在測試過程中最開始我以為使用begin語句就是開始乙個事物了,所以在上面第二次測試中因為先開始的事物1,結果在事物1中卻查到了事物2新增的資料,當時認為這和前面mvcc中的select的規則不一致了,所以做了如下測試:

select * from information_schema.innodb_trx //用於查詢當前正在執行中的事物
可以看到如果只是執行begin語句實際上並沒有開啟乙個事物。

下面在begin後新增一條select語句:

所以要明白實際上是對資料進行了增刪改查等操作後才開啟了乙個事物。

如何解決幻讀

很明顯可重複讀的隔離級別沒有辦法徹底的解決幻讀的問題,如果我們的專案中需要解決幻讀的話也有兩個辦法:

實際上很多的專案中是不會使用到上面的兩種方法的,序列化讀的效能太差,而且其實幻讀很多時候是我們完全可以接受的。

MySQL的可重複讀級別能解決幻讀嗎

之前在深入了解資料庫理論的時候,了解到事物的不同隔離級別可能存在的問題。為了更好的理解所以在mysql資料庫中測試復現這些問題。關於髒讀和不可重複讀在相應的隔離級別下都很容易的復現了。但是對於幻讀,我發現在可重複讀的隔離級別下沒有出現,當時想到難道是mysql對幻讀做了什麼處理?建立一張測試用的表d...

MySQL的可重複讀級別能解決幻讀嗎

之前在深入了解資料庫理論的時候,了解到事物的不同隔離級別可能存在的問題。為了更好的理解所以在mysql資料庫中測試復現這些問題。關於髒讀和不可重複讀在相應的隔離級別下都很容易的復現了。但是對於幻讀,我發現在可重複讀的隔離級別下沒有出現,當時想到難道是mysql對幻讀做了什麼處理?建立一張測試用的表d...

mysql 可重複讀。

一 可重複讀 我們先看看現象,再分析原理。我的mysql版本是5.5。下面是一張表,只有一條資料,並且我開啟了事物 此時,另乙個事物將record加1,因此我在開啟乙個命令列客戶端,執行下面的命令 成功加1之後,實際上,資料庫中record肯定是2。然後回到之前的客戶端,再查一次 沒毛病,recor...