如何理解資料庫事務中的一致性的概念?

資料庫事務,4個屬性,ACID,C就是Consistency,一致性,但是教科書式的定義說明,讓人有點不清楚。誰能擺事實,講道理,解釋一下何為事務一致性。


要想真正弄清楚這個問題,那是必須要把資料庫理論中的事務機制從頭開始看起,牽扯的內容比較多。

當然,如果只是想粗略的了解下,我就來舉個例子吧——當然不可能太嚴謹。

假設我們10個人,每人有一個賬號,裡面有錢,可以轉來轉去,這組成了一個小型的數據系統,那麼什麼叫數據一致性?這是由你自己來定義的,比較通用的就是:這10個人的賬號金額總數不變——滿足這一條件,就叫數據一致,不滿足,就叫數據不一致,或者在分散式的環境下,有一個數據在幾個地方都保存了,那麼任何時候,這幾個地方的數據都必須相同,這也叫一致性。

現在我們就這個簡單的一致性規則:10個人的賬號金額總數不變。假設初始的時候每個人賬號里有一萬,A賬號往B賬號里轉5000,這時候資料庫要執行兩行代碼:

A:減去5000

B:加上5000

在執行完第一行代碼的時候,這時候數據是不滿足一致性條件的!必須要執行完第二行代碼,數據才恢復到一致性的狀態!換而言之,資料庫中的數據是經常處於不一致的狀態,這是不可避免的,因此我們提出了事務的概念,用於檢測資料庫中的數據是否處於一致性狀態——如果資料庫中有沒有執行完的事務,那就是不一致的,否則,就是一致的。

上面的例子只是最簡單的情況,實際的運用中要複雜得多,比如前面提到的分散式系統:某個數據存在了三個伺服器上,現在要更新,就必須保證三個伺服器上全都更新好,如果有一個沒有成功,那麼其他兩個也應該維持不變,這又涉及到網路通信等問題,非常的折騰。

——————————————————————————————————————

評論中有提到了原子性,其實原子性與一致性是兩個完全不同的概念,當然他們的聯繫也很緊密。

還是用上面的這個例子:

為了保證一致性(即10個人 的賬號金額總數不變),那在我寫代碼的時候,如果寫了代碼:

A=A-5000;

那就必須要寫上

B=B+5000,或者是C=C+5000,這樣的代碼才能保證了資料庫的一致性狀態。

那什麼是原子性?

就是將上面的兩行代碼合成為一個事務,要麼全做,要麼全不做。

比如我寫了兩行代碼:

A=A+2000;

B=B+3000;

如果這兩行代碼看成是一個事務,並且在某一時刻全執行完了,那麼這個事務的原子性滿足了,但卻沒有滿足資料庫的一致性。


原子性:記錄之前的版本,允許回滾

一致性:事務開始和結束之間的中間狀態不會被其他事務看到

隔離性:適當的破壞一致性來提升性能與並行度 例如:最終一致~=讀未提交。

持久性:每一次的事務提交後就會保證不會丟失


講資料庫事務一致性怎麼能不提資料庫的ACID特性。

首先介紹事務,什麼是事務,事務就是DBMS當中用戶程序的任何一次執行,事務是DBMS能看到的基本修改單元。

事務是指對系統進行的一組操作,為了保證系統的完整性,事務需要具有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)

事務提交後,對系統的影響是永久的。


可以參考我這篇文章http://www.cnblogs.com/CareySon/p/3308926.html


一致性就是如果你做個銀行資料庫的話,無論怎麼轉賬,錢的總數都不變。


記得在做MySQL培訓時,也碰到過這個問題,我的理解如下:

定義:一致性:C,Consistency,事務必須是使資料庫從一個一致性狀態變到另一個一致性狀態。(教科書式的定義說明)

理解by我:在事務T開始時,此時資料庫有一種狀態,這個狀態是所有的MySQL對象處於一致的狀態,例如資料庫完整性約束正確,日誌狀態一致等,當事務T提交後,這時資料庫又有了一個新的狀態,不同的數據,不同的索引,不同的日誌等,但此時,約束,數據,索引,日誌等MySQL各種對象還是要保持一致性(正確性)。 這就是 從一個一致性的狀態,變到另一個一致性的狀態。也就是事務執行後,並沒有破壞資料庫的完整性約束(一切都是對的)。

