0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

MySQL事務(wù)的四大隔離級(jí)別詳解

數(shù)據(jù)分析與開(kāi)發(fā) ? 來(lái)源:撿田螺的小男孩 ? 作者:撿田螺的小男孩 ? 2020-11-27 16:07 ? 次閱讀

之前分析一個(gè)死鎖問(wèn)題,發(fā)現(xiàn)自己對(duì)數(shù)據(jù)庫(kù)隔離級(jí)別理解還不夠深入,所以趁著這幾天假期,整理一下MySQL事務(wù)的四大隔離級(jí)別相關(guān)知識(shí),希望對(duì)大家有幫助~

事務(wù)

什么是事務(wù)?

事務(wù),由一個(gè)有限的數(shù)據(jù)庫(kù)操作序列構(gòu)成,這些操作要么全部執(zhí)行,要么全部不執(zhí)行,是一個(gè)不可分割的工作單位。

假如A轉(zhuǎn)賬給B 100 元,先從A的賬戶(hù)里扣除 100 元,再在 B 的賬戶(hù)上加上 100 元。如果扣完A的100元后,還沒(méi)來(lái)得及給B加上,銀行系統(tǒng)異常了,最后導(dǎo)致A的余額減少了,B的余額卻沒(méi)有增加。所以就需要事務(wù),將A的錢(qián)回滾回去,就是這么簡(jiǎn)單。

事務(wù)的四大特性

原子性:事務(wù)作為一個(gè)整體被執(zhí)行,包含在其中的對(duì)數(shù)據(jù)庫(kù)的操作要么全部都執(zhí)行,要么都不執(zhí)行。

一致性:指在事務(wù)開(kāi)始之前和事務(wù)結(jié)束以后,數(shù)據(jù)不會(huì)被破壞,假如A賬戶(hù)給B賬戶(hù)轉(zhuǎn)10塊錢(qián),不管成功與否,A和B的總金額是不變的。

隔離性:多個(gè)事務(wù)并發(fā)訪問(wèn)時(shí),事務(wù)之間是相互隔離的,一個(gè)事務(wù)不應(yīng)該被其他事務(wù)干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離。。

持久性:表示事務(wù)完成提交后,該事務(wù)對(duì)數(shù)據(jù)庫(kù)所作的操作更改,將持久地保存在數(shù)據(jù)庫(kù)之中。

事務(wù)并發(fā)存在的問(wèn)題

事務(wù)并發(fā)執(zhí)行存在什么問(wèn)題呢,換句話說(shuō)就是,一個(gè)事務(wù)是怎么干擾到其他事務(wù)的呢?看例子吧~

假設(shè)現(xiàn)在有表:

CREATE TABLE `account`(

`id`int(11) NOT NULL,

`name` varchar(255) DEFAULT NULL,

`balance`int(11) DEFAULT NULL,

PRIMARY KEY (`id`),

UNIQUE KEY `un_name_idx`(`name`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表中有數(shù)據(jù):

臟讀(dirty read

假設(shè)現(xiàn)在有兩個(gè)事務(wù)A、B:

假設(shè)現(xiàn)在A的余額是100,事務(wù)A正在準(zhǔn)備查詢(xún)Jay的余額

這時(shí)候,事務(wù)B先扣減Jay的余額,扣了10

最后A 讀到的是扣減后的余額

由上圖可以發(fā)現(xiàn),事務(wù)A、B交替執(zhí)行,事務(wù)A被事務(wù)B干擾到了,因?yàn)槭聞?wù)A讀取到事務(wù)B未提交的數(shù)據(jù),這就是臟讀。

不可重復(fù)讀(unrepeatable read)

假設(shè)現(xiàn)在有兩個(gè)事務(wù)A和B:

事務(wù)A先查詢(xún)Jay的余額,查到結(jié)果是100

這時(shí)候事務(wù)B 對(duì)Jay的賬戶(hù)余額進(jìn)行扣減,扣去10后,提交事務(wù)

事務(wù)A再去查詢(xún)Jay的賬戶(hù)余額發(fā)現(xiàn)變成了90

事務(wù)A又被事務(wù)B干擾到了!在事務(wù)A范圍內(nèi),兩個(gè)相同的查詢(xún),讀取同一條記錄,卻返回了不同的數(shù)據(jù),這就是不可重復(fù)讀。

幻讀

假設(shè)現(xiàn)在有兩個(gè)事務(wù)A、B:

事務(wù)A先查詢(xún)id大于2的賬戶(hù)記錄,得到記錄id=2和id=3的兩條記錄

這時(shí)候,事務(wù)B開(kāi)啟,插入一條id=4的記錄,并且提交了

事務(wù)A再去執(zhí)行相同的查詢(xún),卻得到了id=2,3,4的3條記錄了。

事務(wù)A查詢(xún)一個(gè)范圍的結(jié)果集,另一個(gè)并發(fā)事務(wù)B往這個(gè)范圍中插入/刪除了數(shù)據(jù),并靜悄悄地提交,然后事務(wù)A再次查詢(xún)相同的范圍,兩次讀取得到的結(jié)果集不一樣了,這就是幻讀。

事務(wù)的四大隔離級(jí)別實(shí)踐

既然并發(fā)事務(wù)存在臟讀、不可重復(fù)、幻讀等問(wèn)題,InnoDB實(shí)現(xiàn)了哪幾種事務(wù)的隔離級(jí)別應(yīng)對(duì)呢?

讀未提交(Read Uncommitted)

讀已提交(Read Committed)

可重復(fù)讀(Repeatable Read)

串行化(Serializable)

讀未提交(Read Uncommitted)

想學(xué)習(xí)一個(gè)知識(shí)點(diǎn),最好的方式就是實(shí)踐之。好了,我們?nèi)?shù)據(jù)庫(kù)給它設(shè)置讀未提交隔離級(jí)別,實(shí)踐一下吧~

