以前學(xué)的時(shí)候就對(duì)mysql的四種隔離級(jí)別懵逼再懵逼瘩缆,重學(xué)這個(gè)关拒,腦殼疼,沒看完庸娱,排期再看着绊。。涌韩。
這段時(shí)間又看了些資料畔柔,貌似稍微理解了點(diǎn)。臣樱。
出自:MySQL可重復(fù)讀級(jí)別能夠解決幻讀嗎
這篇文章主要給大家介紹了關(guān)于MySQL可重復(fù)讀級(jí)別能否解決幻讀的相關(guān)資料靶擦,文中通過示例代碼介紹的非常詳細(xì)腮考,對(duì)大家學(xué)習(xí)或者使用MySQL具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
引言
之前在深入了解數(shù)據(jù)庫(kù)理論的時(shí)候玄捕,了解到事物的不同隔離級(jí)別可能存在的問題踩蔚。為了更好的理解所以在MySQL數(shù)據(jù)庫(kù)中測(cè)試復(fù)現(xiàn)這些問題。關(guān)于臟讀和不可重復(fù)讀在相應(yīng)的隔離級(jí)別下都很容易的復(fù)現(xiàn)了枚粘。但是對(duì)于幻讀馅闽,我發(fā)現(xiàn)在可重復(fù)讀的隔離級(jí)別下沒有出現(xiàn),當(dāng)時(shí)想到難道是MySQL對(duì)幻讀做了什么處理馍迄?
測(cè)試:
創(chuàng)建一張測(cè)試用的表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("研發(fā)部") |
- | commit |
select * from dept | |
commit |
根據(jù)上面的流程執(zhí)行福也,預(yù)期來(lái)說應(yīng)該是事物1的第一條select查詢出一條數(shù)據(jù),第二個(gè)select查詢出兩條數(shù)據(jù)(包含事物2提交的數(shù)據(jù))攀圈。
但是在實(shí)際測(cè)試中發(fā)現(xiàn)第二條select實(shí)際上也只查詢處理一條數(shù)據(jù)暴凑。這是但是根據(jù)數(shù)據(jù)庫(kù)理論的可重復(fù)讀的實(shí)現(xiàn)(排他鎖和共享鎖)這是不應(yīng)該的情況。
在了解實(shí)際原因前我們先復(fù)習(xí)下事物的相關(guān)理論赘来。
數(shù)據(jù)庫(kù)原理理論
事物
事務(wù)(Transaction)现喳,一般是指要做的或所做的事情。在計(jì)算機(jī)術(shù)語(yǔ)中是指訪問并可能更新數(shù)據(jù)庫(kù)中各種數(shù)據(jù)項(xiàng)的一個(gè)程序執(zhí)行單元(unit)犬辰。事務(wù)由事務(wù)開始(begin transaction)和事務(wù)結(jié)束(end transaction)之間執(zhí)行的全體操作組成嗦篱。在關(guān)系數(shù)據(jù)庫(kù)中,一個(gè)事務(wù)可以是一組SQL語(yǔ)句或整個(gè)程序幌缝。
為什么要有事物
一個(gè)數(shù)據(jù)庫(kù)事務(wù)通常包含對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀或?qū)懙囊粋€(gè)操作序列灸促。它的存在包含有以下兩個(gè)目的:
- 為數(shù)據(jù)庫(kù)操作提供了一個(gè)從失敗中恢復(fù)到正常狀態(tài)的方法,同時(shí)提供了數(shù)據(jù)庫(kù)在異常狀態(tài)下仍能保持一致性的方法涵卵。
- 當(dāng)多個(gè)應(yīng)用程序在并發(fā)訪問數(shù)據(jù)庫(kù)時(shí)腿宰,可以在這些應(yīng)用程序之間提供一個(gè)隔離方法,保證彼此的操作互相干擾缘厢。
事物特性
事務(wù)具有4個(gè)特性:原子性吃度、一致性、隔離性贴硫、持久性椿每。這四個(gè)屬性通常稱為 ACID 特性。
- 原子性(atomicity):
一個(gè)事務(wù)應(yīng)該是一個(gè)不可分割的工作單位英遭,事務(wù)中包括的操作要么都成功间护,要么都不成功。 - 一致性(consistency):
事務(wù)必須是使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài)挖诸。一致性與原子性是密切相關(guān)的汁尺。 - 隔離性(isolation):
一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾。即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)在事物未提交前對(duì)并發(fā)的其他事務(wù)是隔離的多律,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相影響痴突。 - 持久性(durability):
一個(gè)事務(wù)一旦成功提交搂蜓,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變就應(yīng)該是永久性的。接下來(lái)的其他操作或故障不應(yīng)該對(duì)其有任何影響辽装。
事物之間的幾個(gè)特性并不是一組同等的概念:
如果在任何時(shí)刻都只有一個(gè)事物帮碰,那么其天然是具有隔離性的,這時(shí)只要保證原子性就能具有一致性拾积。
如果存在并發(fā)的情況下殉挽,就需要保證原子性和隔離性才能保證一致性。
數(shù)據(jù)庫(kù)并發(fā)事物中存在的問題
如果不考慮事務(wù)的隔離性拓巧,會(huì)發(fā)生以下幾種問題:
- 臟讀:臟讀是指在一個(gè)事務(wù)處理過程里讀取了另一個(gè)未提交的事務(wù)中的數(shù)據(jù)斯碌。當(dāng)一個(gè)事務(wù)正在多次修改某個(gè)數(shù)據(jù),而在這個(gè)事務(wù)中這多次的修改都還未提交肛度,這時(shí)一個(gè)并發(fā)的事務(wù)來(lái)訪問該數(shù)據(jù)输拇,就會(huì)造成兩個(gè)事務(wù)得到的數(shù)據(jù)不一致。
- 不可重復(fù)讀:不可重復(fù)讀是指在對(duì)于數(shù)據(jù)庫(kù)中的某條數(shù)據(jù)贤斜,一個(gè)事務(wù)范圍內(nèi)多次查詢返回不同的數(shù)據(jù)值(這里不同是指某一條或多條數(shù)據(jù)的內(nèi)容前后不一致,但數(shù)據(jù)條數(shù)相同)逛裤,這是由于在查詢間隔瘩绒,該事物需要用到的數(shù)據(jù)被另一個(gè)事務(wù)修改并提交了。不可重復(fù)讀和臟讀的區(qū)別是带族,臟讀是某一事務(wù)讀取了另一個(gè)事務(wù)未提交的臟數(shù)據(jù)锁荔,而不可重復(fù)讀則是讀取了其他事務(wù)提交的數(shù)據(jù)。需要注意的是在某些情況下不可重復(fù)讀并不是問題蝙砌。
- 幻讀:幻讀是事務(wù)非獨(dú)立執(zhí)行時(shí)發(fā)生的一種現(xiàn)象阳堕。例如事務(wù)T1對(duì)一個(gè)表中所有的行的某個(gè)數(shù)據(jù)項(xiàng)做了從“1”修改為“2”的操作,這時(shí)事務(wù)T2又對(duì)這個(gè)表中插入了一行數(shù)據(jù)項(xiàng)择克,而這個(gè)數(shù)據(jù)項(xiàng)的數(shù)值還是為“1”并且提交給數(shù)據(jù)庫(kù)恬总。而操作事務(wù)T1的用戶如果再查看剛剛修改的數(shù)據(jù),會(huì)發(fā)現(xiàn)還有一行沒有修改肚邢,其實(shí)這行是從事務(wù)T2中添加的壹堰,就好像產(chǎn)生幻覺一樣,這就是發(fā)生了幻讀骡湖〖溃幻讀和不可重復(fù)讀都是讀取了另一條已經(jīng)提交的事務(wù)(這點(diǎn)就臟讀不同),所不同的是不可重復(fù)讀可能發(fā)生在update,delete操作中响蕴,而幻讀發(fā)生在insert操作中谆焊。
排他鎖,共享鎖
排它鎖(Exclusive)浦夷,又稱為X 鎖辖试,寫鎖辜王。
共享鎖(Shared),又稱為S 鎖剃执,讀鎖誓禁。
讀寫鎖之間有以下的關(guān)系:
- 一個(gè)事務(wù)對(duì)數(shù)據(jù)對(duì)象O加了 S 鎖,可以對(duì) O進(jìn)行讀取操作肾档,但是不能進(jìn)行更新操作摹恰。加鎖期間其它事務(wù)能對(duì)O 加 S 鎖,但是不能加 X 鎖怒见。
- 一個(gè)事務(wù)對(duì)數(shù)據(jù)對(duì)象 O 加了 X 鎖俗慈,就可以對(duì) O 進(jìn)行讀取和更新。加鎖期間其它事務(wù)不能對(duì) O 加任何鎖遣耍。
即讀寫鎖之間的關(guān)系可以概括為:多讀單寫
事物的隔離級(jí)別
在事物中存在以下幾種隔離級(jí)別:
- 讀未提交(Read Uncommitted):解決更新丟失問題闺阱。如果一個(gè)事務(wù)已經(jīng)開始寫操作,那么其他事務(wù)則不允許同時(shí)進(jìn)行寫操作舵变,但允許其他事務(wù)讀此行數(shù)據(jù)酣溃。該隔離級(jí)別可以通過“排他寫鎖”實(shí)現(xiàn),即事物需要對(duì)某些數(shù)據(jù)進(jìn)行修改必須對(duì)這些數(shù)據(jù)加 X 鎖纪隙,讀數(shù)據(jù)不需要加 S 鎖赊豌。
- 讀已提交(Read Committed):解決了臟讀問題。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù)绵咱,但是未提交的寫事務(wù)將會(huì)禁止其他事務(wù)訪問該行碘饼。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實(shí)現(xiàn), 即事物需要對(duì)某些數(shù)據(jù)進(jìn)行修改必須對(duì)這些數(shù)據(jù)加 X 鎖悲伶,讀數(shù)據(jù)時(shí)需要加上 S 鎖艾恼,當(dāng)數(shù)據(jù)讀取完成后立刻釋放 S 鎖,不用等到事物結(jié)束麸锉。
- 可重復(fù)讀取(Repeatable Read):禁止不可重復(fù)讀取和臟讀取钠绍,但是有時(shí)可能出現(xiàn)幻讀數(shù)據(jù)。讀取數(shù)據(jù)的事務(wù)將會(huì)禁止寫事務(wù)(但允許讀事務(wù))花沉,寫事務(wù)則禁止任何其他事務(wù)五慈。Mysql默認(rèn)使用該隔離級(jí)別。這可以通過“共享讀鎖”和“排他寫鎖”實(shí)現(xiàn)主穗,即事物需要對(duì)某些數(shù)據(jù)進(jìn)行修改必須對(duì)這些數(shù)據(jù)加 X 鎖泻拦,讀數(shù)據(jù)時(shí)需要加上 S 鎖,當(dāng)數(shù)據(jù)讀取完成并不立刻釋放 S 鎖忽媒,而是等到事物結(jié)束后再釋放争拐。
- 串行化(Serializable):解決了幻讀的問題的。提供嚴(yán)格的事務(wù)隔離。它要求事務(wù)序列化執(zhí)行架曹,事務(wù)只能一個(gè)接著一個(gè)地執(zhí)行隘冲,不能并發(fā)執(zhí)行。僅僅通過“行級(jí)鎖”是無(wú)法實(shí)現(xiàn)事務(wù)序列化的绑雄,必須通過其他機(jī)制保證新插入的數(shù)據(jù)不會(huì)被剛執(zhí)行查詢操作的事務(wù)訪問到展辞。
MySQL中的隔離級(jí)別的實(shí)現(xiàn)
上面的內(nèi)容解釋了一些數(shù)據(jù)庫(kù)理論的概念,但是在MySQL万牺、ORACLE這樣的數(shù)據(jù)庫(kù)中罗珍,為了性能的考慮并不是完全按照上面介紹的理論來(lái)實(shí)現(xiàn)的。
MVCC
多版本并發(fā)控制(Multi-Version Concurrency Control, MVCC)是MySQL中基于樂觀鎖理論實(shí)現(xiàn)隔離級(jí)別的方式脚粟,用于實(shí)現(xiàn)讀已提交和可重復(fù)讀取隔離級(jí)別的實(shí)現(xiàn)覆旱。
實(shí)現(xiàn)(隔離級(jí)別為可重復(fù)讀)
在說到如何實(shí)現(xiàn)前先引入兩個(gè)概念:
系統(tǒng)版本號(hào):一個(gè)遞增的數(shù)字,每開始一個(gè)新的事務(wù)核无,系統(tǒng)版本號(hào)就會(huì)自動(dòng)遞增扣唱。
事務(wù)版本號(hào):事務(wù)開始時(shí)的系統(tǒng)版本號(hào)。
在MySQL中团南,會(huì)在表中每一條數(shù)據(jù)后面添加兩個(gè)字段:
創(chuàng)建版本號(hào):創(chuàng)建一行數(shù)據(jù)時(shí)噪沙,將當(dāng)前系統(tǒng)版本號(hào)作為創(chuàng)建版本號(hào)賦值
刪除版本號(hào):刪除一行數(shù)據(jù)時(shí),將當(dāng)前系統(tǒng)版本號(hào)作為刪除版本號(hào)賦值
SELECT
select時(shí)讀取數(shù)據(jù)的規(guī)則為:創(chuàng)建版本號(hào)<=當(dāng)前事務(wù)版本號(hào)吐根,刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)正歼。
創(chuàng)建版本號(hào)<=當(dāng)前事務(wù)版本號(hào)保證取出的數(shù)據(jù)不會(huì)有后啟動(dòng)的事物中創(chuàng)建的數(shù)據(jù)。這也是為什么在開始的示例中我們不會(huì)查出后來(lái)添加的數(shù)據(jù)的原因
刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)保證了至少在該事物開啟之前數(shù)據(jù)沒有被刪除佑惠,是應(yīng)該被查出來(lái)的數(shù)據(jù)。
INSERT
insert時(shí)將當(dāng)前的系統(tǒng)版本號(hào)賦值給創(chuàng)建版本號(hào)字段齐疙。
UPDATE
插入一條新紀(jì)錄膜楷,保存當(dāng)前事務(wù)版本號(hào)為行創(chuàng)建版本號(hào),同時(shí)保存當(dāng)前事務(wù)版本號(hào)到原來(lái)刪除的行贞奋,實(shí)際上這里的更新是通過delete和insert實(shí)現(xiàn)的赌厅。
DELETE
刪除時(shí)將當(dāng)前的系統(tǒng)版本號(hào)賦值給刪除版本號(hào)字段,標(biāo)識(shí)該行數(shù)據(jù)在那一個(gè)事物中會(huì)被刪除轿塔,即使實(shí)際上在位commit時(shí)該數(shù)據(jù)沒有被刪除特愿。根據(jù)select的規(guī)則后開啟懂?dāng)?shù)據(jù)也不會(huì)查詢到該數(shù)據(jù)。
MVCC真的解決了幻讀勾缭?
從最開始我們的測(cè)試示例和上面的理論支持來(lái)看貌似在MySQL中通過MVCC就解決了幻讀的問題揍障,那既然這樣串行化讀貌似就沒啥意義了,帶著疑問繼續(xù)測(cè)試俩由。
測(cè)試前數(shù)據(jù):
事物 1 | 事物 2 |
---|---|
begin | begin |
select * from dept | |
- | insert into dept(name) values("研發(fā)部") |
- | commit |
update dept set name="財(cái)務(wù)部"(工作中如果不想被辭退一定要寫where條件) | |
commit |
根據(jù)上面的結(jié)果我們期望的結(jié)果是這樣的:
id name
1 財(cái)務(wù)部
2 研發(fā)部
但是實(shí)際上我們的經(jīng)過是:
本來(lái)我們希望得到的結(jié)果只是第一條數(shù)據(jù)的部門改為財(cái)務(wù)毒嫡,但是結(jié)果確實(shí)兩條數(shù)據(jù)都被修改了。這種結(jié)果告訴我們其實(shí)在MySQL可重復(fù)讀的隔離級(jí)別中并不是完全解決了幻讀的問題幻梯,而是解決了讀數(shù)據(jù)情況下的幻讀問題兜畸。而對(duì)于修改的操作依舊存在幻讀問題努释,就是說MVCC對(duì)于幻讀的解決時(shí)不徹底的。
快照讀和當(dāng)前讀
出現(xiàn)了上面的情況我們需要知道為什么會(huì)出現(xiàn)這種情況咬摇。在查閱了一些資料后發(fā)現(xiàn)在RR級(jí)別中伐蒂,通過MVCC機(jī)制,雖然讓數(shù)據(jù)變得可重復(fù)讀肛鹏,但我們讀到的數(shù)據(jù)可能是歷史數(shù)據(jù)逸邦,不是數(shù)據(jù)庫(kù)最新的數(shù)據(jù)。這種讀取歷史數(shù)據(jù)的方式龄坪,我們叫它快照讀 (snapshot read)昭雌,而讀取數(shù)據(jù)庫(kù)最新版本數(shù)據(jù)的方式,叫當(dāng)前讀 (current read)健田。
select 快照讀
當(dāng)執(zhí)行select操作是innodb默認(rèn)會(huì)執(zhí)行快照讀烛卧,會(huì)記錄下這次select后的結(jié)果,之后select 的時(shí)候就會(huì)返回這次快照的數(shù)據(jù)妓局,即使其他事務(wù)提交了不會(huì)影響當(dāng)前select的數(shù)據(jù)总放,這就實(shí)現(xiàn)了可重復(fù)讀了『门溃快照的生成當(dāng)在第一次執(zhí)行select的時(shí)候局雄,也就是說假設(shè)當(dāng)A開啟了事務(wù),然后沒有執(zhí)行任何操作存炮,這時(shí)候B insert了一條數(shù)據(jù)然后commit,這時(shí)候A執(zhí)行 select炬搭,那么返回的數(shù)據(jù)中就會(huì)有B添加的那條數(shù)據(jù)。之后無(wú)論再有其他事務(wù)commit都沒有關(guān)系穆桂,因?yàn)榭煺找呀?jīng)生成了宫盔,后面的select都是根據(jù)快照來(lái)的。
當(dāng)前讀
對(duì)于會(huì)對(duì)數(shù)據(jù)修改的操作(update享完、insert灼芭、delete)都是采用當(dāng)前讀的模式。在執(zhí)行這幾個(gè)操作時(shí)會(huì)讀取最新的記錄般又,即使是別的事務(wù)提交的數(shù)據(jù)也可以查詢到彼绷。假設(shè)要update一條記錄,但是在另一個(gè)事務(wù)中已經(jīng)delete掉這條數(shù)據(jù)并且commit了茴迁,如果update就會(huì)產(chǎn)生沖突寄悯,所以在update的時(shí)候需要知道最新的數(shù)據(jù)。也正是因?yàn)檫@樣所以才導(dǎo)致上面我們測(cè)試的那種情況堕义。
select的當(dāng)前讀需要手動(dòng)的加鎖:
`select` `*` `from` `table` `where` `? lock` `in` `share mode;`
`select` `*` `from` `table` `where` `?` `for` `update``;`
有個(gè)問題說明下
在測(cè)試過程中最開始我以為使用begin語(yǔ)句就是開始一個(gè)事物了热某,所以在上面第二次測(cè)試中因?yàn)橄乳_始的事物1,結(jié)果在事物1中卻查到了事物2新增的數(shù)據(jù),當(dāng)時(shí)認(rèn)為這和前面MVCC中的select的規(guī)則不一致了昔馋,所以做了如下測(cè)試:
`SELECT` `*` `FROM` `information_schema.INNODB_TRX //用于查詢當(dāng)前正在執(zhí)行中的事物`
可以看到如果只是執(zhí)行begin語(yǔ)句實(shí)際上并沒有開啟一個(gè)事物筹吐。
下面在begin后添加一條select語(yǔ)句:
所以要明白實(shí)際上是對(duì)數(shù)據(jù)進(jìn)行了增刪改查等操作后才開啟了一個(gè)事物。
如何解決幻讀
很明顯可重復(fù)讀的隔離級(jí)別沒有辦法徹底的解決幻讀的問題秘遏,如果我們的項(xiàng)目中需要解決幻讀的話也有兩個(gè)辦法:
- 使用串行化讀的隔離級(jí)別
- MVCC+next-key locks:next-key locks由record locks(索引加鎖) 和 gap locks(間隙鎖丘薛,每次鎖住的不光是需要使用的數(shù)據(jù),還會(huì)鎖住這些數(shù)據(jù)附近的數(shù)據(jù))
實(shí)際上很多的項(xiàng)目中是不會(huì)使用到上面的兩種方法的邦危,串行化讀的性能太差洋侨,而且其實(shí)幻讀很多時(shí)候是我們完全可以接受的。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了倦蚪,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值希坚,謝謝大家對(duì)腳本之家的支持。