MySQL資料庫innodb的事務,是通過redo log(innodb log),undo log,鎖機制,來維護這個一致性的。

一家之言,歡迎拍磚!


講道理

定義:資料庫一致性(Database Consistency)是指事務執行的結果必須是使資料庫從一個一致性狀態變到另一個一致性狀態。

資料庫狀態如何變化?每一次數據變更就會導致資料庫的狀態遷移。如果資料庫的初始狀態是C0,第一次事務T1的提交就會導致系統生成一個SYSTEM CHANGE NUMBER(SCN),這是資料庫狀態從C0轉變成C1。執行第二個事務T2的時候資料庫狀態從T1變成T2,以此類推,執行第Tn次事務的時候資料庫狀態由C(n-1)變成Cn。

定義一致性主要有2個方面,一致讀和一致寫。

一致寫:事務執行的數據變更只能基於上一個一致的狀態,且只能體現在一個狀態中。T(n)的變更結果只能基於C(n-1),C(n-2), ...C(1)狀態,且只能體現在C(n)狀態中。也就是說,一個狀態只能有一個事務變更數據,不允許有2個或者2個以上事務在一個狀態中變更數據。至於具體一致寫基於哪個狀態,需要判斷T(n)事務是否和T(n-1),T(n-2),...T(1)有依賴關係。

一致讀:事務讀取數據只能從一個狀態中讀取,不能從2個或者2個以上狀態讀取。也就是T(n)只能從C(n-1),C(n-2)... C(1)中的一個狀態讀取數據,不能一部分數據讀取自C(n-1),而另一部分數據讀取自C(n-2)。

擺事實

一致寫:

定義100個事務T(1)...T(100)實現相同的邏輯 update table set i=i+1,i的初始值是0,那麼並發執行這100個事務之後i的值是多少?可能很容易想到是100。那麼怎麼從一致性角度去理解呢?

資料庫隨機調度到T(50)執行,此時資料庫狀態是C(0),而其它事務都和T(50)有依賴關係,根據寫一致性原理,其它事務必須等到T(50)執行完畢後資料庫狀態變為C(1)才可以執行。因此資料庫利用鎖機制阻塞其它事務的執行。直到T(50)執行完畢,資料庫狀態從C(0)遷移到C(1)。資料庫喚醒其它事務後隨機調度到T(89)執行,以此類推直到所有事務調度執行完畢,資料庫狀態最終變為C(100)。

一致讀:

還是上面的例子,假設T(1)...T(100)順序執行,在不同的時機執行select i from table,我們看到i的值是什麼?

1. T(1)的執行過程中。資料庫狀態尚未遷移,讀到的i=0

2. T(1)執行完畢,T(2)的執行過程中,資料庫狀態遷移至C(1),讀到的i=1

不知道有沒有回到LZ的問題。


其實資料庫的一致性(單機)不容易理解往往是因為跟其他三個AID並列,然後又試圖獨立解釋。

然後又用官方的定義諸如狀態和轉賬這種不太好理解的東西解釋。我試圖簡單的描述下。

比如說我賬戶有500塊錢,這時候我下單買A,花了400,假如說我手速特別快,在下單成功還沒來得及扣錢之前就又買了B花了300,如果這個時候的結果是我的賬戶餘額200,就產生了不一致,如果-200,那其實還是一致的,雖然也許邏輯上出錯了。

到這裡其實一致性就算完事了,額外的問題就是誰來保證一致性,用來保證一致性的手段有很多,最容易理解的AID里的原子性,其實就是來保證一致性的一種手段,其他的手段諸如約束甚至程序保證。

跟隔離性一起理解一下,我們都說資料庫是強一致性,隔離性是指一個事務和另一個事務之間相互不影響,還是上面的例子,所以可能兩個事務讀的時候都是500的餘額,但是寫入的時候出錯。所以才會產生不同的隔離級別,不同的隔離級別其實就是用來解決在並發下遇到不一致該怎麼辦,往往要根據業務來進行隔離級別的取捨。

其實持久性也是用來保證一致性的,持久性的主要目標是保證已完成的操作不會丟失,還是上面的例子,比如我購買A的整個事務已經成功提交了,但是因為資料庫設計問題導致訂單儲存成功,但是扣款失敗(事務已經成功提交),這樣就產生了不一致,原子性是保證事務運行中的都成功或者都回滾,持久性主要是用來描述提交之後不會出錯。