先把事務(wù)隔離級(jí)別設(shè)置為read uncommitted,開(kāi)啟事務(wù)A,查詢(xún)id=1的數(shù)據(jù)

set session transaction isolation level read uncommitted;

begin;

select* from account where id =1;

結(jié)果如下:

這時(shí)候,另開(kāi)一個(gè)窗口打開(kāi)mysql,也把當(dāng)前事務(wù)隔離級(jí)別設(shè)置為read uncommitted,開(kāi)啟事務(wù)B,執(zhí)行更新操作

set session transaction isolation level read uncommitted;

begin;

update account set balance=balance+20where id =1;

接著回事務(wù)A的窗口,再查account表id=1的數(shù)據(jù),結(jié)果如下:

可以發(fā)現(xiàn),在讀未提交(Read Uncommitted)隔離級(jí)別下,一個(gè)事務(wù)會(huì)讀到其他事務(wù)未提交的數(shù)據(jù)的,即存在臟讀問(wèn)題。事務(wù)B都還沒(méi)commit到數(shù)據(jù)庫(kù)呢,事務(wù)A就讀到了,感覺(jué)都亂套了。。。實(shí)際上,讀未提交是隔離級(jí)別最低的一種。

讀已提交(READ COMMITTED)

為了避免臟讀,數(shù)據(jù)庫(kù)有了比讀未提交更高的隔離級(jí)別,即讀已提交。

把當(dāng)前事務(wù)隔離級(jí)別設(shè)置為讀已提交(READ COMMITTED),開(kāi)啟事務(wù)A,查詢(xún)account中id=1的數(shù)據(jù)

set session transaction isolation level read committed;

begin;

select* from account where id =1;

另開(kāi)一個(gè)窗口打開(kāi)mysql,也把事務(wù)隔離級(jí)別設(shè)置為read committed,開(kāi)啟事務(wù)B,執(zhí)行以下操作

set session transaction isolation level read committed;

begin;

update account set balance=balance+20where id =1;

接著回事務(wù)A的窗口,再查account數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)沒(méi)變:

我們?cè)偃サ绞聞?wù)B的窗口執(zhí)行commit操作:

commit;

最后回到事務(wù)A窗口查詢(xún),發(fā)現(xiàn)數(shù)據(jù)變了:

由此可以得出結(jié)論,隔離級(jí)別設(shè)置為已提交讀(READ COMMITTED)時(shí),已經(jīng)不會(huì)出現(xiàn)臟讀問(wèn)題了,當(dāng)前事務(wù)只能讀取到其他事務(wù)提交的數(shù)據(jù)。但是,你站在事務(wù)A的角度想想,存在其他問(wèn)題嗎?

讀已提交的隔離級(jí)別會(huì)有什么問(wèn)題呢?

在同一個(gè)事務(wù)A里,相同的查詢(xún)sql,讀取同一條記錄(id=1),讀到的結(jié)果是不一樣的,即不可重復(fù)讀。所以,隔離級(jí)別設(shè)置為read committed的時(shí)候,還會(huì)存在不可重復(fù)讀的并發(fā)問(wèn)題。

可重復(fù)讀(Repeatable Read)

如果你的老板要求,在同個(gè)事務(wù)中,查詢(xún)結(jié)果必須是一致的,即老板要求你解決不可重復(fù)的并發(fā)問(wèn)題,怎么辦呢?老板,臣妾辦不到?來(lái)實(shí)踐一下可重復(fù)讀(Repeatable Read)這個(gè)隔離級(jí)別吧~

哈哈,步驟1、2、6的查詢(xún)結(jié)果都是一樣的,即repeatable read解決了不可重復(fù)讀問(wèn)題,是不是心里美滋滋的呢,終于解決老板的難題了~

RR級(jí)別是否解決了幻讀問(wèn)題呢?

再來(lái)看看網(wǎng)上的一個(gè)熱點(diǎn)問(wèn)題,有關(guān)于RR級(jí)別下,是否解決了幻讀問(wèn)題?我們來(lái)實(shí)踐一下:

由圖可得,步驟2和步驟6查詢(xún)結(jié)果集沒(méi)有變化,看起來(lái)RR級(jí)別是已經(jīng)解決幻讀問(wèn)題了~但是呢,RR級(jí)別還是存在這種現(xiàn)象:

