事務是資料庫進行併發控制非常重要的機制,通過本文來系統的瞭解一下事務到底是怎麼回事。 1、什麼是事務? 事務是作為單個邏輯工作單元執行的一系列操作,它由一條或者一組語句組成,它們麽全部成功,要麼全部失敗。 舉個例子,比如在12306訂火車票,要麼你訂票成功,餘票顯示就減少一張;要麼你訂票失敗,餘票顯 ...
事務是資料庫進行併發控制非常重要的機制,通過本文來系統的瞭解一下事務到底是怎麼回事。
1、什麼是事務?
事務是作為單個邏輯工作單元執行的一系列操作,它由一條或者一組語句組成,它們麽全部成功,要麼全部失敗。
舉個例子,比如在12306訂火車票,要麼你訂票成功,餘票顯示就減少一張;要麼你訂票失敗,餘票顯示還是那麼多。不允許出現你訂票成功了,餘票卻沒有減少的情況。那麼這種購票和餘票減少的兩個不同的操作必須放在一起,成為一個完成的邏輯,這樣就構成了一個事務。
2、事務有哪些特性?
原子性(Atomicity): 原子性是指一個是事務中包含的一條語句或者多條語句構成了一個完整的工作單元,那麼這個工作單元要麼一起提交執行全部成功,要麼執行全部失敗;
一致性(Consistency):可以理解為數據的完整性,它用來保證數據從一個一致性的狀態變成另外一個一致性的狀態,比如賬戶A給賬戶B轉賬100元,那麼賬戶A應該減少100元,賬戶B增加100元,但是兩人錢數總和還是沒變的;
隔離性(Isolation):一個事務的所做的修改不能被其他的事務影響,讀取到的數據狀態要麼是事務開始前的,要麼是事務結束後的;
持久性(Durability):事務一旦對數據的操作完成後,數據修改就已經完成了。
3、事務的分類?
事務有三種常見的類型:
自動提交事務:sql server 的預設事務類型,每條單獨的SQL語句都是單獨的一個事務,語句執行完畢自動提交,錯誤則自動回滾;
顯示事務:顯示的聲明事務的開始(BEGIN TRANSACTION)和結束(COMMIT TRANSACTIOIN或ROLLBACK TRANSACTION);
隱式事務:使用 SET IMPLICIT_TRANSACTIONS ON語句啟動隱式事務,使用SET IMPLICIT_TRANSACTIONS OFF 關閉隱式事務,但不會像自動模式那樣自動執行ROLLBACK或COMMIT語句,隱式事務必須顯示結束(COMMIT或ROLLBACK);
4、事務隔離級別
隔離級別是用來限制一個事務中正在讀取或被修改的數據免於被其他事務修改的程度。
理論上每個事務和其他的事務都應該完全隔離開來。然而出於性能和可行性的原因,實踐中幾乎不可能做到的。在併發環境下如果沒有鎖和隔離級別,可能會發生以下四種情況:
臟讀:在這種情況下,一個事務能夠讀取另一個事務正在修改且未提交的數據,那麼另一個事務如果發生回滾操作,將導致第一個事務讀取到的數據和實際的數據不一致;
丟失更新:這種情況下,事務沒有隔離。多個事務能夠讀取同一份數據並且修改它。最後對數據集做出修改的事務將勝出,而其他的事務所做的修改都失效;
不可重覆讀:兩個事務讀取數據,但是在第二個事務讀取前,另一個事務修改了該數據,因此兩次讀取的數據不一致;
幻讀:這種情況和不可重覆讀類似,不同的是,兩個事務讀取一個範圍的數據,但是在第二個事務讀取之前,另一個事務新增了一條數據,導致兩次讀取的結果不同。
在瞭解了併發情況下出現的上述問題後,就可以進一步理解隔離級別的概念,通俗一點講就是:你希望以何種方式將併發的事務隔離開來, 隔離到什麼程度?比如允許臟讀,等。隔離級別越高,讀取臟數據或者造成數據不一致的情況就越少,但是在高併發系統中的性能降低就越嚴重。
Sql Server 2008支持6種隔離級別:
- 未提交讀(Read Uncommited)
- 已提交讀(Read Commited)
- 可重覆讀(Repeatable Read)
- 序列化(Serializable)
- 快照(Snapshot)
- 已提交讀快照(Read Commited Snapshot)
下麵我們通過代碼來演示各個事務隔離級別的表現:
未提交讀(Read Uncommited)
隔離級別最低,允許一個事務讀取其他事務修改但未提交的數據,也就是會產生“臟讀”的問題,它的作用和SELECT語句在對象表上設置(NOLOCK)相同。
打開兩個查詢視窗,下麵第一個表示事務A,第二個表示事務,事務A保持預設隔離級別,事務B設置為Read Uncommitted,先執行事務A,接著執行事務B;
語法:SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
DBCC USEROPTIONS ---查看當前設置
事務A:
事務B:
從圖中可以看出,事務B對同一條數據讀取了兩次,但很明顯第一次讀到的數據是“臟數據”
已提交讀(Read Committed)
這是Sql Server的預設隔離級別,一個事務不允許讀取另一個事務未提交的數據,Read Committed可以有效的防止“臟讀”的出現,但是能夠讀取由其他事務修改並提交的數據,也就是說,有可能會出現“不可重覆讀”和“幻讀”的問題。
接著上一個例子,我們把事務B的隔離級別設置為Read Committed,來看一下執行情況,還是先執行事務A,再接著執行事務 B;
事務A:
事務B:
在當前隔離級別下,當事務B讀取數據時,事務B必須等待事務A結束以後才能讀取,但是也有可能會出現其他問題,請看例子。先執行事務A,接著執行事務B。
從上圖中可以看的出來,事務A讀第一次查詢後,事務B對數據進行了修改,導致事務A兩次查詢不一致,因此出現了“不可重覆讀”的情況,那麼怎麼避免呢,接著往下看
可重覆讀(Repeatable Read)
一個事務讀取的數據在未結束之前,不能被其他事務修改,這個隔離級別可以解決“不可重覆讀”的問題
通過結果我們可以看出事務A兩次讀取的數據時一致的 ,那麼如果把事務B的修改換成插入呢,修改上面的示例:
通過運行結果我們發現,事務A兩次讀取的結果集不一樣了,這就是幻讀。那麼我們繼續看下一個隔離級別
序列化(SERIALIZABLE)
這是最高級別的隔離,它會鎖定一個範圍的數據,從而阻止其他事務修改或新增這個範圍的數據,修改上面的例子,依然是先執行事務A,在執行事務B
從執行結果可以看出,事務A兩次讀取數據的結果是一致的,事務B明顯是等待事務A結束後才執行完成的,雖然序列化隔離級別更高,也可以很好的避免併發產生的問題,但是同時也降低了數據的可用性。
另外兩個隔離級別是基於行版本的隔離級別
快照(SNAPSHOT)
通過在事務開始前在tempdb中創建一份資料庫的虛擬快照,此後它只允許事務訪問該資料庫的虛擬快照,但是如果當前快照事務修改已經由其他事務修改的數據,將導致錯誤並終止,預設情況下SNAPSHOT隔離級別在資料庫中是不啟用的,那麼需要使用ALTER DATABASE test SET ALLOW_SNAPSHOT_ISOLATION ON啟用
只有當資料庫中啟用SNAPSHOT事務隔離級別的開關打開後,才能使用它。打開此開關將告知資料庫去設置版本化環境。理解這一點很重要,因為,一旦版本化開啟,資料庫會有維護版本化的開銷,無論是否有事務正在使用SNAPSHOT事務隔離級別。
提交讀快照(READ COMMITTED SNAPSHOT)
提交讀快照和快照隔離級別是類似的,不同的是:
- 通過ALTER DATABASE Test SET READ_COMMITTED_SNAPSHOT ON 來開啟資料庫提交讀快照,運行這句話的時候不能有其他連接到當前DB;
- 已提交讀啟用後,READ_COMMITTED事務會通過使用行版本提供數據讀取的一致性;
- 在其他事務修改提交後可以讀取到修改的數據,而快照事務在當前事務未重新開始前讀取的都是快照數據;
- 能夠更新由其他事務修改的數據,而快照事務不能;
針對這兩種隔離級別的測試,有興趣的話可以自己試一下。
五、總結
隔離級別 | 解決併發問題 | 存在的併發問題 | 說明 |
READ UNCOMMITED | 不適用於併發場合 | 臟讀、不可重覆讀、幻讀 | 隔離級別最低,允許一個事務讀取其他事務修改但未提交的數據 |
READ COMMITED | 臟讀 | 丟失更新、不可重覆讀、幻讀 | 這是Sql Server的預設隔離級別,一個事務不允許讀取另一個事務未提交的數據 |
REPEATABLE READ | 不可重覆讀 | 幻讀、死鎖 | 一個事務讀取的數據在未結束之前,不能被其他事務修改 |
SERIALIZABLE | 幻讀 | 數據可用性降低、死鎖 | 這是最高級別的隔離,它會鎖定一個範圍的數據,從而阻止其他事務修改或新增這個範圍的數據 |
SNAPSHOT | 上述所有併發問題 | 事務訪問的是虛擬快照,其他事務Commited的數據對當前事務仍然不可見,也不允許Update被其他事務Update的數據 | 快照事務隔離級別預設是不啟用的,是基於行版本的事務控制 |
READ COMMITTED SNAPSHOT | 上述所有併發問題 | 無 | 它可以讀取被其他事務提交的數據,也可以修改數據 |