您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“MySQL的可重復(fù)讀級(jí)別能解決幻讀問(wèn)題嗎”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
引言
之前在深入了解數(shù)據(jù)庫(kù)理論的時(shí)候,了解到事務(wù)的不同隔離級(jí)別可能存在的問(wèn)題。為了更好的理解所以在MySQL數(shù)據(jù)庫(kù)中測(cè)試復(fù)現(xiàn)這些問(wèn)題。關(guān)于臟讀和不可重復(fù)讀在相應(yīng)的隔離級(jí)別下都很容易的復(fù)現(xiàn)了。
但是對(duì)于幻讀,我發(fā)現(xiàn)在可重復(fù)讀的隔離級(jí)別下沒(méi)有出現(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("后勤部")
根據(jù)上面的流程執(zhí)行,預(yù)期來(lái)說(shuō)應(yīng)該是事務(wù)1的第一條select查詢(xún)出一條數(shù)據(jù),第二個(gè)select查詢(xún)出兩條數(shù)據(jù)(包含事務(wù)2提交的數(shù)據(jù))。
但是在實(shí)際測(cè)試中發(fā)現(xiàn)第二條select實(shí)際上也只查詢(xún)處理一條數(shù)據(jù)。這是但是根據(jù)數(shù)據(jù)庫(kù)理論的可重復(fù)讀的實(shí)現(xiàn)(排他鎖和共享鎖)這是不應(yīng)該的情況。
在了解實(shí)際原因前我們先復(fù)習(xí)下事務(wù)的相關(guān)理論。
數(shù)據(jù)庫(kù)原理理論
事務(wù)
事務(wù)(Transaction),一般是指要做的或所做的事情。在計(jì)算機(jī)術(shù)語(yǔ)中是指訪問(wèn)并可能更新數(shù)據(jù)庫(kù)中各種數(shù)據(jù)項(xiàng)的一個(gè)程序執(zhí)行單元(unit)。事務(wù)由事務(wù)開(kāi)始(begin transaction)和事務(wù)結(jié)束(end transaction)之間執(zhí)行的全體操作組成。
在關(guān)系數(shù)據(jù)庫(kù)中,一個(gè)事務(wù)可以是一組SQL語(yǔ)句或整個(gè)程序。
為什么要有事務(wù)
一個(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ā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),可以在這些應(yīng)用程序之間提供一個(gè)隔離方法,保證彼此的操作互相干擾。
事務(wù)特性
事務(wù)具有4個(gè)特性:原子性、一致性、隔離性、持久性。這四個(gè)屬性通常稱(chēng)為 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ù)在事務(wù)未提交前對(duì)并發(fā)的其他事務(wù)是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相影響。
持久性(durability):一個(gè)事務(wù)一旦成功提交,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變就應(yīng)該是永久性的。接下來(lái)的其他操作或故障不應(yīng)該對(duì)其有任何影響。
事務(wù)之間的幾個(gè)特性并不是一組同等的概念:
如果在任何時(shí)刻都只有一個(gè)事務(wù),那么其天然是具有隔離性的,這時(shí)只要保證原子性就能具有一致性。
如果存在并發(fā)的情況下,就需要保證原子性和隔離性才能保證一致性。
數(shù)據(jù)庫(kù)并發(fā)事務(wù)中存在的問(wèn)題
如果不考慮事務(wù)的隔離性,會(huì)發(fā)生以下幾種問(wèn)題:
臟讀
臟讀是指在一個(gè)事務(wù)處理過(guò)程里讀取了另一個(gè)未提交的事務(wù)中的數(shù)據(jù)。當(dāng)一個(gè)事務(wù)正在多次修改某個(gè)數(shù)據(jù),而在這個(gè)事務(wù)中這多次的修改都還未提交,這時(shí)一個(gè)并發(fā)的事務(wù)來(lái)訪問(wèn)該數(shù)據(jù),就會(huì)造成兩個(gè)事務(wù)得到的數(shù)據(jù)不一致。
不可重復(fù)讀
不可重復(fù)讀是指在對(duì)于數(shù)據(jù)庫(kù)中的某條數(shù)據(jù),一個(gè)事務(wù)范圍內(nèi)多次查詢(xún)返回不同的數(shù)據(jù)值(這里不同是指某一條或多條數(shù)據(jù)的內(nèi)容前后不一致,但數(shù)據(jù)條數(shù)相同),這是由于在查詢(xún)間隔,該事務(wù)需要用到的數(shù)據(jù)被另一個(gè)事務(wù)修改并提交了。
不可重復(fù)讀和臟讀的區(qū)別是,臟讀是某一事務(wù)讀取了另一個(gè)事務(wù)未提交的臟數(shù)據(jù),而不可重復(fù)讀則是讀取了其他事務(wù)提交的數(shù)據(jù)。需要注意的是在某些情況下不可重復(fù)讀并不是問(wèn)題。
幻讀
幻讀是事務(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的用戶(hù)如果再查看剛剛修改的數(shù)據(jù),會(huì)發(fā)現(xiàn)還有一行沒(méi)有修改,其實(shí)這行是從事務(wù)T2中添加的,就好像產(chǎn)生幻覺(jué)一樣,這就是發(fā)生了幻讀。
幻讀和不可重復(fù)讀都是讀取了另一條已經(jīng)提交的事務(wù)(這點(diǎn)就臟讀不同),所不同的是不可重復(fù)讀可能發(fā)生在update,delete操作中,而幻讀發(fā)生在insert操作中。
排他鎖,共享鎖
排它鎖(Exclusive),又稱(chēng)為X 鎖,寫(xiě)鎖。
共享鎖(Shared),又稱(chēng)為S 鎖,讀鎖。
讀寫(xiě)鎖之間有以下的關(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 加任何鎖。
即讀寫(xiě)鎖之間的關(guān)系可以概括為:多讀單寫(xiě)
事務(wù)的隔離級(jí)別
在事務(wù)中存在以下幾種隔離級(jí)別:
讀未提交(Read Uncommitted)
解決更新丟失問(wèn)題。如果一個(gè)事務(wù)已經(jīng)開(kāi)始寫(xiě)操作,那么其他事務(wù)則不允許同時(shí)進(jìn)行寫(xiě)操作,但允許其他事務(wù)讀此行數(shù)據(jù)。該隔離級(jí)別可以通過(guò)“排他寫(xiě)鎖”實(shí)現(xiàn),即事務(wù)需要對(duì)某些數(shù)據(jù)進(jìn)行修改必須對(duì)這些數(shù)據(jù)加 X 鎖,讀數(shù)據(jù)不需要加 S 鎖。
讀已提交(Read Committed)
解決了臟讀問(wèn)題。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問(wèn)該行數(shù)據(jù),但是未提交的寫(xiě)事務(wù)將會(huì)禁止其他事務(wù)訪問(wèn)該行。這可以通過(guò)“瞬間共享讀鎖”和“排他寫(xiě)鎖”實(shí)現(xiàn), 即事務(wù)需要對(duì)某些數(shù)據(jù)進(jìn)行修改必須對(duì)這些數(shù)據(jù)加 X 鎖,讀數(shù)據(jù)時(shí)需要加上 S 鎖,當(dāng)數(shù)據(jù)讀取完成后立刻釋放 S 鎖,不用等到事務(wù)結(jié)束。
可重復(fù)讀取(Repeatable Read)
禁止不可重復(fù)讀取和臟讀取,但是有時(shí)可能出現(xiàn)幻讀數(shù)據(jù)。讀取數(shù)據(jù)的事務(wù)將會(huì)禁止寫(xiě)事務(wù)(但允許讀事務(wù)),寫(xiě)事務(wù)則禁止任何其他事務(wù)。
Mysql默認(rèn)使用該隔離級(jí)別。這可以通過(guò)“共享讀鎖”和“排他寫(xiě)鎖”實(shí)現(xiàn),即事務(wù)需要對(duì)某些數(shù)據(jù)進(jìn)行修改必須對(duì)這些數(shù)據(jù)加 X 鎖,讀數(shù)據(jù)時(shí)需要加上 S 鎖,當(dāng)數(shù)據(jù)讀取完成并不立刻釋放 S 鎖,而是等到事務(wù)結(jié)束后再釋放。
串行化(Serializable)
解決了幻讀的問(wèn)題的。提供嚴(yán)格的事務(wù)隔離。它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個(gè)接著一個(gè)地執(zhí)行,不能并發(fā)執(zhí)行。僅僅通過(guò)“行級(jí)鎖”是無(wú)法實(shí)現(xiàn)事務(wù)序列化的,必須通過(guò)其他機(jī)制保證新插入的數(shù)據(jù)不會(huì)被剛執(zhí)行查詢(xún)操作的事務(wù)訪問(wèn)到。
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中基于樂(lè)觀鎖理論實(shí)現(xiàn)隔離級(jí)別的方式,用于實(shí)現(xiàn)讀已提交和可重復(fù)讀取隔離級(jí)別的實(shí)現(xiàn)。
實(shí)現(xiàn)(隔離級(jí)別為可重復(fù)讀)
在說(shuō)到如何實(shí)現(xiàn)前先引入兩個(gè)概念:
系統(tǒng)版本號(hào):一個(gè)遞增的數(shù)字,每開(kāi)始一個(gè)新的事務(wù),系統(tǒng)版本號(hào)就會(huì)自動(dòng)遞增。
事務(wù)版本號(hào):事務(wù)開(kāi)始時(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)的事務(wù)中創(chuàng)建的數(shù)據(jù)。這也是為什么在開(kāi)始的示例中我們不會(huì)查出后來(lái)添加的數(shù)據(jù)的原因
刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)保證了至少在該事務(wù)開(kāi)啟之前數(shù)據(jù)沒(méi)有被刪除,是應(yīng)該被查出來(lái)的數(shù)據(jù)。
INSERT
insert是將當(dāng)前的系統(tǒng)版本號(hào)賦值給創(chuàng)建版本號(hào)字段。
UPDATE
插入一條新記錄,保存當(dāng)前事務(wù)版本號(hào)為行創(chuàng)建版本號(hào),同時(shí)保存當(dāng)前事務(wù)版本號(hào)到原來(lái)刪除的行,實(shí)際上這里的更新是通過(guò)delete和insert實(shí)現(xiàn)的。
DELETE
刪除時(shí)將當(dāng)前的系統(tǒng)版本號(hào)賦值給刪除版本號(hào)字段,標(biāo)識(shí)該行數(shù)據(jù)在那一個(gè)事務(wù)中會(huì)被刪除,即使實(shí)際上在位commit時(shí)該數(shù)據(jù)沒(méi)有被刪除。根據(jù)select的規(guī)則后開(kāi)啟的數(shù)據(jù)也不會(huì)查詢(xún)到該數(shù)據(jù)。
MVCC真的解決了幻讀?
從最開(kāi)始我們的測(cè)試示例和上面的理論支持來(lái)看貌似在MySQL中通過(guò)MVCC就解決了幻讀的問(wèn)題,那既然這樣串行化讀貌似就沒(méi)啥意義了,帶著疑問(wèn)繼續(xù)測(cè)試。
測(cè)試前數(shù)據(jù):
根據(jù)上面的結(jié)果我們期望的結(jié)果是這樣的:
id | name |
---|---|
1 | 財(cái)務(wù)部 |
2 | 研發(fā)部 |
但是實(shí)際上我們的經(jīng)過(guò)是:
本來(lái)我們希望得到的結(jié)果只是第一條數(shù)據(jù)的部門(mén)改為財(cái)務(wù),但是結(jié)果確實(shí)兩條數(shù)據(jù)都被修改了。
這種結(jié)果告訴我們其實(shí)在MySQL可重復(fù)讀的隔離級(jí)別中并不是完全解決了幻讀的問(wèn)題,而是解決了讀數(shù)據(jù)情況下的幻讀問(wèn)題。而對(duì)于修改的操作依舊存在幻讀問(wèn)題,就是說(shuō)MVCC對(duì)于幻讀的解決時(shí)不徹底的。
快照讀和當(dāng)前讀
出現(xiàn)了上面的情況我們需要知道為什么會(huì)出現(xiàn)這種情況。在查閱了一些資料后發(fā)現(xiàn)在RR級(jí)別中,通過(guò)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í)候,也就是說(shuō)假設(shè)當(dāng)A開(kāi)啟了事務(wù),然后沒(méi)有執(zhí)行任何操作,這時(shí)候B insert了一條數(shù)據(jù)然后commit,這時(shí)候A執(zhí)行 select,那么返回的數(shù)據(jù)中就會(huì)有B添加的那條數(shù)據(jù)。
之后無(wú)論再有其他事務(wù)commit都沒(méi)有關(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ù)也可以查詢(xún)到。
假設(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è)問(wèn)題說(shuō)明下
在測(cè)試過(guò)程中最開(kāi)始我以為使用begin語(yǔ)句就是開(kāi)始一個(gè)事務(wù)了,所以在上面第二次測(cè)試中因?yàn)橄乳_(kāi)始的事務(wù)1,結(jié)果在事務(wù)1中卻查到了事務(wù)2新增的數(shù)據(jù),當(dāng)時(shí)認(rèn)為這和前面MVCC中的select的規(guī)則不一致了,所以做了如下測(cè)試:
SELECT * FROM information_schema.INNODB_TRX //用于查詢(xún)當(dāng)前正在執(zhí)行中的事務(wù)
可以看到如果只是執(zhí)行begin語(yǔ)句實(shí)際上并沒(méi)有開(kāi)啟一個(gè)事務(wù)。
下面在begin后添加一條select語(yǔ)句:
所以要明白實(shí)際上是對(duì)數(shù)據(jù)進(jìn)行了增刪改查等操作后才開(kāi)啟了一個(gè)事務(wù)。
如何解決幻讀
很明顯可重復(fù)讀的隔離級(jí)別沒(méi)有辦法徹底的解決幻讀的問(wèn)題,如果我們的項(xiàng)目中需要解決幻讀的話(huà)也有兩個(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í)候是我們完全可以接受的。
“MySQL的可重復(fù)讀級(jí)別能解決幻讀問(wèn)題嗎”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。