其實(shí),上圖如果事務(wù)A中,沒(méi)有 update accountsetbalance=200whereid=5;這步操作, select*fromaccountwhereid>2查詢(xún)到的結(jié)果集確實(shí)是不變,這種情況沒(méi)有幻讀問(wèn)題。但是,有了update這個(gè)騷操作,同一個(gè)事務(wù),相同的sql,查出的結(jié)果集不同,這個(gè)是符合了幻讀的定義~

這個(gè)問(wèn)題,親愛(ài)的朋友,你覺(jué)得它算幻讀問(wèn)題嗎?

串行化(Serializable)

前面三種數(shù)據(jù)庫(kù)隔離級(jí)別,都有一定的并發(fā)問(wèn)題,現(xiàn)在放大招吧,實(shí)踐SERIALIZABLE隔離級(jí)別。

把事務(wù)隔離級(jí)別設(shè)置為Serializable,開(kāi)啟事務(wù)A,查詢(xún)account表數(shù)據(jù)

set session transaction isolation level serializable;

select@@tx_isolation;

begin;

select* from account;

另開(kāi)一個(gè)窗口打開(kāi)mysql,也把事務(wù)隔離級(jí)別設(shè)置為Serializable,開(kāi)啟事務(wù)B,執(zhí)行插入一條數(shù)據(jù):

set session transaction isolation level serializable;

select@@tx_isolation;

begin;

insert into account(id,name,balance) value(6,'Li',100);

執(zhí)行結(jié)果如下:

由圖可得,當(dāng)數(shù)據(jù)庫(kù)隔離級(jí)別設(shè)置為serializable的時(shí)候,事務(wù)B對(duì)表的寫(xiě)操作,在等事務(wù)A的讀操作。其實(shí),這是隔離級(jí)別中最嚴(yán)格的,讀寫(xiě)都不允許并發(fā)。它保證了最好的安全性,性能卻是個(gè)問(wèn)題~

MySql隔離級(jí)別的實(shí)現(xiàn)原理

實(shí)現(xiàn)隔離機(jī)制的方法主要有兩種:

讀寫(xiě)鎖

一致性快照讀,即 MVCC

MySql使用不同的鎖策略(Locking Strategy)/MVCC來(lái)實(shí)現(xiàn)四種不同的隔離級(jí)別。RR、RC的實(shí)現(xiàn)原理跟MVCC有關(guān),RU和Serializable跟鎖有關(guān)。

讀未提交(Read Uncommitted)

官方說(shuō)法:

SELECT statements are performed in a nonlocking fashion, but a possible earlier version of a row might be used. Thus, using this isolation level, such reads are not consistent.

讀未提交,采取的是讀不加鎖原理。

事務(wù)讀不加鎖,不阻塞其他事務(wù)的讀和寫(xiě)

事務(wù)寫(xiě)阻塞其他事務(wù)寫(xiě),但不阻塞其他事務(wù)讀;

串行化(Serializable)

官方的說(shuō)法:

InnoDB implicitly converts all plain SELECT statements to SELECT ... FOR SHARE if autocommit is disabled. If autocommit is enabled, the SELECT is its own transaction. It therefore is known to be read only and can be serialized if performed as a consistent (nonlocking) read and need not block for other transactions. (To force a plain SELECT to block if other transactions have modified the selected rows, disable autocommit.)

所有SELECT語(yǔ)句會(huì)隱式轉(zhuǎn)化為SELECT...FOR SHARE,即加共享鎖。

讀加共享鎖,寫(xiě)加排他鎖,讀寫(xiě)互斥。如果有未提交的事務(wù)正在修改某些行,所有select這些行的語(yǔ)句都會(huì)阻塞。

MVCC的實(shí)現(xiàn)原理

MVCC,中文叫多版本并發(fā)控制,它是通過(guò)讀取歷史版本的數(shù)據(jù),來(lái)降低并發(fā)事務(wù)沖突,從而提高并發(fā)性能的一種機(jī)制。它的實(shí)現(xiàn)依賴(lài)于隱式字段、undo日志、快照讀&當(dāng)前讀、Read View,因此,我們先來(lái)了解這幾個(gè)知識(shí)點(diǎn)。

隱式字段

對(duì)于InnoDB存儲(chǔ)引擎,每一行記錄都有兩個(gè)隱藏列DBTRXID,DBROLLPTR,如果表中沒(méi)有主鍵和非NULL唯一鍵時(shí),則還會(huì)有第三個(gè)隱藏的主鍵列 DBROWID。

DBTRXID,記錄每一行最近一次修改(修改/更新)它的事務(wù)ID,大小為6字節(jié);

DBROLLPTR,這個(gè)隱藏列就相當(dāng)于一個(gè)指針,指向回滾段的undo日志,大小為7字節(jié);

DBROWID,單調(diào)遞增的行ID,大小為6字節(jié);

undo日志

