(原文發佈於我的博客:HW311)


距離上次更新已經很久了,過年的這段時間剛好是學期中,重度拖延症拖下來的projects和midterms堆在一起,實在忙得不可開交。這個週末閑下來一點,眼看著又一次寫博客的嘗試又要半途而廢,就抽出時間來總結記錄一下最近的學習歷程和心得吧。這一篇文章以及後續(可能會有)的一系列文章會簡單介紹一下資料庫事務管理(database transaction management)和分散式數據管理(distributed data management)。


前言

我們可能沒有意識到,資料庫已經滲透到我們生活中的點點滴滴,無處不在:早上起牀打開手機放一首喜歡的歌,播放器需要訪問資料庫調取歌曲信息;出門到銀行取錢,賬戶和交易信息都由銀行的資料庫保存和處理;晚上回到家打開網頁瀏覽新聞,網站後臺也一定會使用資料庫...... 我們也很難意識到,這個系統究竟有多麼複雜:想像你正在打一通美國到中國的跨洋長途電話,你的撥號請求會被附近你的服務商基站處理,並找出一條到中國目的基站的線路。這條線路會途徑許多層級的電話線路交換機。為了計算費用,每一處都有相應的資料庫記錄線路連接的數據,比如時長信息等等。更複雜的是,這樣規模的電話線路肯定不會為一家電信服務商單獨所有,電信服務商也會租用第三方線路提供商提供的線路,這些提供商也要根據線路使用情況向電信服務商收費。在你撥通太平洋另一端的電話號碼的短短几秒鐘內,十幾家公司的上百個資料庫被同時調動、十幾個不同的計費信息開始處理,要是電話沒能接通,這些數據也應當被相應地取消...... 作為一個程序員,單單是想像正確地實現這樣一次通話的數據處理都覺得複雜到頭疼,更別提現實情景中,這樣一個資料庫每秒需要並行處理上萬次通話了。

然而這樣複雜的一個系統,還對正確性有非常苛刻的要求。沒人希望自己被一通沒有撥通的長途電話收費,也沒人會接受一筆銀行轉賬扣了錢卻不知所蹤。在其他領域,保證正確性的做法通常是犧牲一部分的性能,但是在資料庫的很多使用場景中,每秒能夠處理的請求數量也非常關鍵,尤其是之前提到過的國際長途或者互聯網之類的全球性服務,犧牲了高並行的性能甚至可能完全無法使用。

綜上所述,如何在大規模、甚至分佈在全球的資料庫系統中保證高度並行處理中的正確性,絕對不是一個瑣碎容易(trivial)的問題。希望以上的介紹和例子能夠成功勾起讀者的興趣和注意。這個問題也許在當今的計算機科學研究中沒有很大熱度,但它還是非常有趣,也有很高的實用價值。如果不能清楚地瞭解問題是什麼就沒法給出優秀的解決方案,所以這篇文章還不會跳到具體的演算法和實現,我們先簡單瞭解一下如何為這個問題建立模型。


問題模型

在開始之前我覺得有必要給大家提醒一下這一篇文章和後續(可能會有)的一系列文章所討論的範疇:儘管我們感興趣的這個問題是一個工程上的實際問題,但是為了更好的理解它的性質我們還是要從簡化抽象的理論分析開始。所以我們在這一節裏提出的模型跟現實世界有一些落差。我們會先定義什麼是一個資料庫以及在資料庫上的操作;基於這個資料庫的模型,我們會介紹事務(transaction)和調度(schedule)的概念。

還有一點題外話,鑒於我學習這些概念的時候就是學的英文,而且我個人覺得有些概念的中文翻譯不是很到位(事務是什麼鬼啊喂!),我還是習慣在提及這些概念的時候使用英文。對,沒錯,我就是辭彙匱乏,看不慣中英夾雜的中文警察可以散了。

我們的目標

不知道目的地就不能合理地規劃路線,不清楚追求的目標就沒法設計出正確的演算法。在一切分析開始之前,我們先來仔細思考一下我們的目的是什麼、對於一個資料庫系統來說,「正確性」意味著什麼。

