事物 1 | 事物 2 |
---|---|
begin | begin |
select * from dept | |
- | insert into dept(name) values("研發(fā)部") |
- | commit |
select * from dept | |
commit |
根據(jù)上面的流程執(zhí)行,預(yù)期來說應(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ù)庫理論的可重復(fù)讀的實(shí)現(xiàn)(排他鎖和共享鎖)這是不應(yīng)該的情況。
在了解實(shí)際原因前我們先復(fù)習(xí)下事物的相關(guān)理論。
數(shù)據(jù)庫原理理論
事物
事務(wù)(Transaction),一般是指要做的或所做的事情。在計(jì)算機(jī)術(shù)語中是指訪問并可能更新數(shù)據(jù)庫中各種數(shù)據(jù)項(xiàng)的一個(gè)程序執(zhí)行單元(unit)。事務(wù)由事務(wù)開始(begin transaction)和事務(wù)結(jié)束(end transaction)之間執(zhí)行的全體操作組成。在關(guān)系數(shù)據(jù)庫中,一個(gè)事務(wù)可以是一組SQL語句或整個(gè)程序。
為什么要有事物
一個(gè)數(shù)據(jù)庫事務(wù)通常包含對(duì)數(shù)據(jù)庫進(jìn)行讀或?qū)懙囊粋€(gè)操作序列。它的存在包含有以下兩個(gè)目的:
事物特性
事務(wù)具有4個(gè)特性:原子性、一致性、隔離性、持久性。這四個(gè)屬性通常稱為 ACID 特性。
事物之間的幾個(gè)特性并不是一組同等的概念:
如果在任何時(shí)刻都只有一個(gè)事物,那么其天然是具有隔離性的,這時(shí)只要保證原子性就能具有一致性。
如果存在并發(fā)的情況下,就需要保證原子性和隔離性才能保證一致性。
數(shù)據(jù)庫并發(fā)事物中存在的問題
如果不考慮事務(wù)的隔離性,會(huì)發(fā)生以下幾種問題:
排他鎖,共享鎖
排它鎖(Exclusive),又稱為X 鎖,寫鎖。
共享鎖(Shared),又稱為S 鎖,讀鎖。
讀寫鎖之間有以下的關(guān)系:
即讀寫鎖之間的關(guān)系可以概括為:多讀單寫
事物的隔離級(jí)別
在事物中存在以下幾種隔離級(jí)別:
MySQL中的隔離級(jí)別的實(shí)現(xiàn)
上面的內(nèi)容解釋了一些數(shù)據(jù)庫理論的概念,但是在MySQL、ORACLE這樣的數(shù)據(jù)庫中,為了性能的考慮并不是完全按照上面介紹的理論來實(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ì)查出后來添加的數(shù)據(jù)的原因
刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)保證了至少在該事物開啟之前數(shù)據(jù)沒有被刪除,是應(yīng)該被查出來的數(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)到原來刪除的行,實(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è)試示例和上面的理論支持來看貌似在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)過是:
本來我們希望得到的結(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ù)庫最新的數(shù)據(jù)。這種讀取歷史數(shù)據(jù)的方式,我們叫它快照讀 (snapshot read),而讀取數(shù)據(jù)庫最新版本數(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ù)commit都沒有關(guān)系,因?yàn)榭煺找呀?jīng)生成了,后面的select都是根據(jù)快照來的。
當(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語句就是開始一個(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語句實(shí)際上并沒有開啟一個(gè)事物。
下面在begin后添加一條select語句:
所以要明白實(shí)際上是對(duì)數(shù)據(jù)進(jìn)行了增刪改查等操作后才開啟了一個(gè)事物。
如何解決幻讀
很明顯可重復(fù)讀的隔離級(jí)別沒有辦法徹底的解決幻讀的問題,如果我們的項(xiàng)目中需要解決幻讀的話也有兩個(gè)辦法:
實(shí)際上很多的項(xiàng)目中是不會(huì)使用到上面的兩種方法的,串行化讀的性能太差,而且其實(shí)幻讀很多時(shí)候是我們完全可以接受的。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
標(biāo)簽:拉薩 洛陽 吐魯番 嘉峪關(guān) 安徽 葫蘆島 甘南
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《MySQL可重復(fù)讀級(jí)別能夠解決幻讀嗎》,本文關(guān)鍵詞 MySQL,可重,復(fù)讀,級(jí)別,能夠,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。