事務(wù)未提交的時(shí)候,修改數(shù)據(jù)的鏡像(修改前的舊版本),存到undo日志里。以便事務(wù)回滾時(shí),恢復(fù)舊版本數(shù)據(jù),撤銷(xiāo)未提交事務(wù)數(shù)據(jù)對(duì)數(shù)據(jù)庫(kù)的影響。

undo日志是邏輯日志??梢赃@樣認(rèn)為,當(dāng)delete一條記錄時(shí),undo log中會(huì)記錄一條對(duì)應(yīng)的insert記錄,當(dāng)update一條記錄時(shí),它記錄一條對(duì)應(yīng)相反的update記錄。

存儲(chǔ)undo日志的地方,就是回滾段。

多個(gè)事務(wù)并行操作某一行數(shù)據(jù)時(shí),不同事務(wù)對(duì)該行數(shù)據(jù)的修改會(huì)產(chǎn)生多個(gè)版本,然后通過(guò)回滾指針(DBROLLPTR)連一條Undo日志鏈。

我們通過(guò)例子來(lái)看一下~

mysql> select* from account ;

+----+------+---------+

| id | name | balance |

+----+------+---------+

| 1| Jay| 100|

+----+------+---------+

1 row inset(0.00 sec)

假設(shè)表accout現(xiàn)在只有一條記錄,插入該該記錄的事務(wù)Id為100

如果事務(wù)B(事務(wù)Id為200),對(duì)id=1的該行記錄進(jìn)行更新,把balance值修改為90

事務(wù)B修改后,形成的Undo Log鏈如下:

快照讀&當(dāng)前讀

快照讀:

讀取的是記錄數(shù)據(jù)的可見(jiàn)版本(有舊的版本),不加鎖,普通的select語(yǔ)句都是快照讀,如:

select* from account where id>2;

當(dāng)前讀:

讀取的是記錄數(shù)據(jù)的最新版本,顯示加鎖的都是當(dāng)前讀

select* from account where id>2lockin share mode;

select* from account where id>2for update;

Read View

Read View就是事務(wù)執(zhí)行快照讀時(shí),產(chǎn)生的讀視圖。

事務(wù)執(zhí)行快照讀時(shí),會(huì)生成數(shù)據(jù)庫(kù)系統(tǒng)當(dāng)前的一個(gè)快照,記錄當(dāng)前系統(tǒng)中還有哪些活躍的讀寫(xiě)事務(wù),把它們放到一個(gè)列表里。

Read View主要是用來(lái)做可見(jiàn)性判斷的,即判斷當(dāng)前事務(wù)可見(jiàn)哪個(gè)版本的數(shù)據(jù)~

為了下面方便討論Read View可見(jiàn)性規(guī)則,先定義幾個(gè)變量

m_ids:當(dāng)前系統(tǒng)中那些活躍的讀寫(xiě)事務(wù)ID,它數(shù)據(jù)結(jié)構(gòu)為一個(gè)List。

minlimitid:m_ids事務(wù)列表中,最小的事務(wù)ID

maxlimitid:m_ids事務(wù)列表中,最大的事務(wù)ID

如果DBTRXID < minlimitid,表明生成該版本的事務(wù)在生成ReadView前已經(jīng)提交(因?yàn)槭聞?wù)ID是遞增的),所以該版本可以被當(dāng)前事務(wù)訪問(wèn)。

如果DBTRXID > m_ids列表中最大的事務(wù)id,表明生成該版本的事務(wù)在生成ReadView后才生成,所以該版本不可以被當(dāng)前事務(wù)訪問(wèn)。

如果 minlimitid =

注意啦?。R跟RC隔離級(jí)別,最大的區(qū)別就是:RC每次讀取數(shù)據(jù)前都生成一個(gè)ReadView,而RR只在第一次讀取數(shù)據(jù)時(shí)生成一個(gè)ReadView。

已提交讀(READ COMMITTED) 存在不可重復(fù)讀問(wèn)題的分析歷程

我覺(jué)得理解一個(gè)新的知識(shí)點(diǎn),最好的方法就是居于目前存在的問(wèn)題/現(xiàn)象,去分析它的來(lái)龍去脈~ RC的實(shí)現(xiàn)也跟MVCC有關(guān),RC是存在重復(fù)讀并發(fā)問(wèn)題的,所以我們來(lái)分析一波RC吧,先看一下執(zhí)行流程

假設(shè)現(xiàn)在系統(tǒng)里有A,B兩個(gè)事務(wù)在執(zhí)行,事務(wù)ID分別為100、200,并且假設(shè)存在的老數(shù)據(jù),插入事務(wù)ID是50哈~

事務(wù)A 先執(zhí)行查詢(xún)1的操作

# 事務(wù)A,Transaction ID 100

begin;

查詢(xún)1:select* from account WHERE id = 1;

事務(wù) B 執(zhí)行更新操作,id =1記錄的undo日志鏈如下

begin;

update account set balance =balance+20where id =1;

回到事務(wù)A,執(zhí)行查詢(xún)2的操作

begin;