從直覺上說,一個資料庫就是用來保存數據,那隻要數據能按我們的預期被讀取寫入並且不出錯,這個資料庫就可以被認為是「正確」的。那「數據不出錯」又意味著什麼呢?這個簡單問題的答案並不是非常顯然。從語義上(syntactic)來看,一條數據擁有一個值,但是這個值是什麼並沒有具體的要求。但是大部分人很可能會認為存入一百塊錢錢之後銀行賬戶餘額變成了負數並不正確。於是我們發現,資料庫的正確性實際上跟context有關,不同的資料庫可能會有不同的constraints。比如在機票預訂或者座位預訂系統中,我們會要求剩餘票數或者座位數不能小於零;在銀行轉賬中轉賬雙方賬戶總額應當不變等等。這樣的正確性要求看起來似乎很簡單,但是在並行的情況下就沒這麼簡單了。舉個例子:程序A想要從賬戶x轉一百塊錢到賬戶y;程序B想要計算賬戶x和賬戶y的資產總額。假設賬戶x和賬戶y初始各有一千塊錢。如果A和B並行執行,那麼有可能B先讀取了x的餘額1000,然後A將100從x轉到y,最後B又讀取了y的餘額1100,計算得出x+y=2100!一個正確的資料庫系統應當避免這種情況的發生。

除了正確性之外,我們對資料庫可能還有很多其他的要求,比如支持操作回滾等等。限於篇幅,這篇文章只討論正確性,其他的內容會在後續(可能會有)的文章中討論。

資料庫模型

提到資料庫,很多人的第一想法可能就是 SELECT ... FROM ... WHERE ... 的SQL語句。這樣的關係資料庫模型在現實系統中非常常見,但是讀者之後可以看到,對於我們的分析來說這種模型並沒有反映出資料庫最底層最關鍵的不可分割的原子操作(atomic operations)。除此之外,這種基於關係的數據模型引入了我們不需要的概念:關係,在key-value store的資料庫中我們同樣會有正確性的問題。所以我們重新定義一個簡化的資料庫模型:

一個資料庫系統由一個不可再分(indivisible)的、互不重疊(non-overlapping)的數據對象(data objects)的集合構成: {o_1, o_2,dots,o_n} ,每一個object都有一個取值範圍(domain of values)。這個系統的一個狀態(state) 就是一個從object到value的映射。資料庫的操作有 readr(o_i) )和 writew(o_i) )。[1]

簡單來說,在這個模型中我們只關心最原始的、不可再分的數據的讀和寫,比如銀行賬戶的餘額,或者航班上的剩餘座位。在這個模型中單獨一條SQL語句也可能會被分拆成很多項操作。

資料庫事務(database transactions)

Transaction 是對資料庫系統中讀寫操作的更高一層抽象,代表了「一個單位」的資料庫操作。一個transaction可能包含對多個數據對象的多個讀寫操作,但是這些操作被視為一個整體、一個「transaction」。舉個例子,之前提到的銀行轉賬涉及了分別讀寫x和y兩個賬戶,在我們的資料庫模型中可能被表示為 r(x)~w(x)~r(y)~w(y) ,但是這四個操作應當被視為一個整體。

Transaction必須滿足ACID特性[1][2][3]:

  • Atomicity: 一個transaction就是一個不可再分的單位操作,要麼完全執行成功、要麼完全不執行。如果transaction中的某一項讀寫操作失敗了,那麼整個transaction都應該失敗,之前執行過的讀寫操作也應該回滾,整個系統應該恢復到執行這個transaction之前的狀態。
  • Consistency: 一個transaction應當使資料庫從一個合法的狀態變化到另一個合法的狀態,保證數據正確性和完整性。在我們之前舉的銀行轉賬和計算總額的例子中,最終的狀態就不合法,因為計算得出的總額並不正確,不滿足銀行賬戶這個資料庫系統的implied constraints.
  • Isolation: 並發執行的transactions不會觀察到其他transactions執行中的中間結果或者說副作用(partial effects)。還是之前的銀行轉賬的例子,導致數據不正確、consistency被破壞的原因就是A、B兩個程序沒有滿足isolation,B看到了A的partial effect.
  • Durability: 一旦一個transaction成功執行,那執行的結果就應當是(相對)永久性的。

Transaction這個概念放在資料庫的使用情景中看非常自然,比如銀行交易(transaction)本身就是一個transaction。ACID的要求也是現實生活中非常普遍的,比如預訂聯程航班時,要麼就成功預訂所有航段的機票、要麼就一張機票都不預訂。更加重要的是,transaction這個概念為資料庫的用戶和程序員提供了一層非常方便的抽象,用戶只需要使用transaction來編寫業務邏輯就可以享受其ACID性質,而不需要自己處理複雜的正確性需求。我個人認為這樣的層層抽象(還比如網路的OSI多層模型)是計算機科學非常優雅的美感之一。