所以你看,其實先描述一致性,然後再來解釋AID就好理解多了,不要把他們當做同一級別的東西來理解。AID在一定程度上來說都是為了保證一致性的手段。


ACID的C很簡單,比如多個線程並發執行轉賬業務:A轉給B一塊錢,無論成功與否(A+B)的總額是不會變的,這是單機事務。

在分散式資料庫里,一致性還包括分區間的一致性保證:

A在分區1,執行A-1 --phase1

B在分區2,執行B+1 --phase2

假設phase1操作成功,phase2失敗,這時候總額是少了1塊的,這就是破壞了一致性,自己慢慢琢磨C吧


原子性:無論一個事務里有多少執行步驟,這所有的步驟合起來是一個最小的執行單元,要麼不做,要麼全做,不存在只做到一半情況。比如銀行轉賬,轉出跟轉入這兩個包含在一個事務里的動作就是原子的。要麼不轉出也不轉入,轉出了就要轉入。

一致性:事務執行前與執行後數據內在的邏輯始終是成立的。比如轉賬前與轉賬後兩人存款的總和始終不變。

隔離性:雖說事務是原子的,要麼不做,要麼全做,不存在做一半的情況。但是從代碼實現上來說,事務里的步驟還是一步一步執行的,還是存在事務做到一半的情況。比如轉賬,代碼怎麼寫?就兩行代碼,是先轉出扣錢,再轉入加錢。兩行代碼中間,也就是轉出之後,轉入之前,此時數據是不一致的。那怎樣始終保證數據一致?那就用一個類似自欺欺人的辦法,讓轉賬這個事務在完成之前對別人都不可見,事務完成之前別人看到的都是轉賬前的狀態,看不到轉賬步驟中間不一致的狀態,所謂」隔離」。

持久性:事務做完了就是做完了,就生效了。就像錢轉給別人後當前這比轉賬交易就結束了,不可能再倒回來。


說一下我的理解:

要理解資料庫事務的一致性我覺得最好不要從應用程序員的角度來理解,也就是不要從事務的宏觀角度來理解,從DBMS的角度來理解會更好一些(即微觀的角度)。

首先對於兩個並發的事務,宏觀上來說在時間線上並發執行時是互不相關的兩個操作,但是在微觀的角度上來說也就是從DBMS的角度來說其實是把事務中涉及到的數據的一個個讀寫操作交叉操作的。

下面這個例子我從題主問題的反面角度來解讀一下,舉幾個不一致性的例子來說明問題。

微觀上執行順序調度的不正確導致數據的不一致性出現

如上圖,就是在微觀上對事物的基本操作沒有很好的安排好調度順序從而導致了宏觀上事務的不一致性的出現。

現在來回答這個問題「何為事務的一致性?」

答:DBMS確保事務在宏觀上操作狀態是正確的,不能出現如上面描述的幾種典型的不一致現象。


感覺這個問題的回答都不是很對....

查看了一下維基百科:

The consistency property ensures that any transaction will bring the database from one valid state to another. Any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This does not guarantee correctness of the transaction in all ways the application programmer might have wanted (that is the responsibility of application-level code), but merely that any programming errors cannot result in the violation of any defined rules.

應該就是資料庫系統在寫入的時候會去檢查約束等硬性規則,非法的數據(事務)則會被拒絕插入。


舉個反例就清楚了:A向B轉賬100元,操作1:A賬號減去100;操作2:B賬號加上100,執行操作1後,進程中斷,操作2沒有執行,但是A的賬戶少了100元,B又沒加上100元。從一個一致性狀態到了一個不一致的狀態。如果不能保證數據的一致性,也就是說操作結果不準確,那該操作就無意義了。為了保證一個事務不受其他事務的影響,一般通過設置事務的隔離級別和回滾機制保駕護航。關於事務的隔離級別:資料庫中隔離性的四種級別詳解與例子-爭取一文全懂 - 知乎專欄


自己的理解:

一致性:同生共死。


推薦閱讀:

如何設計一個資料庫,能夠存下如此「大量」的數據?
MySQL 查詢 select * from table where id in (幾百或幾千個 id) 如何提高效率?
uuid作為主鍵,還是用自增呢?
兩個800萬條目的表,相同主鍵,總數差了1個,如何高效的將其找出來?

TAG:資料庫 | 程序員 | 資料庫管理員DBA | DBMS | 事務並發 |