查詢(xún)1:select* from account WHERE id = 1;

查詢(xún)2:select* from account WHERE id = 1;

查詢(xún)2執(zhí)行分析:

事務(wù)A在執(zhí)行到SELECT語(yǔ)句時(shí),重新生成一個(gè)ReadView,因?yàn)槭聞?wù)B(200)在活躍,所以ReadView的m_ids列表內(nèi)容就是[200]

由上圖undo日志鏈可得,最新版本的balance為1000,它的事務(wù)ID為200,在活躍事務(wù)列表里,所以當(dāng)前事務(wù)(事務(wù)A)不可見(jiàn)。

我們繼續(xù)找下一個(gè)版本,balance為100這行記錄,事務(wù)Id為50,小于活躍事務(wù)ID列表最小記錄200,所以這個(gè)版本可見(jiàn),因此,查詢(xún)2的結(jié)果,就是返回balance=100這個(gè)記錄~~

我們回到事務(wù)B,執(zhí)行提交操作,這時(shí)候undo日志鏈不變

begin;

update account set balance =balance+20where id =1;

commit

再次回到事務(wù)A,執(zhí)行查詢(xún)3的操作

begin;

查詢(xún)1:select* from account WHERE id = 1;

查詢(xún)2:select* from account WHERE id = 1;

查詢(xún)3:select* from account WHERE id = 1;

查詢(xún)3執(zhí)行分析:

事務(wù)A在執(zhí)行到SELECT語(yǔ)句時(shí),重新生成一個(gè)ReadView,因?yàn)槭聞?wù)B(200)已經(jīng)提交,不再活躍,所以ReadView的m_ids列表內(nèi)容就是空的了。

所以事務(wù)A直接讀取最新記錄,讀取到balance =120這個(gè)版本的數(shù)據(jù)。

所以,這就是RC存在不可重復(fù)讀問(wèn)題的過(guò)程啦~有不理解的地方可以多讀幾遍哈~

可重復(fù)讀(Repeatable Read)解決不可重復(fù)讀問(wèn)題的一次分析

我們?cè)賮?lái)分析一波,RR隔離級(jí)別是如何解決不可重復(fù)讀并發(fā)問(wèn)題的吧~

你可能會(huì)覺(jué)得兩個(gè)并發(fā)事務(wù)的例子太簡(jiǎn)單了,好的!我們現(xiàn)在來(lái)點(diǎn)刺激的,開(kāi)啟三個(gè)事務(wù)~

假設(shè)現(xiàn)在系統(tǒng)里有A,B,C兩個(gè)事務(wù)在執(zhí)行,事務(wù)ID分別為100、200,300,存量數(shù)據(jù)插入的事務(wù)ID是50~

# 事務(wù)A,Transaction ID 100

begin;

UPDATE account SET balance = 1000 WHERE id = 1;

# 事務(wù)B,Transaction ID 200

begin; //開(kāi)個(gè)事務(wù),占坑先

這時(shí)候,account表中,id =1記錄的undo日志鏈如下:

# 事務(wù)C,Transaction ID 300

begin;

//查詢(xún)1:select * from account WHERE id = 1;

查詢(xún)1執(zhí)行過(guò)程分析:

事務(wù)C在執(zhí)行SELECT語(yǔ)句時(shí),會(huì)先生成一個(gè)ReadView。因?yàn)槭聞?wù)A(100)、B(200)在活躍,所以ReadView的m_ids列表內(nèi)容就是[100, 200]。

由上圖undo日志鏈可得,最新版本的balance為1000,它的事務(wù)ID為100,在活躍事務(wù)列表里,所以當(dāng)前事務(wù)(事務(wù)C)不可見(jiàn)。

我們繼續(xù)找下一個(gè)版本,balance為100這行記錄,事務(wù)Id為50,小于活躍事務(wù)ID列表最小記錄100,所以這個(gè)版本可見(jiàn),因此,查詢(xún)1的結(jié)果,就是返回balance=100這個(gè)記錄~~

接著,我們把事務(wù)A提交一下:

# 事務(wù)A,Transaction ID 100

begin;

UPDATE account SET balance = 1000 WHERE id = 1;

commit;

在事務(wù)B中,執(zhí)行更新操作,把id=1的記錄balance修改為2000,更新完后,undo 日志鏈如下:

# 事務(wù)B,Transaction ID 200

begin; //開(kāi)個(gè)事務(wù),占坑先

UPDATE account SET balance = 2000 WHERE id = 1;

回到事務(wù)C,執(zhí)行查詢(xún)2

# 事務(wù)C,Transaction ID 300

begin;

//查詢(xún)1:select * from account WHERE id = 1;

//查詢(xún)2:select * from account WHERE id = 1;

查詢(xún)2:執(zhí)行分析:

在RR 級(jí)別下,執(zhí)行查詢(xún)2的時(shí)候,因?yàn)榍懊鍾eadView已經(jīng)生成過(guò)了,所以直接服用之前的ReadView,活躍事務(wù)列表為[100,200].