為了方便描述和使用transaction模型,這裡給出正式定義[1]:

一個transaction T_i 是一個偏序 prec_i

T_isubseteq{r_i(x), w_i(x)mid x~mathrm{is~an~object}}cup{a_i, c_i}cup{b_i} 其中, a_i 代表abort; c_i 代表commit; b_i 代表begin。 T_ib_i 開始, a_ic_i 只能存在一個,並且是 T_i 的最終操作。為了簡潔,b_ic_i 經常被省略。 如果操作 o_i(x)o_i(x) 屬於 T_i ,那要麼 o_i(x)prec_i o_i(x) ,要麼 o_i(x)prec_i o_i(x)T_i 中寫操作 w_i(x) 寫入數據對象 x 的值是一個關於 T_i 之前(由 prec_i 定義)所有讀取的值的函數。比如在 T_1: r_1(x)~r_1(y)~w_1(z) 中, z 的值是關於 xy 的某個函數 f(x, y) ,也就是說我們沒有對寫操作做任何假設,寫入的值可能會取決於 T_i 觀察到的所有值。

事務調度(schedule)

明白了transaction的概念,理解schedule就很簡單了:一個schedule就是多個transactions的交錯,就是多個transaction中的多個數據操作用什麼順序執行的一個「計劃」(或者執行過的歷史,所以有時也叫history)。

最簡單的schedule就是按順序執行多個transactions:

S: T_2~T_1~T_3

類似這樣的schedule叫做 serial schedule(串列調度)。Serial schedule在我們的分析中非常重要,但是就執行效率來說並不理想:如果 T_2 被某個數據操作block了,那麼等待數據的這段時間就會被浪費,從而增加了 T_1T_3 的響應時間,所以schedule中不同的transaction很可能是交錯執行的:

egin{align} T_1&: r_1(x)~w_1(y)\  T_2&: w_2(z)~w_2(x)\[0.6em] S&: r_1(x)~w_2(z)~w_1(y)~w_2(x) end{align}

Schedule的正式定義是[1]:

一個schedule S=(	au, prec_S) ,其中: 	au 是transaction的集合; prec_S	au 中transactions中的數據操作的偏序,並滿足: forall,T_iin	au, prec_isubseteqprec_S ,也就是說,schedule會保持每個transaction中操作自己的順序。


可串列性(serializability)

簡單來說,transaction就是具有意義的資料庫操作最小單位,可能包含多個具體的數據讀寫操作;schedule描述了多個transaction如何穿插執行。有了基本的模型,我們的目標也更加明確了:如何得出高並發並且正確的schedule?或者在現實應用中由於複雜度和機能限制,如何辨別並拒絕錯誤的、可能會破壞數據consistency的schedule?

要回答這兩個問題,我們首先需要回答「正確」對於schedule來說意味著什麼(déjà vu!)。

直覺上分析,schedule可能出現的「錯誤」來源於多個transaction的並發執行。再回顧我們之前提到的銀行轉賬的例子,在我們的模型中可以描述為:

egin{align}T_A&: r_A(x)~w_A(x)~r_A(y)~w_A(y)\   T_B&: r_B(x)~r_B(y)~w_B(sum)\[0.6em]   S&: r_B(x)~r_A(x)~w_A(x)~r_A(y)~w_A(y)~r_B(y)~w_B(sum) end{align}

在schedule S 中, T_B 讀取了 T_A 執行的中間結果,ACID中的isolation被破壞了,所以產生了inconsistent的結果。既然問題出在並發執行上,那我們確保順序執行不就好了嗎?在這個例子中, S: T_A~T_B 或者 S: T_B~T_A 都可以保證結果的正確性。但是我們上文也提到,這樣的serial schedule性能非常差,respond time和throughput都不理想,在某些應用中甚至完全不可用。但起碼我們思考的方向是對的:我們既想要高並發的速度,又想要串列執行的isolation和正確性。那我們只需要保證一個schedule是「等價於」某個serial schedule的就行了,這就是 serializability(可串列性/化) 的概念[1][4]:

