1 概述數據庫
本篇文章簡要對事物與鎖的分析比較詳細,所以就轉載了。安全
2 具體內容併發
併發能夠定義爲多個進程同時訪問或修改共享數據的能力。處於活動狀態而互不干涉的併發用戶進程的數量越多,數據庫系統的併發性就越好。當一個正在修改數據的進程阻止了其餘進程讀取該數據,或者當一個正在讀取數據的進程阻止了其餘進程修改該數據,併發性就下降了。本文用術語「讀取」或者「訪問」描述數據上的SELECT操做,用「寫入」或「修改」描述數據上的INSERT,UPDATE以及DELETE操做。高併發
通常地,數據庫系統能夠採用兩種方式來管理併發數據訪問,樂觀併發控制和悲觀併發控制。性能
對於任何一種併發控制模式,若是兩個事務試圖同一時刻修改數據的話都會產生衝突。這兩種模式之間的區別在於,是在衝突發生前進行防止,仍是發生後採起某種方法來處理衝突。學習
對於樂觀併發控制,該模型假定系統中存在很是少的相互衝突的數據修改操做,以至任何單獨的事務都不太可能修改其它事務正在修改的數據。樂觀併發控制默認採用行版本控制來處理併發。spa
例如,在讀取數據時咱們會獲得一個數據的版本version 1,當須要修改數據時,咱們先檢查數據的版本是否是version 1,若是是就修改數據;若是不是,就說明在當前事務的讀操做和寫操做之間已經有別的事務對數據進行了修改(每次修改操做都會使得數據的版本+1),SQL Server將會產生一個錯誤消息,由上層應用程序響應此錯誤。設計
原子性(Atomicity)
SQL Server保證事務的原子性。原子性指的是每一個事務要麼所有執行,要麼什麼都不執行。也就是說,若是一個事務提交了,它形成的全部效果都會被保留。若是停止了,其全部效果都會被撤銷。3d
一致性(Consistency)
一致性屬性確保事務不容許系統到達一個不許確的邏輯狀態——數據必須老是保持邏輯上的正確。即便在發生系統故障時,約束和規則必須獲得保證。(一致性通常被原子性、隔離性以及持久性所涵蓋,而且概念上會產生重複)版本控制
隔離性(Isolation)
隔離性會將併發事務與其餘併發事務的更新操做分隔開。當該事務正在執行時,其餘事務是沒法看到進行中的任務的。SQL Server會在事務之間自動實現隔離。它採用鎖定數據或者行版本使得多個併發事務可以併發操做數據,以防止致使不正確結果。
隔離性意味着事務必須在不干擾其餘事務的前提下獨立執行。換言之,在事務執行完畢以前,其所訪問的數據不能受系統其餘部分的影響。
持久性(Durability)
當事務提交以後,SQL Server的持久性屬性就會確保該事務的做用持續存在(即便發生系統故障)。若是在事務進行過程當中發生系統故障,事務就會被徹底撤銷,不會在數據上遺留部分做用。若是在事務的提交確認被髮送到調用的程序以後馬上發生故障,數據庫會確保該事務的存在。預寫式日誌以及SQL Server啓動恢復階段的事務自動回滾/自動重作機制可以確保持久性。
時間 | 取款事務A | 取款事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢帳戶餘額爲1000 | |
T4 | 查詢帳戶餘額爲1000 | |
T5 | 取出100,存款餘額爲900 | |
T6 | 取出300,存款餘額爲700 | |
T7 | 提交事務 | |
T8 | 提交事務 |
最終帳戶餘額爲900,取款事務A的更新丟失了。丟失更新是這些行爲中惟一一個用戶可能在全部狀況下都想避免的行爲。
時間 | 查詢事務A | 取款事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢帳戶餘額爲1000 | |
T4 | 取出100,存款餘額爲900 | |
T5 | 查詢帳戶餘額爲900 | |
T6 | 撤銷事務,恢復爲1000 | |
T7 | 提交事務 |
查詢事務A讀取到取款事務B還未提交的餘額900。
默認狀況下,髒讀是不容許的。謹記:更新數據的事務是沒法控制別的事務在它提交以前讀取其數據的,這是由讀取數據的事務來決定是否想要讀取未必會被提交的數據。
時間 | 查詢事務A | 取款事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢帳戶餘額爲1000 | |
T4 | 取出100,存款餘額爲900 | |
T5 | 查詢帳戶餘額爲900 | |
T6 | 提交事務 | |
T7 | 提交事務 |
查詢事務A兩次讀取餘額獲取到不一樣結果。
時間 | 取款記錄處理事務A | 取款事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢到5條取款記錄 | |
T4 | 查詢餘額爲1000元 | |
T5 | 取出100,存款餘額爲900 | |
T6 | 查詢到6條取款記錄 | |
T7 | 提交事務 | |
T8 | 提交事務 |
對於取款記錄處理事務A,兩次查詢的結果集不一樣。
SQL Server支持五種隔離級別來控制讀操做的行爲。其中三個只在悲觀併發模型中可用,一個只在樂觀併發模型中可用。剩下的一個在兩個模式下都是可用的。
除了丟失更新之外,上面提到的其餘行爲均可能發生。未提交讀是經過使讀操做不佔用任何鎖來實現的,當前事務可以讀取其餘事務已經修改過可是還沒有提交的數據。
當採用未提交讀時,用戶是放棄了對高一致性數據的把握而趨向於支持系統的高併發能力,使用戶不會再互相鎖定對方。那麼,什麼時候才應該選擇未提交讀呢?顯然,每筆數據都須保證平衡的金融交易是不適合的。而對於某些決策支持分析來講可能會很適合(譬如,須要察看銷售走勢時),由於徹底沒有必要作到徹底精確並且會帶來併發性能的提高,所以是至關值得的。
已提交讀是數據庫引擎的默認級別。SQL Server 2005支持兩種已提交讀的隔離級別,這種隔離級別既能夠是樂觀的也能夠是悲觀的,默認採用悲觀併發控制。爲了區分,悲觀實現稱「已提交讀(鎖定)」,樂觀實現稱爲」已提交讀(快照)」。
已提交讀隔離級別保證了一個操做不會讀到別的程序已經修改可是還沒有提交的數據。若是別的事務正在更新數據並所以在數據行上持有排它鎖,當前的事務就必須等待這些鎖釋放後才能使用這個數據(不管是讀取仍是修改)。一樣地,事務必須至少在要被訪問的數據上加上共享鎖,其餘事務能夠讀取數據可是不能修改數據。默認,共享鎖在數據讀取事後就被釋放掉,而無需在事務的持續時間內保留。
已提交讀(快照),也能保證一個操做不會讀到未提交數據,但不是經過迫使其餘進程等待的方式。對於已提交讀(快照),每當一行數據被修改後,SQL Server就會生成該行數據前一次已提交值的一個版本(version),被修改的數據仍舊被鎖定着,可是其餘進程能夠看到該數據在更新操做開始以前的版本。
可重複讀是一種悲觀的隔離級別。它在已提交讀的基礎上增長了新的屬性:確保當事務從新訪問數據或查詢被再一次執行時,數據將再也不發生改變。換句話說,在一個事務中執行相同的查詢兩次是不會看到由其餘事務所形成的任何數據的改變的。然而,可重複讀隔離級別仍是容許幻讀的出現。
在某些狀況下,防止不可重複讀是用戶嚮往的一種安全措施。可是世上沒有免費的午飯,這種額外的措施所帶來的開銷是事務中全部的共享鎖必須保留到事務完成爲止。
排它鎖必須老是保留到事務結束爲止,不管採用何種隔離級別或者併發模型,這樣事務才能在須要時被回滾。若是鎖提早釋放了,就不太可能完成撤銷操做,由於其餘併發事務可能已經使用了同一數據,而且修改了它的值。
只要事務是打開的,沒有其餘用戶能夠修改被該事務所訪問的數據。顯然這會嚴重下降併發性和性能。所以,若是事務不保持簡短或者編寫應用程序時沒有可以注意到這樣潛在的鎖競爭問題,將會致使大量的事務由於等待鎖釋放而掛起。
快照隔離是一種樂觀隔離級別,相似於已提交讀(快照),若是當前版本被鎖定住時,它容許其餘事務讀取已提交數據的早期版本。快照隔離和已提交讀(快照)的區別與(早期版本該有多早、保留多少個早期版本)這個問題相關,咱們在行版本控制小節中詳述。儘管快照隔離所避免的行爲和可串行化所避免的是相同的,可是快照隔離並非真正意義上的可串行化隔離級別。對於快照隔離,可能會有兩個個事務同時執行,並引發一個任何序列化執行都不可能產生的結果。
可串行化也是一種悲觀隔離級別。可串行化隔離級別在可重複讀的基礎上增長了新的屬性:確保在從新執行查詢時,SQL Server不會在中間的過渡期增長新的行。換句話說,若是同一事物在相同的查詢被執行兩次的話,幻讀不會出現。可串行化也所以成爲最健壯的悲觀隔離級別,由於防止了以前所描述的全部可能的「不一致問題「。
額外的安全措施一定會帶來額外的開銷。可串行化隔離級別下,事務中的全部共享鎖都必須保留到事務完成爲止。另外,執行可串行化隔離級別不只須要鎖定已讀數據,還須要鎖定那些不存在的數據,參看後面的鍵範圍鎖。
SQL Server可使用幾種不一樣方式來鎖定數據,舉例來講,讀操做獲取共享鎖而寫操做獲取排他鎖。更新鎖在更新操做的開頭部分獲取。SQL Server會自動獲取並釋放全部這些類型的鎖。它還負責管理鎖定模式之間的兼容性,解決死鎖問題,並在須要的時候進行鎖升級。它在表、表的分頁、索引鍵以及單獨的數據行上支配鎖。
更新鎖自己不足以使用戶可以修改數據——全部的數據修改都要求被修改的數據資源上存在一個排它鎖。只要有一個事務對資源持有更新鎖,其它事務就沒法獲取該資源的更新鎖或者排他鎖了。持有更新鎖的事務可以將其轉換成該資源上的排它鎖,由於更新鎖避免了與其餘進程之間的鎖的不兼容。能夠將更新鎖看做是「意圖更新鎖」,這纔是它實際所扮演的角色。更新鎖會保留到事務結束或者當它轉換成排他鎖。
不要被鎖的名字誤導,更新鎖並不僅是針對更新操做而設計的。SQL Server使用更新鎖適用於任何須要進行實際修改以前搜索數據的數據修改操做。這樣的操做包括受限更新及刪除,也包括在帶有彙集索引的表上進行的插入操做。對於後面一種狀況,SQL Server必須先搜索數據(使用匯集索引)以找到正確的位置來插入新的記錄。當SQL Server只進行到搜索階段時,它會採用更新鎖來保護數據,而只有當它找到正確的位置並開始插入之後纔將更新鎖升級爲排他鎖。
SQL Server支持兩種類型的鍵鎖,而它採用哪一種類型則取決於當前事務的隔離級別。若是隔離級別是已經提交讀、可重複讀或者快照,SQL Server會在處理查詢時嘗試鎖定實際被訪問的索引鍵。對於彙集索引的表而言,數據行就是索引的葉級別,而用戶能夠看到所獲取的鍵鎖。若是表是堆結構的話,用戶可能會看到非彙集索引上的鍵鎖以及實際數據上的行鎖。
若是隔離級別是可串行化,狀況就有所不一樣了。爲了防止幻讀,若是一個事務中掃描了一個範圍內的數據就須要充分鎖定住該表以確保沒人可以插入新值到已掃描的範圍內。在SQL Server早期版本中是經過鎖定整個分頁甚至整張表來保證這一點的。在許多狀況下,這可能致使了更大範圍的數據被鎖定住了,形成了沒必要要的資源競爭。SQL Server 2005採用了一種稱爲「鍵範圍鎖」的單獨鎖模式,與索引中的特定鍵值相關聯並代表在索引中這兩個鍵之間的全部值被鎖定住了。
鎖簡稱
簡單兼容性矩陣
完整兼容性矩陣
樂觀併發控制採用了一種稱爲行版本控制的新技術來保障事務。在使用樂觀鎖併發控制時會獲取排他鎖。樂觀併發和悲觀併發的區別在於樂觀併發中寫操做與讀操做之間不會互相阻塞。換句話說就是,當被請求資源當前擁有共享鎖時,申請排它鎖的事務不會被阻塞,相反,當被請求資源當前擁有排他鎖時,申請共享鎖的進程也不會被阻塞。
一旦啓用樂觀並反控制,SQL Server就使用tempdb數據庫來存儲全部已經修改過的記錄的副本,而且只要存在來自任意事務的訪問需求,就會繼續維持這些副本。當tempdb用來存儲被修改記錄的早期版本時,就其稱爲版本存儲區。
SQL Server引入了一種新的隔離級別:快照隔離以及一種新式的無阻塞風格的已提交讀隔離——已提交讀(快照)。這些基於版本控制的隔離級別容許讀者獲取行的一個先前已提交過的值而不會產生阻塞,這樣就提升了系統的併發能力。爲了使它起做用,SQL Server必須在行被修改或刪除時保留舊版本的記錄。若是在同一行上進行屢次更新,SQL Server就可能須要維護該行的多個早起版本。鑑於此,行版本控制有時也被稱爲多版併發控制。
當表或索引中的一行數據被更新時,SQL Server會用執行更新的那個事務的事務序列號來標記新的行。事務序列號是一個單調遞增的數字,在每一個SQL Server的實例中保證惟一。在更新一行數據時,以前的版本存放在版本存儲區內,而新的行包含一個指向版本存儲區中舊的行數據的指針。版本存儲區裏舊的行數據可能包含了指向更早版本的指針。一條行記錄的全部版本串接成一個鏈表。SQL Server可能須要沿着鏈表中的幾個指針才能到達一個正確的版本,只要有操做須要引用它們,行的版本就必須在版本存儲區內保存。
在應用程序使用默認的悲觀模型形成的併發性降低而不能使人滿意時,SQL Server能夠改用樂觀併發控制模型。在切換到基於樂觀版本控制的隔離級別以前,用戶必須仔細權衡使用新型併發模型的效果。處理須要額外的管理來爲版本存儲區監控tempdb之外,鑑於維護舊版本鎖帶來的額外工做量,版本控制還會下降更新操做的性能。即便當前沒有人在讀取數據,更新操做也得爲此買單。若是有使用行版本控制的讀操做,它們必須花費額外的開銷來遍歷鏈表指針,以找到須要的行數據的合適版本。
另外,因爲快照隔離的樂觀併發模型假定系統不會發生不少的更新衝突,若是用戶預見到在同一數據上的併發更新會產生競爭,就不該該選擇快照隔離級別。快照隔離級別可以使讀者不被寫者阻塞,可是併發的寫者仍然不被容許。在默認的悲觀模型中,第一個寫者會阻塞全部的後續寫者,但若是採用快照隔離,後續寫者實際上會接受到錯誤消息且應用程序須要從新提交初始請求。
已提交讀快照隔離是一種語句級的快照隔離,也就是任何查詢都能看到在語句開始那一刻最近提交過的數值。假設在啓用了RCSI的數據庫上有以下兩個事務,且在事務開始運行以前Product 922的ListPrice值是8.89
注意當時間爲2時,事務1所做出的修改還沒有提交,所以Product ID=922的行上仍然持有鎖。可是事務2不會被這個鎖阻塞住,它可以訪問該行數據上一次已提交的ListPrice值8.89。這仍然屬於已提交讀隔離級別(一個無阻塞的變種),因此不能防止「不可重複讀」。
RCSI最大的益處是能夠引入更好的併發性,由於讀者與寫者之間不會相互阻塞。可是寫者之間仍是會發生阻塞,所以標準的加鎖機制適用於所有的更新、刪除和插入操做。
衝突發生是由於事務2在Quantity值爲324的時候開始,當這個值被事務1更新後,行版本324被存儲到版本存儲區內。事務2會在事務的持續時間內繼續讀取該行數據。若是兩個更新操做都被容許成功執行的話,就會產生經典的更新丟失情形。事務1增長了200個數量,而後事務2會在初始值上增長300個數量並存儲。由第一個事務增長的那200個產品就會完全丟失,SQL Server不會容許這樣的狀況發生。
當事務2開始嘗試執行更新時,並不會馬上獲得一個錯誤——僅僅是被阻塞。事務1在行上擁有一個排他鎖,所以事務2嘗試獲取排他鎖時會被阻塞。若是事務1回滾,那麼事務2就可以完成更新。但事務1最終被提交了,SQL Server檢測到一個衝突併產生錯誤。
衝突只可能發生在SI模式下,由於SI隔離級別是基於事務而不是基於語句的。若是上述例子在一個採用RCSI的數據庫中執行,事務2執行的更新語句不會使用該數據的原來值。當試圖讀取當前的Quantity值時,它會被阻塞住,而接着事務1完成時,它就能讀取更新過的Quantity將其做爲當前值並再增長300,沒有一個更新會丟失。
若是用戶選擇工做在SI模式下就須要注意可能發生的衝突,它們可以被減小到最低限度,可是如同死鎖同樣,用戶不能保證不發生衝突。用戶必須寫程序來合理地處理衝突,而且不能想固然地認爲更新已經成功了。若是衝突只是偶然發生,用戶可能須要將其做爲使用SI模式的部分代價考慮在內,但若是衝突太過頻繁,就須要額外措施來避免衝突。
3 參考文獻
【01】http://blog.jobbole.com/104445/
4 版權