由上圖undo日志鏈可得,最新版本的balance為2000,它的事務(wù)ID為200,在活躍事務(wù)列表里,所以當(dāng)前事務(wù)(事務(wù)C)不可見(jiàn)。

我們繼續(xù)找下一個(gè)版本,balance為1000這行記錄,事務(wù)Id為100,也在活躍事務(wù)列表里,所以當(dāng)前事務(wù)(事務(wù)C)不可見(jiàn)。

繼續(xù)找下一個(gè)版本,balance為100這行記錄,事務(wù)Id為50,小于活躍事務(wù)ID列表最小記錄100,所以這個(gè)版本可見(jiàn),因此,查詢(xún)2的結(jié)果,也是返回balance=100這個(gè)記錄~~

鎖相關(guān)概念補(bǔ)充(附):

共享鎖與排他鎖

InnoDB 實(shí)現(xiàn)了標(biāo)準(zhǔn)的行級(jí)鎖,包括兩種:共享鎖(簡(jiǎn)稱(chēng) s 鎖)、排它鎖(簡(jiǎn)稱(chēng) x 鎖)。

共享鎖(S鎖):允許持鎖事務(wù)讀取一行。

排他鎖(X鎖):允許持鎖事務(wù)更新或者刪除一行。

如果事務(wù) T1 持有行 r 的 s 鎖,那么另一個(gè)事務(wù) T2 請(qǐng)求 r 的鎖時(shí),會(huì)做如下處理:

T2 請(qǐng)求 s 鎖立即被允許,結(jié)果 T1 T2 都持有 r 行的 s 鎖

T2 請(qǐng)求 x 鎖不能被立即允許

如果 T1 持有 r 的 x 鎖,那么 T2 請(qǐng)求 r 的 x、s 鎖都不能被立即允許,T2 必須等待T1釋放 x 鎖才可以,因?yàn)閄鎖與任何的鎖都不兼容。

記錄鎖(Record Locks)

記錄鎖是最簡(jiǎn)單的行鎖,僅僅鎖住一行。如:SELECT c1 FROM t WHERE c1=10FOR UPDATE

記錄鎖永遠(yuǎn)都是加在索引上的,即使一個(gè)表沒(méi)有索引,InnoDB也會(huì)隱式的創(chuàng)建一個(gè)索引,并使用這個(gè)索引實(shí)施記錄鎖。

會(huì)阻塞其他事務(wù)對(duì)其插入、更新、刪除

記錄鎖的事務(wù)數(shù)據(jù)(關(guān)鍵詞:lock_mode X locks rec butnotgap),記錄如下:

RECORD LOCKS space id 58 page no3 n bits 72 index `PRIMARY` of table `test`.`t`

trx id 10078 lock_mode X locks rec but not gap

Recordlock, heap no2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 8000000a; asc ;;

1: len 6; hex 00000000274f; asc 'O;;

2: len 7; hex b60000019d0110; asc ;;

間隙鎖(Gap Locks)

間隙鎖是一種加在兩個(gè)索引之間的鎖,或者加在第一個(gè)索引之前,或最后一個(gè)索引之后的間隙。

使用間隙鎖鎖住的是一個(gè)區(qū)間,而不僅僅是這個(gè)區(qū)間中的每一條數(shù)據(jù)。

間隙鎖只阻止其他事務(wù)插入到間隙中,他們不阻止其他事務(wù)在同一個(gè)間隙上獲得間隙鎖,所以 gap x lock 和 gap s lock 有相同的作用。

Next-Key Locks

Next-key鎖是記錄鎖和間隙鎖的組合,它指的是加在某條記錄以及這條記錄前面間隙上的鎖。

RC級(jí)別存在幻讀分析

因?yàn)镽C是存在幻讀問(wèn)題的,所以我們先切到RC隔離級(jí)別,分析一波~

假設(shè)account表有4條數(shù)據(jù)。

開(kāi)啟事務(wù)A,執(zhí)行當(dāng)前讀,查詢(xún)id>2的所有記錄。

再開(kāi)啟事務(wù)B,插入id=5的一條數(shù)據(jù)。

事務(wù)B插入數(shù)據(jù)成功后,再修改id=3的記錄

回到事務(wù)A,再次執(zhí)行id>2的當(dāng)前讀查詢(xún)

事務(wù)B可以插入id=5的數(shù)據(jù),卻更新不了id=3的數(shù)據(jù),陷入阻塞。證明事務(wù)A在執(zhí)行當(dāng)前讀的時(shí)候在id =3和id=4這兩條記錄上加了鎖,但是并沒(méi)有對(duì) id > 2 這個(gè)范圍加鎖~

事務(wù)B陷入阻塞后,切回事務(wù)A執(zhí)行當(dāng)前讀操作時(shí),死鎖出現(xiàn)。因?yàn)槭聞?wù)B在 insert 的時(shí)候,會(huì)在新記錄(id=5)上加鎖,所以事務(wù)A再次執(zhí)行當(dāng)前讀,想獲取id> 2 的記錄,就需要在 id=3,4,5 這3條記錄上加鎖,但是 id = 5這條記錄已經(jīng)被事務(wù)B 鎖住了,于是事務(wù)A被事務(wù)B阻塞,同時(shí)事務(wù)B還在等待 事務(wù)A釋放 id = 3上的鎖,最終產(chǎn)生了死鎖。