如果一個schedule S=(	au, prec_S) 等價於 某個serial schedule S=(	au, prec_{S}) ,那麼 S 就是serializable(可串列)的。

那麼問題又來了:對於兩個schedule來說,什麼叫做等價呢?這就是我們要討論的關鍵問題了。接下來的幾個小節會討論三種不同的定義,並相應地得出 final state serializabilityview serializability、和 conflict serializability 的概念。(我並沒有找到這些概念合適的中文翻譯,網路上的一些翻譯在我感覺也不是很貼切,所以我鬥膽自行翻譯了這三個概念,但還是會主要使用英文原文。)

終態可串列(final state serializability)

提起「等價」的定義,我們可能首先會想到function中「external equivalence」的概念:把兩個function當作黑盒,如果它們把所有相同的輸入都映射到相同的輸出,那從一個使用者的視角來看,這兩個function就是等價的——我們在外部並不能觀察到任何區別。同樣地,我們也可以從這個角度定義schedule之間的等價關係,如果兩個schedule在所有相同的資料庫狀態下執行都會使資料庫轉移到相同的最終狀態,那它們對我們來說就是等價的。正式地說[1]:

我們說兩個schedule S_1S_2終態等價的(final state equivalent),如果它們滿足: 它們涉及的transactions相同;以及 在所有對寫操作的解讀下( w_i(x) 可能是任何關於之前讀取的數據的函數 f ),對於所有的初始狀態 IIxrightarrow{S_1}ILeftrightarrow Ixrightarrow{S_2}I

我們可以相應地給出final state serializability的定義:

如果一個schedule S=(	au, prec_S) final state equivalent to 某個serial schedule S=(	au, prec_{S}) ,那麼 S 就是final state serializable的。

這個定義非常直觀,可是我們該怎麼檢查兩個schedule是不是final state equivalent的呢?根據定義來檢查肯定不現實,我們沒法去檢查每一種可能的初始狀態,但是我們可以通過給schedule中的數據操作構建一個圖來進行驗證[1]:

  • 對於每個schedule S ,定義一個 augmented schedule hat{S}hat{S} 除了包括 S 中的所有transaction之外,還包含兩個輔助transaction T_0T_alpha :
    • T_0 只有寫操作,寫入所有的object,相當於資料庫中所有object的初始值;
    • T_alpha 只有讀操作,讀取所有的object,相當於資料庫中所有object的最終值。
  • hat{S} 保留 S 中的操作的順序,並保證 T_0S 之前, T_alphaS 之後。( hat{S}equiv T_0~S~T_alpha
  • 根據這個augmented schedule hat{S} ,構建一個有向圖 D(S)=(V, E)
    • 節點 Vhat{S} 中所有的數據操作(e.g., r_i(x), w_j(y), dots );
    • E 包含 o_1	o o_2 ,如果 o_1prec_{hat{S}}o_2 ,以及:
      • 在某個transaction T_i 中, o_1=r_i(x), o_2=w_i(x) ;或者
      • o_1o_2 屬於不同的transaction,並且 o_2 o_1 中讀取(read from)

我們說 o_2 o_1 中讀取o_2 reads from o_1 ),如果:對於某個數據對象 xo_1=w_i(x), o_2=r_j(x) ;並且 o_1prec_S o_2 ,並且 o_1o_2 之間沒有其他操作也寫入了 x

舉個例子,對於以下兩個schedule S_1, S_2

egin{align}   T_1&: r_1(x)~w_1(y)\   T_2&: r_2(x)~w_2(x)~w_2(y)\[0.6em]   S_1&: r_2(x)~w_2(x)~r_1(x)~w_1(y)~w_2(y)\   S_2&: r_1(x)~w_1(y)~r_2(x)~w_2(x)~w_2(y) end{align}

S_1: r_2(x), w_2(x), r_1(x), w_1(y), w_2(y) 所對應的圖 D(S_1) 是:

S_2: r_1(x), w_1(y), r_2(x), w_2(x), w_2(y) 所對應的圖 D(S_2) 是:

(例子及圖片出自[1])

仔細觀察我們就會發現, D(S) 實際上描述了信息是怎麼在 S 中「流動」的: w_0(x)	o r_1(x) 代表了 r_1 讀取了 w_0x 的初始信息從 w_0 「流動」到了 r_1r_2(x)	o w_2(x) 代表了 r_2 之後的 w_2 可能用到了 r_2 讀取到的數據,信息從 r_2 「流動」並反映到了 w_2 。所以,指向最終代表最終狀態的 r_alpha 的path就代表了構成資料庫最終狀態的「信息流」。

