ACID性質是資料庫理論中的奠基石,它定義了一個理論上可靠資料庫所必須具備的四個性質:原子性,一致性,隔離性和持久性。雖然這四個性質都很重要,但是隔離性最為靈活。大部分資料庫都提供了一些可供選擇的隔離等級,且現在許多庫都增加了附加層來建立顆粒度更細的隔離。隔離等級應用範圍如此之廣主要是因為放寬隔離約束往往會使得可擴充性和效能提高几個數量級。
串列一致性是可用的最古老最高的隔離等級之一,它之所以倍受青睞是因為其提供的簡單編程模型,即每次僅能有一個事務對給定的資源進行操作,這就避免了很多潛在的資源問題。儘管如此,大部分應用程式(尤其是Web應用程式)都不採用這種層級非常高的隔離,因為從終端使用者的角度來看這是不切實際的-任何一個擁有大量使用者群的應用程式在訪問共用資源時都將會有幾分鐘的延遲,而這會使得使用者量迅速減少。弱一致性和最終一致性在大規模分布式資料來源中,例如Web中,隨處可見。好幾個成功的大型Web應用(例如,eBay和Amazon)都顯示出樂觀的(optimistic)弱一致性要比傳統悲觀的(pessimistic)機制在擴充性方面好得多。本文將一窺八種不同的隔離等級。學會適當的放寬資料一致性的約束,你可以在自己的應用程式中使用這八種隔離等級來獲得更好的效能和可擴充性。
並發控制的主要目標是為了確保事務被隔離且不會影響到其他事務。要達到進階別的隔離需以犧牲效能為代價。並發控制可以用悲觀或者樂觀的機制來實現。大部分關係型資料庫都使用了悲觀機制來實現寫入最佳化。悲觀機制採用了鎖,通過使用鎖它可以阻塞一些操作或者進行某些形式的衝突檢測。當一個表格,頁面或是行被修改後,悲觀機制中的鎖可以用來阻塞其他潛在的訪問修改資源的事務。然而,樂觀機制並不採用任何鎖,它僅僅依賴於衝突檢測來維護事務隔離。樂觀機制採用的衝突檢測可以允許所有的讀操作,並在事務結束時檢驗其一致性。如果檢測到衝突,那麼事務會進行復原或重做。大部分web伺服器都是讀入最佳化,因此使用了樂觀機制。通過允許所有的讀入操作,樂觀機制既可以保證很高的讀寫輸送量,也可以在資源不是一直改變的情況下保證資料的一致性。
下面列出的隔離等級是用來協助Web開發人員更好的理解他們編程模型中放置的約束,協助系統架構師和開發人員共同討論如何在保持必要的資料完整性的同時選擇最有效隔離等級。它們按照最少隔離(未提交讀)到最多隔離(序列化)的順序列出。
1、未提交讀(Read Uncommitted)
未提交讀隔離等級需要事務間很少的隔離。每一個讀操作都能看到事務中等待的寫操作(髒讀)。然而已經提交的寫操作必須要有一個串列順序來防止髒寫。悲觀機制會阻塞有衝突的寫操作直到其他寫操作已經被提交或已經復原。樂觀機制不會鎖住這些操作,它會允許所有的操作都通過。如果一個串連進行了復原,那麼接下來修改同一塊資料的其他動作也會被復原。在這種層級中,共用緩衝可以不加驗證的進行使用。這種隔離等級最好在不需要事務(比如唯讀資料集),或者事務只在獨佔資料庫時才修改的情況下使用。
例子:一個只在離線情況下更新的檔案資料庫,或者不在事務中使用的審核/登陸(audit/logging)表。
2、已提交讀(Read Committed)
已提交讀可以讀取系統中任何已經提交的狀態,並且可以不加驗證(混合狀態)的進行緩衝,只需當前串連中發生的改變能夠反映到結果中即可。悲觀機制將其實現為單調視圖。樂觀事務則隔離儲存所有的改動,使得它們直到提交後才可用。讀已提交使用一個非常樂觀的機制,它延遲寫入所有的變化直到事務被提交為止。這種形式的樂觀隔離可以在不阻塞讀操作的情況下實現複雜的寫入操作,並且它沒有驗證模式。共用緩衝只能在已提交的狀態中使用。這種隔離等級最好在結果可以使用舊值,且事務只能用於寫入操作的情況下使用。
例子:一個不必顯示當前最新文章的線上論壇,且它的文章間資料不相衝突。
3、單調視圖(Monotonic View )
單調視圖是對讀已提交的一個擴充,它其中的事務在執行時會觀察資料庫中一個單調上升的狀態。在這種層級中,如果有明顯的寫入事務,那麼悲觀事務會在讀入操作中被阻塞。樂觀事務會像在讀已提交中一樣操作,隔離儲存所有的改動,並且會驗證它們的緩衝以確保其仍然合法。這種層級可以定期地同步資料庫副本,且最好在不需要事務或者僅存在寫操作事務的情況下使用。
例子:一個僅能由一個人來修改的使用者偏好表。
4、快照讀取(Snapshot Reads)
快照讀取擴充了單調視圖,它可以保證查詢結果都能反映到資料庫一致的快照中。悲觀機制會在讀操作時阻礙其他影響結果的寫入操作。樂觀機制則允許其他的寫入操作,並通知讀取事務某部分已經發生改變並進行復原。想要實現一個樂觀機制,必須在讀操作結束之前驗證是否有什麼並行的寫入操作修改了結果,如果有的話,那麼結果可能會重做或復原。這個檢驗過程可能只是簡單的檢查同一張表中是否出現了寫入操作,或者只是檢查改動的查詢結果。樂觀隔離等級可以很輕鬆地檢測出衝突,並且在允許並發讀入操作的過程中,支援寫入操作。這種層級只要能夠讀取到快照,便可以定期地同步資料庫副本。最好在寫入操作很少,不想與讀入操作衝突,且查詢結果需要一致性的時候使用這種隔離等級。
例子::一個查詢比修改頻繁,且只保留最新值的貨幣換位表或者查詢表。
5、遊標穩定性(Cursor Stability)
遊標穩定性隔離擴充了讀已提交,並且是許多關係型資料預設的隔離等級。在這種隔離等級中,悲觀事務如果在一個單獨的語句中執行的話,必須得指定它將修改的記錄。這通常可以在"SELECT"查詢後附加“FOR UPDATE”關鍵字來實現。在這種情況下,其他衝突的讀寫悲觀事務都將被阻塞直到該事務結束為止。樂觀事務會跟蹤提交時被驗證的所有修改記錄/實體的版本號碼。這是一種很流行的樂觀隔離等級,因此被所有的主流對象關係映射庫支援。在Java持久性API中,可以使用FLUSH_ON_COMMIT(儘管查詢可能不影響本地改動)來接近達到這種層級,且如果檢測到衝突的話,可以拋出OptimisticLockException 異常。這種隔離也同樣可以用在HTTP頭域的If-Match或者 If-Unmodified-Since中,它可以用來在更新前對比上一個資源的版本或者時間戳記。這種層級最好在實體由外部資訊(不從資料庫中讀取)更改,或者改動不會彼此覆蓋的情況下使用。
例子:一個共用的公司目錄或者一個wiki。
6、可重複讀取(Repeatable Read)
可重複讀取層級擴充了遊標穩定性,它保證事務內的任何資料在事務過程中都不會被修改或者移除。悲觀事務需要讀取所有記錄上的鎖,並阻塞其他服務來修改這些記錄。樂觀事務則會跟蹤所有的記錄或者實體,並檢查它們是否在提交時被修改過。這種層級最好在實體狀態能夠影響其他實體,或者事務由讀寫操作構成的情況下使用。
例子:一個訂單追蹤資料庫,它從一個實體中讀取值並用它來計算其他的實體值。
7、快照隔離(Snapshot Isolation)
快照隔離擴充了快照讀取和可重複讀取,它保證事務中所有進行的讀操作都能看到資料庫中一致的快照。事務執行的的任何讀操作都會有相同的結果,而不管它們在事務中執行的早晚。這和可重複讀取不同,因為快照隔離能夠防止幻讀(查詢結果不斷變化)。許多關係型資料庫採用多版本並發控制(也可以叫做 SERIALIZABLE)來支援這種層級,實現方法是通過鎖和衝突檢測的組合。在這種層級中,考慮到它可能與悲觀機制或者樂觀機制相衝突,因此事務一定要做好復原的準備。悲觀機制會通過鎖住資源來嘗試減少衝突的機會,但是必須在事務提交後將這些改動合并。樂觀機制也會使用多版本並發控制,但是它不會阻塞其他可能產生潛在衝突操作的事務,反而是將衝突的事務進行復原。這種層級的隔離最好在事務可以讀取和修改多個記錄的情況下使用。
例子:一個基於系統狀態規則的工作流程系統。
8、可串列性(Serializability)
串列性是快照隔離的擴充,它要求所有的事務都必須一個接著一個的出現,就好比它們被序列化過一樣。悲觀機制需要鎖住所有評估過的查詢,以防止寫入操作影響這些結果。而樂觀機制則跟蹤所有評估過的查詢,並在事務結束時使用一個後向驗證或前向驗證的模式來檢查是否有並行寫入操作影響了並行讀入操作,如果有的話,它會將衝突事務外的所有事務進行復原。在這種隔離等級中,任何提交事務都不會改變系統的表徵狀態。最好在需要完整資料一致性的情況下使用這個層級的隔離。
例子:一個進行範圍查詢來計算新值的賬目系統。
總結
下面是本文提到的隔離等級的匯總表,它可以協助你找到最適合你應用程式的層級。
事務在不同隔離等級中可能的衝突類型:
| |
髒寫 |
髒讀 |
混合狀態 |
不一致讀 |
覆寫 |
不可重複 |
幻讀 |
不一致性 |
| 未提交讀 |
不可以 |
可以 |
可以 |
可以 |
可以 |
可以 |
可以 |
可以 |
| 已提交讀 |
不可以 |
不可以 |
可以 |
可以 |
可以 |
可以 |
可以 |
可以 |
| 單調視圖 |
不可以 |
不可以 |
不可以 |
可以 |
可以 |
可以 |
可以 |
可以 |
| 快照讀取 |
不可以 |
不可以 |
不可以 |
不可以d |
可以 |
可以 |
可以 |
可以 |
| 遊標穩定性 |
不可以 |
不可以 |
可以 |
可以 |
不可以 |
可以 |
可以 |
可以 |
| 可重複讀取 |
不可以 |
不可以 |
可以 |
可以 |
不可以 |
不可以 |
可以 |
可以 |
| 快照隔離 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
可以 |
| 可串列性 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
不同隔離等級的最佳前提:
| |
緩衝 |
資料同步 |
樂觀衝突模式 |
建議操作 |
例子 |
| 未提交讀 |
允許緩衝 |
間歇的 |
檢測髒寫 |
不能並發讀寫 |
檔案 |
| 已提交讀 |
允許緩衝 |
間歇的 |
沒有衝突檢測 |
單調的讀/寫 |
Web論壇 |
| 單調視圖 |
必須被驗證 |
周期的 |
沒有衝突檢測 |
組合讀入 |
使用者偏好 |
| 快照讀取 |
必須被驗證 |
周期的 |
對比讀入與修改內容 |
一致性讀入 |
查詢表 |
| 遊標穩定性 |
允許緩衝 |
間歇的 |
對比修改的實體版本 |
CRUD服務 |
目錄 |
| 可重複讀取 |
允許緩衝 |
間歇的 |
對比讀入的實體版本 |
讀/寫實體 |
訂單跟蹤 |
| 快照隔離 |
必須被驗證 |
周期的 |
對比讀入的實體版本 |
同步實體 |
工作流程 |
| 可串列性 |
必須被驗證 |
完整同步 |
對比查詢與修改內容 |
完善資料一致性 |
賬目 |
資料一致性在資料庫應用程式中至關重要-它允許開發人員在分布式環境下使用資料。儘管強一致性層級如可串列性提供了一個簡單的編程模型,但是它們會導致開銷 過大,操作阻塞或者交易回復,這對於很多應用程式來說都是不必要的。如果有其他問題的話,可以使用更加適當的隔離等級來協助開發人員和系統架構師,讓他們 在保持效能和開銷平衡的前提下更好的理解資料一致性的需求。
查看英文原文:Eight Isolation Levels Every Web Developer Should Know。