1、 事務是指對系統進行的一組操做,爲了保證系統的完整性,事務須要具備ACID特性,具體以下:
1. 原子性(Atomic)
一個事務包含多個操做,這些操做要麼所有執行,要麼全都不執行。實現事務的原子性,要支持回滾操做,在某個操做失敗後,回滾到事務執行以前的狀態。
回滾其實是一個比較高層抽象的概念,大多數DB在實現事務時,是在事務操做的數據快照上進行的(好比,MVCC),並不修改實際的數據,若是有錯並不會提交,因此很天然的支持回滾。
而在其餘支持簡單事務的系統中,不會在快照上更新,而直接操做實際數據。能夠先預演一邊全部要執行的操做,若是失敗則這些操做不會被執行,經過這種方式很簡單的實現了原子性。
2. 一致性(Consistency)
一致性是指事務使得系統從一個一致的狀態轉換到另外一個一致狀態。事務的一致性決定了一個系統設計和實現的複雜度。事務能夠不一樣程度的一致性:
強一致性:讀操做能夠當即讀到提交的更新操做。
弱一致性:提交的更新操做,不必定當即會被讀操做讀到,此種狀況會存在一個不一致窗口,指的是讀操做能夠讀到最新值的一段時間。
最終一致性:是弱一致性的特例。事務更新一份數據,最終一致性保證在沒有其餘事務更新一樣的值的話,最終全部的事務都會讀到以前事務更新的最新值。若是沒有錯誤發生,不一致窗口的大小依賴於:通訊延遲,系統負載等。
其餘一致性變體還有:
單調一致性:若是一個進程已經讀到一個值,那麼後續不會讀到更早的值。
會話一致性:保證客戶端和服務器交互的會話過程當中,讀操做能夠讀到更新操做後的最新值。
3. 隔離性(Isolation)
併發事務之間互相影響的程度,好比一個事務會不會讀取到另外一個未提交的事務修改的數據。在事務併發操做時,可能出現的問題有:
髒讀:事務A修改了一個數據,但未提交,事務B讀到了事務A未提交的更新結果,若是事務A提交失敗,事務B讀到的就是髒數據。
不可重複讀:在同一個事務中,對於同一份數據讀取到的結果不一致。好比,事務B在事務A提交前讀到的結果,和提交後讀到的結果可能不一樣。不可重複讀出現的緣由就是事務併發修改記錄,要避免這種狀況,最簡單的方法就是對要修改的記錄加鎖,這回致使鎖競爭加重,影響性能。另外一種方法是經過MVCC能夠在無鎖的狀況下,避免不可重複讀。
幻讀:在同一個事務中,同一個查詢屢次返回的結果不一致。事務A新增了一條記錄,事務B在事務A提交先後各執行了一次查詢操做,發現後一次比前一次多了一條記錄。幻讀是因爲併發事務增長記錄致使的,這個不能像不可重複讀經過記錄加鎖解決,由於對於新增的記錄根本沒法加鎖。須要將事務串行化,才能避免幻讀。
事務的隔離級別從低到高有:
Read Uncommitted:最低的隔離級別,什麼都不須要作,一個事務能夠讀到另外一個事務未提交的結果。全部的併發事務問題都會發生。
Read Committed:只有在事務提交後,其更新結果纔會被其餘事務看見。能夠解決髒讀問題。
Repeated Read:在一個事務中,對於同一份數據的讀取結果老是相同的,不管是否有其餘事務對這份數據進行操做,以及這個事務是否提交。能夠解決髒讀、不可重複讀。
Serialization:事務串行化執行,隔離級別最高,犧牲了系統的併發性。能夠解決併發事務的全部問題。
一般,在工程實踐中,爲了性能的考慮會對隔離性進行折中。
4. 持久性(Durability)
事務提交後,對系統的影響是永久的。java
2、mvccmysql
在併發讀寫數據庫時,讀操做可能會不一致的數據(髒讀)。爲了不這種狀況,須要實現數據庫的併發訪問控制,最簡單的方式就是加鎖訪問。因爲,加鎖會將讀寫操做串行化,因此不會出現不一致的狀態。可是,讀操做會被寫操做阻塞,大幅下降讀性能。在Java concurrent包中,有copyonwrite系列的類,專門用於優化讀遠大於寫的狀況。而其優化的手段就是,在進行寫操做時,將數據copy一份,不會影響原有數據,而後進行修改,修改完成後原子替換掉舊的數據,而讀操做只會讀取原有數據。經過這種方式實現寫操做不會阻塞讀操做,從而優化讀效率。而寫操做之間是要互斥的,而且每次寫操做都會有一次copy,因此只適合讀大於寫的狀況。sql
MVCC的原理與copyonwrite相似,全稱是Multi-Version Concurrent Control,即多版本併發控制。在MVCC協議下,每一個讀操做會看到一個一致性的snapshot,而且能夠實現非阻塞的讀。MVCC容許數據具備多個版本,這個版本能夠是時間戳或者是全局遞增的事務ID,在同一個時間點,不一樣的事務看到的數據是不一樣的。數據庫
實現原理: 服務器
------------------------------------------------------------------------------------------> 時間軸併發
|-------R(T1)-----|mvc
|-----------U(T2)-----------|性能
如上圖,假設有兩個併發操做R(T1)和U(T2),T1和T2是事務ID,T1小於T2,系統中包含數據a = 1(T1),R和W的操做以下:優化
R:read a (T1).net
U:a = 2 (T2)
R(讀操做)的版本T1表示要讀取數據的版本,而以後寫操做纔會更新版本,讀操做不會。在時間軸上,R晚於U,而因爲U在R開始以後提交,因此對於R是不可見的。因此,R只會讀取T1版本的數據,即a = 1。
因爲在update操做提交以前,不能影響已有數據的一致性,因此不會改變舊的數據,update操做會被拆分紅insert + delete。須要標記刪除舊的數據,insert新的數據。只有update提交以後,纔會影響後續的讀操做。而對於讀操做並且,只能讀到在其以前的全部的寫操做,正在執行中的寫操做對其是不可見的。
上面說了一堆的虛的理論,下面來點幹活,看一下MySQL的innodb引擎是如何實現MVCC的。innodb會爲每一行添加兩個字段,分別表示該行建立的版本和刪除的版本,填入的是事務的版本號,這個版本號隨着事務的建立不斷遞增。在repeated read的隔離級別(事務的隔離級別請看這篇文章)下,具體各類數據庫操做的實現:
select:知足如下兩個條件innodb會返回該行數據:(1)該行的建立版本號小於等於當前版本號,用於保證在select操做以前全部的操做已經執行落地。(2)該行的刪除版本號大於當前版本或者爲空。刪除版本號大於當前版本意味着有一個併發事務將該行刪除了。
insert:將新插入的行的建立版本號設置爲當前系統的版本號。
delete:將要刪除的行的刪除版本號設置爲當前系統的版本號。
update:不執行原地update,而是轉換成insert + delete。將舊行的刪除版本號設置爲當前版本號,並將新行insert同時設置建立版本號爲當前版本號。
其中,寫操做(insert、delete和update)執行時,須要將系統版本號遞增。
因爲舊數據並不真正的刪除,因此必須對這些數據進行清理,innodb會開啓一個後臺線程執行清理工做,具體的規則是將刪除版本號小於當前系統版本的行刪除,這個過程叫作purge。
經過MVCC很好的實現了事務的隔離性,能夠達到repeated read級別,要實現serializable還必須加鎖