綜上所述,如果我們把 D(S_1)D(S_2) 中所有無法達到 r_alpha(cdot) 的節點都刪除,並且得到的結果完全相同的話,那麼 S_1S_2 就是final state equivalent的:

視域可串列(view serializability)

Final state serializability的定義看起來很簡單也很合理,但是它本質上還是一種忽略了內部結構的external equivalence. 然而這種忽略在我們的模型中有時也會產生錯誤。比如我們有一個資料庫 d={x, y, z} ,這個資料庫的constraint是 x=yland zge 0 . 初始狀態是 {x=5, y=5, z=2} 。考慮如下的兩個應用程序:

egin{align}   T_1:& & T_2:&\   &x:=7 & &z:=-1\   &y:=7 & &mathrm{if}~(x=y)~z:=1 end{align}

如果我們以 S: w_2(z, -1), w_1(x, 7), r_2(x, 7), r_2(y, 5), w_1(y, 7) 來執行這兩個程序,最終的狀態是 {x=7, y=7, z=-1} ,並不滿足我們的constraint,data consistency被打破了。有趣的地方在於, T_1T_2 以任意順序串列執行都不會造成inconsistency;我們的schedule S 也是final state serializable的(讀者可以用上文介紹的方法自行驗證),卻產生了inconsistent的結果。

仔細觀察後我們不難發現,造成這一現象的原因是,在final state serializability中,我們把transaction和schedule當成了黑盒,假設了資料庫的狀態僅僅取決於輸出(寫操作)的狀態。但是從這個例子中我們可以看到, T_2 中的讀操作 r_2(x)r_2(y) 也對狀態產生了影響,因為我們的簡化模型裏並沒有考慮到控制流等等同樣會基於讀取到的信息對輸出產生影響的內部結構。

根據這個觀察,如果我們也同時保證所有transaction「看到的」數據狀態也等同於它們在sequential execution中所「看到的」狀態,這個問題不就解決了嗎?於是我們有了 view equivalenceview serializability 的定義[1][4]:

我們說兩個schedule S_1S_2視域等價的(view equivalent),如果它們滿足: 它們涉及的transactions相同;以及 對於 S_1S_2 中的任意transaction T_iT_j ,如果在 S_1 中, o_iin T_i o_jin T_j 中讀取(read from),那麼在 S_2 中, o_i o_j 中讀取(read from);以及 對於任意一個數據對象 x ,如果在 S_1T_i 最後寫入了 x ,那麼在 S_2T_i 也最後寫入 x

相應地給出view serializability的定義:

如果一個schedule S=(	au, prec_S) view equivalent to 某個serial schedule S=(	au, prec_{S}) ,那麼 S 就是view serializable的。

檢查兩個schedule S_1, S_2 是否view equivalent的方法與檢查final state equivalence的方法類似:構造出圖 D(S_1)D(S_2) ,但是因為這次我們同樣關心每個操作所「看到」的狀態是否一致,所以不能刪除無法到達最終狀態的節點。所以,如果 D(S_1)D(S_2) 完全相同,那麼 S_1S_2 就是view equivalent的。顯然,我們可以發現,view serializable的schedule也一定是final state serializable的,反之則不成立。

下面是一個view serializable的schedule的例子, Sequiv T_2~T_1~T_3 ,讀者可以自行嘗試驗證:

egin{align}   T_1&: w_1(y)~r_1(x)~w_1(x)\   T_2&: w_2(y)~w_2(x)\   T_3&: r_3(y)~w_3(y)\[0.6em]   S&: w_1(y)~r_3(y)~w_2(y)~w_2(x)~r_1(x)~w_1(x)~w_3(y) end{align}

衝突可串列(conflict serializability)

View serializability似乎一舉解決了consistency的問題(也確實解決了[4]),但是判斷一個schedule是不是view serializable卻是一個NP-complete的問題[4]。這樣的複雜度在現實世界的資料庫系統中是不現實的,我們還需要一個更易於驗證的serializability定義。