因此,我們可以發(fā)現(xiàn),RC隔離級(jí)別下,加鎖的select, update, delete等語(yǔ)句,使用的是記錄鎖,其他事務(wù)的插入依然可以執(zhí)行,因此會(huì)存在幻讀~

RR 級(jí)別解決幻讀分析

因?yàn)镽R是解決幻讀問(wèn)題的,怎么解決的呢,分析一波吧~

假設(shè)account表有4條數(shù)據(jù),RR級(jí)別。

開(kāi)啟事務(wù)A,執(zhí)行當(dāng)前讀,查詢(xún)id>2的所有記錄。

再開(kāi)啟事務(wù)B,插入id=5的一條數(shù)據(jù)。

可以發(fā)現(xiàn),事務(wù)B執(zhí)行插入操作時(shí),阻塞了~因?yàn)槭聞?wù)A在執(zhí)行select ... lock in share mode的時(shí)候,不僅在 id = 3,4 這2條記錄上加了鎖,而且在id > 2 這個(gè)范圍上也加了間隙鎖。

因此,我們可以發(fā)現(xiàn),RR隔離級(jí)別下,加鎖的select, update, delete等語(yǔ)句,會(huì)使用間隙鎖+ 臨鍵鎖,鎖住索引記錄之間的范圍,避免范圍間插入記錄,以避免產(chǎn)生幻影行記錄。

原文標(biāo)題:一文徹底讀懂 MySQL 事務(wù)的四大隔離級(jí)別

文章出處:【微信公眾號(hào):數(shù)據(jù)分析與開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)庫(kù)
    +關(guān)注

    關(guān)注

    7

    文章

    3752

    瀏覽量

    64233
  • MySQL
    +關(guān)注

    關(guān)注

    1

    文章

    797

    瀏覽量

    26399

原文標(biāo)題:一文徹底讀懂 MySQL 事務(wù)的四大隔離級(jí)別

