一、事務的4個基本特征
Atomic(原子性):
事務中包括的操作被看做一個邏輯單元。這個邏輯單元中的操作要
么所有成功。要么所有失敗。
Consistency(一致性):
僅僅有合法的數據能夠被寫入數據庫,否則事務應該將其回滾到最初
狀態。
Isolation(隔離性):
事務同意多個用戶對同一個數據進行並發訪問,而不破壞數據的正
確性和完整性。同一時候。並行事務的改動必須與其它並行事務的改動
相互獨立。
Durability(持久性):
事務結束后。事務處理的結果必須可以得到固化。
二、數據庫隔離級別
數據庫事務的隔離級別有4個。由低到高依次為Read uncommitted、Read committed、Repeatable read、Serializable。這四個級別能夠逐個解決臟讀、不可反復讀、幻讀這幾類問題。MySql設置的隔離級別默覺得Repeatable Read。可反復讀級別。
隔離級別能夠配置。
√: 可能出現 ×: 不會出現
臟讀 | 不可反復讀 | 幻讀 | |
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
注意:我們討論隔離級別的場景,主要是在多個事務並發的情況下。因此,接下來的解說都環繞事務並發。
Read uncommitted 讀未提交
READ UNCOMMITTED是限制性最弱的隔離級別。由於該級別忽略其它事務放置的鎖。使用READ UNCOMMITTED級別運行的事務,能夠讀取尚未由其它事務提交的改動后的數據值,這些行為稱為“臟”讀。我們所說的臟讀,兩個並發的事務,“事務A:領導給singo發工資”、“事務B:singo查詢工資賬戶”,事務B讀取了事務A尚未提交的數據。比方,事務1改動一行,事務2在事務1提交之前讀取了這一行。
假設事務1回滾,事務2就讀取了一行沒有提交的數據。這種數據我們覺得是不存在的。
Read committed 讀提交
該級別通過指定語句不能讀取其它事務已改動可是尚未提交的數據值。禁止運行臟讀。在當前事務中的各個語句運行之間,其它事務仍能夠改動、插入或刪除數據。從而產生無法反復的讀操作。或“影子”數據。比方,事務 1 讀取了一行。事務 2 改動或者刪除這一行而且提交。假設事務 1 想再一次讀取這一行,它將獲得改動后的數據或者發現這一樣已經被刪除。因此事務的第二次讀取結果與第一次讀取結果不同,因此也叫不可反復讀。 大多數數據庫的默認級別就是Read committed。比方Sql Server , Oracle。怎樣解決不可反復讀這一問題。請看下一個隔離級別。
Repeatable read 反復讀
REPEATABLE READ 是比 READ COMMITTED 限制性更強的隔離級別。該級別包含READ COMMITTED,而且另外指定了在當前事務提交之前。其它不論什么事務均不能夠改動或刪除當前事務已讀取的數據。並發性低於 READ COMMITTED。由於已讀數據的共享鎖在整個事務期間持有,而不是在每一個語句結束時釋放。
這個隔離級別僅僅是說,不可以改動和刪除,可是並沒有強制不能插入新的滿足條件查詢的數據行。
此可以得出結論:REPEATABLE READ隔離級別保證了在同樣的查詢條件下,同一個事務中的兩個查詢。第二次讀取的內容肯定包換第一次讀到的內容。注:Mysql的默認隔離級別就是Repeatable read。
反復讀與幻讀
反復讀是為了保證在一個事務中,相同查詢條件下讀取的數據值不發生改變,可是不能保證下次相同條件查詢。結果記錄數不會添加。
幻讀就是為了解決問題而存在的,他將這個查詢范圍都加鎖了。所以就不能再往這個范圍內插入數據。這就是SERIALIZABLE 隔離級別做的事情。
Serializable 序列化
SERIALIZABLE 是限制性最強的隔離級別,由於該級別鎖定整個范圍的鍵。並一直持有鎖,直到事務完畢。該級別包含REPEATABLE READ。並添加了在事務完畢之前,其它事務不能向事務已讀取的范圍插入新行的限制。比方,事務1讀取了一系列滿足搜索條件的行。事務2在運行SQL statement產生一行或者多行滿足事務1搜索條件的行時會沖突。則事務2回滾。這時事務1再次讀取了一系列滿足同樣搜索條件的行。第二次讀取的結果和第一次讀取的結果同樣。
三、鎖
一次封鎖or兩段鎖?
由於有大量的並發訪問,為了預防死鎖。一般應用中推薦使用一次封鎖法。就是在方法的開始階段。已經預先知道會用到哪些數據,然后所有鎖住,在方法執行之后,再所有解鎖。
這樣的方式能夠有效的避免循環死鎖,但在數據庫中卻不適用,由於在事務開始階段,數據庫並不知道會用到哪些數據。
數據庫遵循的是兩段鎖協議,將事務分成兩個階段,加鎖階段和解鎖階段(所以叫兩段鎖)
加鎖階段:在該階段能夠進行加鎖操作。在對不論什么數據進行讀操作之前要申請並獲得S鎖(共享鎖,其他事務能夠繼續加共享鎖,但不能加排它鎖),在進行寫操作之前要申請並獲得X鎖(排它鎖,其他事務不能再獲得不論什么鎖)。加鎖不成功,則事務進入等待狀態,直到加鎖成功才繼續運行。
解鎖階段:當事務釋放了一個封鎖以后,事務進入解鎖階段。在該階段僅僅能進行解鎖操作不能再進行加鎖操作。
事務 加鎖/解鎖處理
begin。
insert into test ..... 加insert相應的鎖
update test set... 加update相應的鎖
delete from test .... 加delete相應的鎖
commit; 事務提交時,同一時候釋放insert、update、delete相應的鎖
這樣的方式盡管無法避免死鎖。可是兩段鎖協議能夠保證事務的並發調度是串行化(串行化非常重要,尤其是在數據恢復和備份的時候)的。
不可反復讀和幻讀的差別
非常多人easy搞混不可反復讀和幻讀,確實這兩者有些相似。
但不可反復讀重點在於update和delete。而幻讀的重點在於insert。
假設使用鎖機制來實現這兩種隔離級別。在可反復讀中,該sql第一次讀取到數據后。就將這些數據加鎖,其他事務無法改動這些數據。就能夠實現可反復讀了。但這樣的方法卻無法鎖住insert的數據。所以當事務A先前讀取了數據,或者改動了所有數據,事務B還是能夠insert數據提交,這時事務A就會發現莫名其妙多了一條之前沒有的數據。這就是幻讀。不能通過行鎖來避免。
須要Serializable隔離級別 。讀用讀鎖,寫用寫鎖,讀鎖和寫鎖相互排斥,這么做能夠有效的避免幻讀、不可反復讀、臟讀等問題,但會極大的減少數據庫的並發能力。
所以說不可反復讀和幻讀最大的差別,就在於怎樣通過鎖機制來解決他們產生的問題。
上文說的,是使用悲觀鎖機制來處理這兩種問題,可是MySQL、ORACLE、PostgreSQL等成熟的數據庫。出於性能考慮,都是使用了以樂觀鎖為理論基礎的MVCC(多版本號並發控制)來避免這兩種問題。
悲觀鎖和樂觀鎖
悲觀鎖
正如其名,它指的是對數據被外界(包含本系統當前的其它事務,以及來自外部系統的事務處理)改動持保守態度。因此,在整個數據處理過程中,將數據處於鎖定狀態。悲觀鎖的實現。往往依靠數據庫提供的鎖機制(也僅僅有數據庫層提供的鎖機制才干真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會改動數據)。
在悲觀鎖的情況下,為了保證事務的隔離性,就須要一致性鎖定讀。
讀取數據時給加鎖,其他事務無法改動這些數據。
改動刪除數據時也要加鎖,其他事務無法讀取這些數據。
樂觀鎖
相對悲觀鎖而言,樂觀鎖機制採取了更加寬松的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷。特別是對長事務而言,這種開銷往往無法承受。
而樂觀鎖機制在一定程度上攻克了這個問題。樂觀鎖,大多是基於數據版本號( Version )記錄機制實現。何謂數據版本號?即為數據添加一個版本號標識,在基於數據庫表的版本號解決方式中,通常是通過為數據庫表添加一個 “version” 字段來實現。
讀取出數據時,將此版本號號一同讀出,之后更新時,對此版本號號加一。
此時。將提交數據的版本號數據與數據庫表相應記錄的當前版本號信息進行比對,假設提交的數據版本號號大於數據庫表當前版本號號,則予以更新。否則覺得是過期數據。
要說明的是,MVCC的實現沒有固定的規范,每一個數據庫都會有不同的實現方式,這里討論的是InnoDB的MVCC。
MVCC在MySQL的InnoDB中的實現
在InnoDB中,會在每行數據后加入兩個額外的隱藏的值來實現MVCC,這兩個值一個記錄這行數據何時被創建,另外一個記錄這行數據何時過期(或者被刪除)。 在實際操作中。存儲的並非時間,而是事務的版本,每開啟一個新事務,事務的版本就會遞增。 在可重讀Repeatable reads事務隔離級別下:
- SELECT時。讀取創建版本<=當前事務版本。刪除版本為空或>當前事務版本。
- INSERT時,保存當前事務版本為行的創建版本
- DELETE時,保存當前事務版本為行的刪除版本
- UPDATE時,插入一條新紀錄。保存當前事務版本為行創建版本,同一時候保存當前事務版本到原來刪除的行
- 通過MVCC,盡管每行記錄都須要額外的存儲空間,很多其它的行檢查工作以及一些額外的維護工作。但能夠降低鎖的使用,大多數讀操作都不用加鎖,讀數據操作非常easy,性能非常好,而且也能保證僅僅會讀取到符合標准的行。也僅僅鎖住必要行。
我們無論從數據庫方面的教課書中學到。還是從網絡上看到,大都是上文中事務的四種隔離級別這一模塊列出的意思,RR級別是可反復讀的,但無法解決幻讀。而僅僅有在Serializable級別才干解決幻讀。
於是我就加了一個事務C來展示效果。在事務C中加入了一條teacher_id=1的數據commit。RR級別中應該會有幻讀現象,事務A在查詢teacher_id=1的數據時會讀到事務C新加的數據。
可是測試后發現,在MySQL中是不存在這樣的情況的。在事務C提交后,事務A還是不會讀到這條數據。可見在MySQL的RR級別中。是攻克了幻讀的讀問題的。參見下圖
Serializable
這個級別非常easy。讀加共享鎖。寫加排他鎖,讀寫相互排斥。使用的悲觀鎖的理論,實現簡單,數據更加安全。可是並發能力非常差。假設你的業務並發的特別少或者沒有並發,同一時候又要求數據及時可靠的話,能夠使用這樣的模式。
這里要吐槽一句,不要看到select就說不會加鎖了。在Serializable這個級別,還是會加鎖的。
參考文章:http://blog.csdn.net/fg2006/article/details/6937413
http://www.cnblogs.com/xwdreamer/archive/2011/01/18/2297042.html
http://case0079.iteye.com/blog/205201
http://www.jb51.net/article/75452.htm