重新思考並發schedule造成inconsistency的原因,我們發現實際上關鍵僅僅在於某幾個數據操作的順序:在一個schedule中這些關鍵操作是這種順序;在另一個schedule中可能是另一種順序。這種順序的區別導致了isolation的破壞和inconsistency。比如在我們最開始的銀行轉賬的例子中,關鍵的 w_A(x), r_B(x)w_A(y), r_B(y) 的執行順序導致了 T_B 讀取了 T_A 的partial effect. 如果我們確保在schedule S 中,所有這樣的關鍵操作的執行順序都和某個serial schedule中這些操作的執行順序相同,那麼我們就可以保證consistency了。

那什麼樣的操作是關鍵操作呢?根據我們的觀察,不滿足「交換律」的操作就是這樣的關鍵操作: r_i(cdot)r_j(cdot) 是滿足交換律的,任意一種執行順序都會產生同樣的效果;但是 r_i(x)w_j(x)w_i(x)w_j(x) 就不滿足交換律,不同的順序會產生不同的狀態。所以我們定義 衝突操作(conflicting operations)[1]:

兩個數據操作 o_io_j 是一對 衝突操作(conflicting operations),如果它們滿足: 它們屬於不同的transaction;以及 其中至少一個操作是寫操作。

由此,我們可以給出 conflict equivalenceconflict serializability 的定義[1][4]:

我們說兩個schedule S_1S_2衝突等價的(conflict equivalent),如果它們滿足: 它們涉及的transactions相同;以及 它們對 衝突操作(conflicting operations) 的排序相同。

相應地,

如果一個schedule S=(	au, prec_S) conflict equivalent to 某個serial schedule S=(	au, prec_{S}) ,那麼 S 就是conflict serializable的。

檢查一個schedule S 是否conflict serializable非常簡單:

  • 根據 S 構造一個有向圖:串列化圖(serialization graph) SG(S)=(V, E)
    • 節點 VS 中的transactions 	au
    • E 包含 T_i	o T_j ,如果 T_i, T_j 中有一對conflicting operations o_iin T_i, o_jin T_j ,並且 o_iS 中早於 o_jo_iprec_S o_j )

需要注意的是,這個serialization graph和之前final state serializability和view serializability中用到的圖不同,serialization graph以transaction為單位,實際上描述了存在conflict的transaction之間的執行順序。我們可以發現,如果 T_i	o T_jSG(S) 的一條邊,意味著 T_iT_j 先執行了某項不滿足交換律、會導致衝突的操作,那麼如果存在一個和 S 等價的serial schedule S ,那麼在 ST_i 也必定先於 T_j 執行( T_iprec_{S}T_j )。所以,如果serialization graph SG(S) 中出現了一個環,那麼 S 肯定就不是conflict serializable的。

所以conflict serializability的判斷可以通過判斷一個schedule S 的serialization graph SG(S) 有沒有環來完成,而檢查一個圖有沒有環可以在 O(|E|) ,或者說 O(n^2) 時間完成。

如果一個schedule是conflict serializable的,它也一定是view serializable的,反之則不成立。所以conflict serializability實際上是犧牲了一部分的並發程度來使檢驗變得更容易。


後記

至此,我們建立了模型,從理論上分析了serializability的合理性,並通過三種不同的角度定義了三種不同的serializability:final state serializability、view serializability、和conflict serializability. 在現實應用中,final state serializability和view serializability要麼存在正確性的問題,要麼太過複雜難以實用,所以我們採用了放棄了一部分合法的schedule但是較為簡單的conflict serializability.

作為高度抽象的理論分析,serializability這個概念本身也有一些侷限性。比如它沒有將實際運行的資料庫應用或者服務的語境納入考慮,如果我對「正確性」的要求沒有達到serializable的程度呢?這就要求我們回到本文一開始的問題、更加細緻地討論究竟「正確」這個詞意味著什麼了。感興趣的讀者可以參考閱讀 Correctness Criteria Beyond Serializability. [5]

在這個系列之後(可能會有)的文章裏,我們會繼續關於serializability的旅程,介紹一些實現conflict serializability的演算法,比如 two-phase locking;還有可能討論討論資料庫系統中除consistency之外的需求,比如 recoverability 等等。


參考資料

[1]: UCI CS223: Transaction Processing and Distributed Data Management

[2]: Database transaction - Wikipedia

[3]: ACID (computer science) - Wikipedia

[4]: Serializability - Wikipedia

[5]: Correctness Criteria Beyond Serializability

推薦閱讀:

相關文章