二、數(shù)據(jù)庫隔離級別
數(shù)據(jù)庫事務(wù)的隔離級別有4個,由低到高依次為Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可以逐個解決臟讀、不可重復(fù)讀、幻讀這幾類問題。MySql設(shè)置的隔離級別默認為Repeatable Read,可重復(fù)讀級別。隔離級別可以配置。
√: 可能出現(xiàn)×: 不會出現(xiàn)
臟讀 | 不可重復(fù)讀 | 幻讀 | |
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
注意:我們討論隔離級別的場景,主要是在多個事務(wù)并發(fā)的情況下,因此,接下來的講解都圍繞事務(wù)并發(fā)。
READ UNCOMMITTED是限制性最弱的隔離級別,因為該級別忽略其他事務(wù)放置的鎖。使用READ UNCOMMITTED級別執(zhí)行的事務(wù),可以讀取尚未由其他事務(wù)提交的修改后的數(shù)據(jù)值,這些行為稱為“臟”讀。我們所說的臟讀,兩個并發(fā)的事務(wù),“事務(wù)A:領(lǐng)導(dǎo)給singo發(fā)工資”、“事務(wù)B:singo查詢工資賬戶”,事務(wù)B讀取了事務(wù)A尚未提交的數(shù)據(jù)。比如,事務(wù)1修改一行,事務(wù)2在事務(wù)1提交之前讀取了這一行。如果事務(wù)1回滾,事務(wù)2就讀取了一行沒有提交的數(shù)據(jù),這樣的數(shù)據(jù)我們認為是不存在的。
大多數(shù)數(shù)據(jù)庫的默認級別就是Read committed,比如Sql Server , Oracle。如何解決不可重復(fù)讀這一問題,請看下一個隔離級別。
重復(fù)讀是為了保證在一個事務(wù)中,相同查詢條件下讀取的數(shù)據(jù)值不發(fā)生改變,但是不能保證下次同樣條件查詢,結(jié)果記錄數(shù)不會增加。
幻讀就是為了解決這個問題而存在的,他將這個查詢范圍都加鎖了,所以就不能再往這個范圍內(nèi)插入數(shù)據(jù),這就是SERIALIZABLE 隔離級別做的事情。
三、鎖
一次封鎖or兩段鎖?
因為有大量的并發(fā)訪問,為了預(yù)防死鎖,一般應(yīng)用中推薦使用一次封鎖法,就是在方法的開始階段,已經(jīng)預(yù)先知道會用到哪些數(shù)據(jù),然后全部鎖住,在方法運行之后,再全部解鎖。這種方式可以有效的避免循環(huán)死鎖,但在數(shù)據(jù)庫中卻不適用,因為在事務(wù)開始階段,數(shù)據(jù)庫并不知道會用到哪些數(shù)據(jù)。
數(shù)據(jù)庫遵循的是兩段鎖協(xié)議,將事務(wù)分成兩個階段,加鎖階段和解鎖階段(所以叫兩段鎖)
加鎖階段:在該階段可以進行加鎖操作。在對任何數(shù)據(jù)進行讀操作之前要申請并獲得S鎖(共享鎖,其它事務(wù)可以繼續(xù)加共享鎖,但不能加排它鎖),在進行寫操作之前要申請并獲得X鎖(排它鎖,其它事務(wù)不能再獲得任何鎖)。加鎖不成功,則事務(wù)進入等待狀態(tài),直到加鎖成功才繼續(xù)執(zhí)行。
解鎖階段:當(dāng)事務(wù)釋放了一個封鎖以后,事務(wù)進入解鎖階段,在該階段只能進行解鎖操作不能再進行加鎖操作。
事務(wù) 加鎖/解鎖處理
begin;
insert into test .....加insert對應(yīng)的鎖
update test set...加update對應(yīng)的鎖
delete from test ....加delete對應(yīng)的鎖
commit;事務(wù)提交時,同時釋放insert、update、delete對應(yīng)的鎖
這種方式雖然無法避免死鎖,但是兩段鎖協(xié)議可以保證事務(wù)的并發(fā)調(diào)度是串行化(串行化很重要,尤其是在數(shù)據(jù)恢復(fù)和備份的時候)的。
不可重復(fù)讀和幻讀的區(qū)別
很多人容易搞混不可重復(fù)讀和幻讀,確實這兩者有些相似。但不可重復(fù)讀重點在于update和delete,而幻讀的重點在于insert。
如果使用鎖機制來實現(xiàn)這兩種隔離級別,在可重復(fù)讀中,該sql第一次讀取到數(shù)據(jù)后,就將這些數(shù)據(jù)加鎖,其它事務(wù)無法修改這些數(shù)據(jù),就可以實現(xiàn)可重復(fù)讀了。但這種方法卻無法鎖住insert的數(shù)據(jù),所以當(dāng)事務(wù)A先前讀取了數(shù)據(jù),或者修改了全部數(shù)據(jù),事務(wù)B還是可以insert數(shù)據(jù)提交,這時事務(wù)A就會發(fā)現(xiàn)莫名其妙多了一條之前沒有的數(shù)據(jù),這就是幻讀,不能通過行鎖來避免。需要Serializable隔離級別 ,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這么做可以有效的避免幻讀、不可重復(fù)讀、臟讀等問題,但會極大的降低數(shù)據(jù)庫的并發(fā)能力。
所以說不可重復(fù)讀和幻讀最大的區(qū)別,就在于如何通過鎖機制來解決他們產(chǎn)生的問題。
上文說的,是使用悲觀鎖機制來處理這兩種問題,但是MySQL、ORACLE、PostgreSQL等成熟的數(shù)據(jù)庫,出于性能考慮,都是使用了以樂觀鎖為理論基礎(chǔ)的MVCC(多版本并發(fā)控制)來避免這兩種問題。
悲觀鎖和樂觀鎖
悲觀鎖
正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
在悲觀鎖的情況下,為了保證事務(wù)的隔離性,就需要一致性鎖定讀。讀取數(shù)據(jù)時給加鎖,其它事務(wù)無法修改這些數(shù)據(jù)。修改刪除數(shù)據(jù)時也要加鎖,其它事務(wù)無法讀取這些數(shù)據(jù)。
樂觀鎖
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務(wù)而言,這樣的開銷往往無法承受。
而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個 “version” 字段來實現(xiàn)。讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新,否則認為是過期數(shù)據(jù)。
要說明的是,MVCC的實現(xiàn)沒有固定的規(guī)范,每個數(shù)據(jù)庫都會有不同的實現(xiàn)方式,這里討論的是InnoDB的MVCC。
MVCC在MySQL的InnoDB中的實現(xiàn)
在InnoDB中,會在每行數(shù)據(jù)后添加兩個額外的隱藏的值來實現(xiàn)MVCC,這兩個值一個記錄這行數(shù)據(jù)何時被創(chuàng)建,另外一個記錄這行數(shù)據(jù)何時過期(或者被刪除)。 在實際操作中,存儲的并不是時間,而是事務(wù)的版本號,每開啟一個新事務(wù),事務(wù)的版本號就會遞增。 在可重讀Repeatable reads事務(wù)隔離級別下:
SELECT時,讀取創(chuàng)建版本號<=當(dāng)前事務(wù)版本號,刪除版本號為空或>當(dāng)前事務(wù)版本號。
INSERT時,保存當(dāng)前事務(wù)版本號為行的創(chuàng)建版本號
DELETE時,保存當(dāng)前事務(wù)版本號為行的刪除版本號
UPDATE時,插入一條新紀錄,保存當(dāng)前事務(wù)版本號為行創(chuàng)建版本號,同時保存當(dāng)前事務(wù)版本號到原來刪除的行
通過MVCC,雖然每行記錄都需要額外的存儲空間,更多的行檢查工作以及一些額外的維護工作,但可以減少鎖的使用,大多數(shù)讀操作都不用加鎖,讀數(shù)據(jù)操作很簡單,性能很好,并且也能保證只會讀取到符合標準的行,也只鎖住必要行。
我們不管從數(shù)據(jù)庫方面的教課書中學(xué)到,還是從網(wǎng)絡(luò)上看到,大都是上文中事務(wù)的四種隔離級別這一模塊列出的意思,RR級別是可重復(fù)讀的,但無法解決幻讀,而只有在Serializable級別才能解決幻讀。于是我就加了一個事務(wù)C來展示效果。在事務(wù)C中添加了一條teacher_id=1的數(shù)據(jù)commit,RR級別中應(yīng)該會有幻讀現(xiàn)象,事務(wù)A在查詢teacher_id=1的數(shù)據(jù)時會讀到事務(wù)C新加的數(shù)據(jù)。但是測試后發(fā)現(xiàn),在MySQL中是不存在這種情況的,在事務(wù)C提交后,事務(wù)A還是不會讀到這條數(shù)據(jù)??梢娫贛ySQL的RR級別中,是解決了幻讀的讀問題的。參見下圖
Serializable
這個級別很簡單,讀加共享鎖,寫加排他鎖,讀寫互斥。使用的悲觀鎖的理論,實現(xiàn)簡單,數(shù)據(jù)更加安全,但是并發(fā)能力非常差。如果你的業(yè)務(wù)并發(fā)的特別少或者沒有并發(fā),同時又要求數(shù)據(jù)及時可靠的話,可以使用這種模式。
這里要吐槽一句,不要看到select就說不會加鎖了,在Serializable這個級別,還是會加鎖的!
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com