文章出處:【微信號(hào):DBDevs,微信公眾號(hào):數(shù)據(jù)分析與開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Spring事務(wù)實(shí)現(xiàn)原理

    作者:京東零售 范錫軍 1、引言 spring的spring-tx模塊提供了對(duì)事務(wù)管理支持,使用spring事務(wù)可以讓我們從復(fù)雜的事務(wù)處理中得到解脫,無(wú)需要去處理獲得連接、關(guān)閉連接、事務(wù)
    的頭像 發(fā)表于 11-08 10:10 ?427次閱讀
    Spring<b class='flag-5'>事務(wù)</b>實(shí)現(xiàn)原理

    華納云:InnoDB 具有哪四大特性

    InnoDB 是 MySQL 數(shù)據(jù)庫(kù)中的一種存儲(chǔ)引擎,它具有許多特性,但通常被認(rèn)為有以下幾個(gè)主要特點(diǎn): 行級(jí)鎖定:InnoDB 支持行級(jí)鎖定,這意味著它在處理并發(fā)事務(wù)時(shí),只鎖定那些需要修改的行,而
    的頭像 發(fā)表于 08-14 16:02 ?268次閱讀

    MySQL的整體邏輯架構(gòu)

    支持多種存儲(chǔ)引擎是眾所周知的MySQL特性,也是MySQL架構(gòu)的關(guān)鍵優(yōu)勢(shì)之一。如果能夠理解MySQL Server與存儲(chǔ)引擎之間是怎樣通過(guò)API交互的,將大大有利于理解MySQL的核心
    的頭像 發(fā)表于 04-30 11:14 ?414次閱讀
    <b class='flag-5'>MySQL</b>的整體邏輯架構(gòu)

    MES實(shí)施的四大疑惑

    電子發(fā)燒友網(wǎng)站提供《MES實(shí)施的四大疑惑.docx》資料免費(fèi)下載
    發(fā)表于 03-01 15:35 ?0次下載

    2024年鋰電四大材料走勢(shì)“劃重點(diǎn)”

    GGII2023年中國(guó)鋰電四大關(guān)鍵材料出貨量數(shù)據(jù)及2024年市場(chǎng)走勢(shì)。
    的頭像 發(fā)表于 02-21 09:19 ?1936次閱讀
    2024年鋰電<b class='flag-5'>四大</b>材料走勢(shì)“劃重點(diǎn)”

    阿里二面:了解MySQL事務(wù)底層原理嗎

    MySQL 是如何來(lái)解決臟寫(xiě)這種問(wèn)題的?沒(méi)錯(cuò),就是鎖。MySQL 在開(kāi)啟一個(gè)事務(wù)的時(shí)候,他會(huì)將某條記錄和事務(wù)做一個(gè)綁定。這個(gè)其實(shí)和 JVM 鎖是類(lèi)似的。
    的頭像 發(fā)表于 01-18 16:34 ?307次閱讀
    阿里二面:了解<b class='flag-5'>MySQL</b><b class='flag-5'>事務(wù)</b>底層原理嗎

    mysql密碼忘了怎么重置

    mysql密碼忘了怎么重置? MySQL是一種開(kāi)源的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),密碼用于保護(hù)數(shù)據(jù)庫(kù)的安全性和保密性。如果你忘記了MySQL的密碼,可以通過(guò)以下幾種方法進(jìn)行重置。 方法一:使用MySQ
    的頭像 發(fā)表于 12-27 16:51 ?5992次閱讀

    GitHub底層數(shù)據(jù)庫(kù)無(wú)縫升級(jí)到MySQL 8.0的經(jīng)驗(yàn)

    GitHub 團(tuán)隊(duì)近日分享了他們將 GitHub.com 的底層數(shù)據(jù)庫(kù)無(wú)縫升級(jí)到 MySQL 8.0 的經(jīng)驗(yàn)。 據(jù)介紹,GitHub 使用 MySQL 來(lái)存儲(chǔ)大量關(guān)系數(shù)據(jù),因此在不影響網(wǎng)站服務(wù)級(jí)別
    的頭像 發(fā)表于 12-13 10:21 ?475次閱讀
    GitHub底層數(shù)據(jù)庫(kù)無(wú)縫升級(jí)到<b class='flag-5'>MySQL</b> 8.0的經(jīng)驗(yàn)

    mysql數(shù)據(jù)庫(kù)基礎(chǔ)命令

    MySQL是一個(gè)流行的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),經(jīng)常用于存儲(chǔ)、管理和操作數(shù)據(jù)。在本文中,我們將詳細(xì)介紹MySQL的基礎(chǔ)命令,并提供與每個(gè)命令相關(guān)的詳細(xì)解釋。 登錄MySQL 要登錄MySQL
    的頭像 發(fā)表于 12-06 10:56 ?530次閱讀

    php的mysql無(wú)法啟動(dòng)

    MySQL是一種常用的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),而PHP是一種廣泛應(yīng)用于服務(wù)器端的腳本語(yǔ)言。在使用PHP開(kāi)發(fā)網(wǎng)站或應(yīng)用時(shí),經(jīng)常會(huì)碰到MySQL無(wú)法啟動(dòng)的問(wèn)題。本文將詳細(xì)介紹解決MySQL無(wú)法啟動(dòng)的方法
    的頭像 發(fā)表于 12-04 15:59 ?1359次閱讀

    關(guān)于圖像傳感器圖像質(zhì)量的四大誤區(qū)!你踩過(guò)幾個(gè)坑?

    關(guān)于圖像傳感器圖像質(zhì)量的四大誤區(qū)!你踩過(guò)幾個(gè)坑?
    的頭像 發(fā)表于 11-27 16:56 ?410次閱讀
    關(guān)于圖像傳感器圖像質(zhì)量的<b class='flag-5'>四大</b>誤區(qū)!你踩過(guò)幾個(gè)坑?

    mysql和sql server區(qū)別

    MySQL和SQL Server是兩種常見(jiàn)的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)(RDBMS),用于存儲(chǔ)和管理數(shù)據(jù)庫(kù)。雖然它們都支持SQL語(yǔ)言,但在其他方面存在一些顯著的區(qū)別。以下是MySQL和SQL Server
    的頭像 發(fā)表于 11-21 11:07 ?1452次閱讀

    MySQL導(dǎo)出的步驟

    MySQL是一種常用的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),用于存儲(chǔ)和管理大量的結(jié)構(gòu)化數(shù)據(jù)。在實(shí)際應(yīng)用中,我們經(jīng)常需要將MySQL數(shù)據(jù)庫(kù)中的數(shù)據(jù)導(dǎo)出到其他地方,如備份數(shù)據(jù)、數(shù)據(jù)遷移、數(shù)據(jù)分析等。下面是使用MySQL
    的頭像 發(fā)表于 11-21 10:58 ?739次閱讀

    MYSQL事務(wù)的底層原理詳解

    事務(wù)的實(shí)現(xiàn)機(jī)制上,MySQL 采用的是 WAL:Write-ahead logging,預(yù)寫(xiě)式日志,機(jī)制來(lái)實(shí)現(xiàn)的。
    的頭像 發(fā)表于 11-15 10:10 ?538次閱讀
    <b class='flag-5'>MYSQL</b><b class='flag-5'>事務(wù)</b>的底層原理<b class='flag-5'>詳解</b>

    購(gòu)買(mǎi)UPS電源需看四大標(biāo)準(zhǔn)

    電子發(fā)燒友網(wǎng)站提供《購(gòu)買(mǎi)UPS電源需看四大標(biāo)準(zhǔn).pdf》資料免費(fèi)下載
    發(fā)表于 11-13 10:49 ?0次下載
    購(gòu)買(mǎi)UPS電源需看<b class='flag-5'>四大</b>標(biāo)準(zhǔn)