事務是資料庫進行併發控制非常重要的機制。

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、事務隔離級別

  隔離級別是用來限制一個事務中正在讀取或被修改的資料免於被其他事務修改的程度。

  理論上每個事務和其他的事務都應該完全隔離開來。然而出於效能和可行性的原因,實踐中幾乎不可能做到的。在併發環境下如果沒有鎖和隔離級別,可能會發生以下四種情況:

  髒讀:在這種情況下,一個事務能夠讀取另一個事務正在修改且未提交的資料,那麼另一個事務如果發生回滾操作,將導致第一個事務讀取到的資料和實際的資料不一致;

  丟失更新:這種情況下,事務沒有隔離。多個事務能夠讀取同一份資料並且修改它。最後對資料集做出修改的事務將勝出,而其他的事務所做的修改都失效;

  不可重複讀:兩個事務讀取資料,但是在第二個事務讀取前,另一個事務修改了該資料,因此兩次讀取的資料不一致;

  幻讀:這種情況和不可重複讀類似,不同的是,兩個事務讀取一個範圍的資料,但是在第二個事務讀取之前,另一個事務新增了一條資料,導致兩次讀取的結果不同。

  在瞭解了併發情況下出現的上述問題後,就可以進一步理解隔離級別的概念,通俗一點講就是:你希望以何種方式將併發的事務隔離開來, 隔離到什麼程度?比如允許髒讀,等。隔離級別越高,讀取髒資料或者造成資料不一致的情況就越少,但是在高併發系統中的效能降低就越嚴重。

檢視事物隔離級別:

    dbcc useroptions

Sql Server 2008支援6種隔離級別:

  • 未提交讀(Read Uncommited)
  • 已提交讀(Read Commited)(default
  • 可重複讀(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(default)   髒讀  丟失更新、不可重複讀、幻讀  這是Sql Server的預設隔離級別,一個事務不允許讀取另一個事務未提交的資料
REPEATABLE READ 不可重複讀 幻讀、死鎖   一個事務讀取的資料在未結束之前,不能被其他事務修改
SERIALIZABLE 幻讀    資料可用性降低、死鎖 這是最高級別的隔離,它會鎖定一個範圍的資料,從而阻止其他事務修改或新增這個範圍的資料 
SNAPSHOT 上述所有併發問題    事務訪問的是虛擬快照,其他事務Commited的資料對當前事務仍然不可見,也不允許Update被其他事務Update的資料 快照事務隔離級別預設是不啟用的,是基於行版本的事務控制
 READ COMMITTED SNAPSHOT    上述所有併發問題  無  它可以讀取被其他事務提交的資料,也可以修改資料