關鍵要點html
僅從ACID或非ACID角度考慮問題是不夠的,你應知道你的數據庫支持何種事務隔離級別。 一些數據庫宣稱本身具備「最終一致性」,但卻可能對重複查詢返回不一致的結果。 相比於你所尋求的數據庫,一些數據庫提供更高的事務隔離級別。 髒讀可致使同一記錄獲得兩個版本,或是徹底地丟失一條記錄。 在同一事務中屢次從新運行同一查詢後,可能會出現幻讀。mysql
最近MongoDB登上了Reddit的頭條,由於MongoDB的核心開發者David Glasser痛苦地認識到MongoDB默認會執行髒讀(https://engineering.meteor.com/mongodb-queries-dont-always-return-all-matching-documents-654b6594a827)。sql
在本文中,咱們將解釋什麼是事務隔離級別和髒讀,並給出一些廣受歡迎的數據庫是如何實現它們的。ANSI SQL給出了四種標準的事務隔離級別:可序列化(Serializable)(應該翻譯爲串行化的事務)、可重複讀(Repeatable reads)、提交讀(Read committed)和未提交讀(Read uncommitted)。mongodb
許多數據庫缺省是提交讀的,這保證了在事務運行期間用戶看不到轉變中的數據。提交讀的實現經過在讀取時暫時性地獲取鎖,並持有寫入鎖直至事務提交。若是在一個事務中須要屢次重複同一讀取,並想要「合理地肯定」全部的讀取老是會獲得一樣的結果,這要在整個過程期間持有讀取鎖。在使用可重複讀事務隔離級別時,上述操做是自動完成的。數據庫
咱們這裏所說的「合理地肯定」可重複讀,是由於存在「幻讀」(phantom reads)的可能性。當執行使用了WHERE語句的查詢時,相似於「WHERE Status=1」,就有可能發生幻讀。雖然所涉及的行將被鎖上,可是這並不能阻止匹配WHERE條件的新行被添加進來。「幻」(phantom)一詞指在查詢第二次執行時所出現的行。爲確保在同一事務中的兩次讀取會返回一樣的數據,可以使用可序列化(串行的)事務隔離級別。可序列化使用了「範圍鎖」,避免了匹配WHERE條件的新行添加到一個開放的事務中。併發
通常狀況下,因爲鎖競爭的存在,事務隔離級別越高,性能越差。所以爲了改進讀取性能,一些數據庫還支持未提交讀。該事務隔離級別將無視鎖的存在(事實上其在SQL Server中被稱爲「NOLOCK」),所以該級別下可執行髒讀。oracle
髒讀所存在的問題post
在探討髒讀問題以前,你必需要理解表並不是是真實存在於數據庫中的,表只是一個邏輯結構。事實上你的數據是按一個或多個索引進行存儲的。主索引在大多數數據庫中被稱爲「聚束索引」或「堆」(該術語在各NoSQL數據庫中各不相同)。於是當執行插入操做時,須要在每一個索引中插入一行。當執行更新操做時,數據庫引擎僅需訪問指到被改變列的索引。但更新操做經常必需要在每一個索引上執行兩個操做,即從舊的位置刪除並在新的位置插入。在下圖中,你可看見一個普通的表,還有表中IX_Customer_State和PK_Customer對象更新操做的執行計劃。鑑於表的FullName列並未改變,因此能夠跳過IX_Customer_FullName索引。 性能
注意在SQL Server中,PK前綴指代主鍵,一般也是用於聚束索引的鍵。IX用於指代非聚束索引。其它的數據具備它們本身的命名規範。解決了上述問題,讓咱們看一下髒讀致使不一致數據的多種途徑。測試
未提交讀問題易於理解。在事務被徹底提交以前,若是無視寫入鎖的存在,使用「未提交讀」的SELECT語句就能夠就看到新插入或更新的行。若是這些轉變操做這時被回滾,從邏輯上說,SELECT操做將返回並不存在的數據。
若是數據在更新操做過程當中被移動了,這就產生了雙重讀取。例如,你正在讀取全部的客戶記錄的狀態。若是在你讀取「California」記錄和讀取「Texas」記錄之間,上面所說的更新語句被執行了,你就能看見「客戶1253」記錄兩次,一次是舊值,一次是新值。
記錄丟失發生的方式相同。若是咱們提取「客戶1253」記錄並將其從「Texas」記錄移動到「Alaska」記錄,並再次使用狀態去選擇數據,你可能會徹底地丟失該記錄。這就是發生在David Glasser的MongoDB數據庫中的事情。因爲在更新操做期間讀取了索引,查詢丟失了記錄。
髒讀也會妨礙到排序操做,該問題的出現取決於數據庫的設計方式及特定的執行計劃。例如,髒讀可能發生於執行計劃對全部候選數據行採集指針信息時,若是在其後一行數據被更新了,但實際上執行引擎仍是會使用已被採集的指針信息從原始位置拷貝數據。
快照隔離,或被稱爲「行級版本控制」
爲在避免髒讀問題的同時提供好的性能,許多數據庫支持快照隔離語義。運行於快照隔離狀態下,當前的事務不能看到任何先於其啓動的其它事務的結果。快照隔離的實現是經過作被改變行的臨時拷貝,而非僅依靠於鎖機制,所以它也常被稱爲「行級版本控制」。不少支持快照隔離語義的數據庫在被請求使用「提交讀」事務隔離時,會自動使用快照隔離。
SQL Server中的事務隔離級別
SQL Server支持全部四種ANSI SQL事務隔離級別,外加一種顯式的快照隔離級別。提交讀可能也使用快照語義,這取決於數據庫中READ_COMMITTED_SNAPSHOT選項的配置方式。在開關該選項前,你的數據庫須要作充分的測試。雖然提交讀能夠提高讀取性能,但它也同時下降了寫入性能。尤爲是tempdb被部署在慢速磁盤上時,由於這存儲了行的舊版本。在SELECT語句中可使用臭名昭著的NOLOCK指示符。NOLOCK的做用等同於將事務運行設置爲未提交讀。這在SQL Server 2000及更早期的版本中被大量地使用,由於那時並無提供行級版本控制。儘管如今再也不必要或不建議這樣作,可是該習慣仍然保留着。 更多信息參見「設置事務隔離級別 (Transact-SQL)」.https://msdn.microsoft.com/en-us/library/ms173763.aspx
PostgreSQL中的事務隔離級別
雖然官方宣稱PostgreSQL支持全部四種ANSI事務隔離級別,但事實上PostgreSQL中只有三種事務隔離級別。每當查詢請求「未提交讀」時,PostgreSQL就默默地將其升級爲「提交讀」。所以PostgreSQL不容許髒讀。當你選取「未提交讀」級別時,事實上你獲得了「提交讀」,在PostgreSQL對可重複讀的實現中,髒讀是不可能發生的,所以實際的事務隔離級別可能比你所選取的要更加嚴格。這是被SQL標準所容許的,由於四種事務隔離級別僅定義了事務中必定不能發生的現象,它們並未定義應該發生哪一種現象。
PostgreSQL並未顯式地提供快照隔離。固然快照隔離是在使用提交讀時自動發生的。這是由於PostgreSQL的設計從一開始就考慮了多版本併發控制。在9.1版本以前,PostgreSQL不提供可序列化事務,會將它們靜默降級爲可重複讀。但當前全部仍在支持的PostgreSQL版本中都再也不有這個限制了。 更多的信息參見PostgreSQL官方文檔的13.2節,「 事務隔離」https://www.postgresql.org/docs/9.1/static/transaction-iso.html.
MySQL中的事務隔離級別
InnoDB默認爲可重複讀,可是提供全部四種ANSI SQL事務隔離級別。提交讀使用快照隔離語義。更多InnoDB相關的信息,參見MySQL官方文檔的15.3.2.1節「 事務隔離等級」https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
事務在使用MyISAM存儲引擎時是徹底不被支持的,這裏使用了表一級的單一讀寫鎖(雖然在某些狀況下,插入操做是能夠繞過鎖的。)
Oracle中的事務隔離等級
Oracle只支持三種事務隔離級別,即提交讀、可序列化和只讀。在Oracle中,提交讀是默認的,它使用快照語義。相似於PostgreSQL,Oracle並不提供未提交讀,永不容許髒讀。
可重複讀並不在Oracle的支持列表中。若是你須要在Oracle中具備該行爲,你的事務隔離級別須要被設置爲可序列化。只讀是Oracle所獨有的事務隔離級別。可是對此並無很好的文檔,手冊中只有以下描述:
只讀事務只能看見那些在事務開始階段就被提交的改變,不容許INSERT、UPDATE和DELETE語言。對其它兩種事務隔離級別的更多信息,參見Oracle官方文檔第13章「數據併發和一致性」。http://docs.oracle.com/cd/B14117_01/server.101/b10743/consist.htm#i17856
DB2中的事務隔離級別
DB2具備四種隔離級別,分別稱爲可重複讀、讀穩定性、遊標穩定性和未提交讀。這四種級別並不與上述四種ANSI術語一一對應。
可重複讀對應於ANSI SQL中的可序列化,意味着不可能存在髒讀。讀穩定性對應於ANSI SQL中的可重複讀。遊標穩定性用於提交讀,是DB2的默認設置配置。對於9.7版快照語義生效。而在9.7的前期版本中,DB2使用相似於SQL Server的鎖機制。
未提交讀在很大程度上相似於SQL Server中的未提交讀,也容許髒讀。手冊中推薦僅在只讀表上使用未提交讀,或是用在「能夠看到未被其它應用提交的數據時」。
MongoDB中的事務隔離級別
正如前文所提到的,MongoDB不支持事務。在其手冊中對此是這樣描述的:
由於在MongoDB中對單一文檔的操做是原子的,兩階段提交只能提供類事務語義。在兩階段提交或回滾期間,應用可在中間點返回中間數據。事實上這意味着MongoDB使用髒讀語義,具備雙倍或丟失記錄的可能性。
CouchDB中的事務隔離等級
CouchDB也不支持事務。可是不一樣於MongoDB的是,它使用了多版本併發控制去避免髒讀。讀取請求將老是在請求開始時就能看到數據庫的最新快照。這所給予CouchDB的事務隔離等級,等價於具備快照語義的提交讀。
Couchbase Server的事務隔離級別
Couchbase Server常被混淆爲CouchDB,但它是一種徹底不一樣的產品。就索引而言,它並未提供任何形式的隔離。當執行更新操做時,Couchbase Server僅更新主索引,或稱其爲「真實的表」。全部的二級索引將被延遲更新。雖然在Couchbase Server文檔並無明確說明,看上去它在構建索引時使用了快照,若是確是如此,髒讀應該不成爲問題。可是因爲索引的延遲更新,在Couchbase Server中仍不能得到真正的提交讀事務隔離級別。
和許多的NoSQL數據庫同樣,Couchbase Server並不直接支持事務。可是你確實可使用顯式鎖,但鎖只能在被自動丟棄前維持30秒的時間。
Cassandra中的事務隔離級別
Cassandra 1.0隔離了甚至是對一行的寫入操做。由於字段是被逐一更新的,因此能夠終止對舊值和新值混合在一塊兒的記錄的讀取。
從1.1版本開始,Cassandra提供了「行級隔離」。這讓Cassandra具備等同於其它的數據庫中被稱爲「未提交讀」的隔離級別。Cassandra並未提供更高級別的隔離。
瞭解你的數據庫的事務隔離級別
正如從上述實例中可看到的,僅從ACID和非ACID角度考慮你的數據庫是不夠的。你的確須要去知道你的數據庫應在何種狀況下支持何種的事務隔離級別。