一、什么是事務
??數據庫中的事務(Transaction)指的是數據庫中的一種執行數據庫操作的一種機制,事務把一組數據操作作為一個整體一起向系統提交或撤銷操作請求,即這一組操作要么全部成功,要么全部失敗,不存在部分成功部分失敗的情況,所有的額操作共進退,因此事務是一個不可分割的邏輯單元。
二、事務的特性(ACID)
??事務有ACID四個特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。
??原子性:指事務是一個不可分割的最小工作單位,事務中的操作只有都發生和都不發生兩種情況。事務中的所有操作必須作為一個整體提交或回滾。如果事務中的任何操作失敗,則整個事務將失敗。
??一致性:事務必須使數據庫從一個一致狀態變換到另外一個一致狀態。也就是說,在事務開始之前,數據庫中存儲的數據處于一致狀態。在正在進行的事務中,數據可能處于不一致的狀態,如數據可能有部分被修改。然而,當事務成功完成時,數據必須再次回到另一個確定的一致狀態。
??隔離性:一個事務的操作對其他事務不可見,即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。事務之間必須是相互隔離的,它不以任何方式依賴于或影響其他事務。
??持久性:一個事務一旦提交成功,它對數據庫所作的改變是永久性的,即使系統出現故障也是如此。一旦事務被提交,事務對數據所做的任何變動都會被永久地保留在數據庫中。
三、如何實現事務
??MySQL 提供了多種存儲引擎來支持事務,支持事務的存儲引擎有 InnoDB 和 NDB,MyISAM 存儲引擎是不支持事務。其中,InnoDB 存儲引擎事務主要通過 UNDO 日志和 REDO 日志實現。本文討論的事務默認都是基于InnoDB存儲引擎的。在開啟Auto-commit
(默認開啟)的情況下,每條 SQL 語句就是一個事務,即SQL 語句執行后自動提交。為了達到將多個操作作為一個事務的目的,需要使用 BEGIN
或START TRANSACTION
顯示地開啟一個事務,或者關閉當前會話的自動提交。最后使用COMMIT
來顯式地提交事務。如果需要撤銷當前事務已經執行的操作所有操作,并回到事務執行前的狀態,可以是用 ROLLBACK
來進行回滾。ROLLBACK
表示撤銷當前事務,即在事務運行的過程中發生了某種故障,事務不能繼續執行,系統將事務中對數據庫的所有已完成的操作全部撤銷,回滾到事務開始時的狀態。
??MySQL中事務的實現流程大體如下,從BEGIN
或START TRANSACTION
開始,然后執行一系列操作,最后要執行COMMIT
操作來提交事務,事務才算結束。當然,如果需要進行回滾操作,通過執行ROLLBACK
事務也會結束。
四、事務的隔離級別
??在介紹事務隔離級別之前,先展示一下在事務并發執行的過程下,可能會遇到的一些問題和場景:
??臟讀(Dirty Read):事務A讀取并使用了另一個事務B修改后尚未提交的數據D, 由于事務B對該數據的修改并未最終提交到數據庫,當事務B回滾時,事務A讀取到的數據D就是“臟數據”,事務A的這種行為稱之為“臟讀”,“臟讀”會導致事務A后續對數據D的所有操作都會產生無法預期的結果。
??修改丟失(Lost of Modify):當事務A讀取一個數據D時,另外一個事務B也訪問了該數據D,且在事務A成功提交并修改了這個數據D為D1之后,事務B也成功提交并修改了這個數據D為D2。這樣事務A對數據D的修改結果就丟失了,這種情況就被稱為“修改丟失”。
??不可重復讀(Unrepeatable Read):事務A需要多次讀取同一數據D,在這個事務還沒結束時,事務B也訪問了這個數據D,并講這個數據D修改成了D1,此時可能會造成事務A多次讀取的數據,這種情況就被稱為不可重復讀。
??幻讀(Phantom Read):事務A需要多次讀取多行數據D,假設某個階段讀取了N行數據D,此時事務A還沒結束,接著事務B插入了n行新數據D,這樣事務A在隨后的查詢中,會讀取到N+n行的數據D,就好像發生了幻覺一樣,因此這種現象也稱之為“幻讀”。“幻讀”與“不可重復度”類似,“幻讀”側重于數據行數的變化,而“不可重復讀”側重于對同一條數據的內容修改。
??事務隔離級別就是用來解決上述可能發生的問題和場景的,MySQL支持全部SQL標準定義的四種隔離級別,分別是:
??讀取未提交(READ-UNCOMMITTED):最低的隔離級別,允許一個事務去讀取另一個事務尚未提交的數據變更,可能造成臟讀、修改丟失、不可重復讀、幻讀。
??讀取已提交(READ-COMMITTED):只允許讀取并發事務已經提交的數據,不允許讀取另一個事務尚未提交的數據變更,可以避免臟讀和修改丟失,但是可能造成不可重復、幻讀。
??可重復讀(REPEATABLE-READ):在事務中,對同一字段多次讀取的結果都是一致的,除非本身事務修改,可以避免臟讀、修改丟失和不可重復讀,但是無法避免幻讀。至于幻讀,MySQL的InnoDB采用了間隙鎖(Next-Key鎖)的方式來解決了幻讀問題。
??串行化(SERIALIZABLE):最高的隔離級別,完全服從ACID的隔離級別,所以的事務依次執行,可以避免臟讀、修改丟失、不可重復讀、幻讀。但是串行化的執行效率最低,完全沒有并發可言。
五、事務隔離的實現
??MySQL中不同事務隔離級別的實現都是依靠不同機制的鎖來實現的,其實隔離說白了就是枷鎖,不然咋隔離。下面針對針對MySQL提供的四種事務的隔離機制,簡單介紹一下內部實現原理。
??讀取未提交(READ-UNCOMMITTED):“讀取未提交”是MySQL中安全級別最低的事務隔離級別,基本上就等于沒隔離,事務中任何對數據的修改都會第一時間暴露給其他事務,即使當前事務還沒有提交甚至可能在后續的操作中被回滾,但是他不管,沒一步對數據的改變,都能被其他事務所獲取。因為不加鎖的原因,所以數據安全無法保證,但是因為沒有了加鎖和釋放鎖的所帶來的額外開銷,因此“讀取未提交”的并發性能是最高。
??讀取已提交(READ-COMMITTED):既然“讀取未提交”會導致臟數據,那么如果每個事務只允許讀取其他事務已經提交的數據,那么臟數據的問題就可以迎刃而解。“讀取已提交”的實現原理是這樣的:每個事務在操作某條記錄之前開始獲取鎖,如果當前有其他事務已經獲取了該條記錄的鎖,那么該事務獲取鎖失敗,就等待另外是個事務釋放鎖。當持有該記錄鎖的事務提交完成之后,鎖釋放,此時新的事務才能獲取當前記錄的鎖,并對當前數據進行操作。在這種隔離級別下,由于事務的中間狀態不被其他事務所感知,所以可以很好的解決臟讀和修改丟失的問題。
??可重復讀(REPEATABLE-READ):“可重復度”是MySQL默認的事務隔離級別,“可重復讀”是針對“不可重復讀”而言的,前面我們說到過,“不可重復讀”是指同一事務在不同時刻讀到的某挑記錄的內容可能不一致。而“可重復讀”指的是:事務不會讀到其他事務對已有數據的修改,即使其他事務已提交,也就是說,事務開始時讀到的已有數據是什么,在事務提交前的任意時刻,這些數據的值都是一樣的。但是,對于其他事務新插入的數據是可以讀到的,這也就引發了幻讀問題。
??為了實現可重復讀,MySQL 采用了MVCC(Multi-Version Concurrency Control,多版本并發控制)的方式,MVCC 在 MySQL InnoDB 中的實現主要是為了提高數據庫并發性能,用更好的方式去處理讀寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞并發讀。
??串行化(SERIALIZABLE):串行化是四種種事務隔離級別中隔離要求最嚴格的,它解決了臟讀、可重復讀、幻讀的問題,但是并發效果是最差的,因為它將多個并發事務的執行變為順序執行。讀的時候加共享鎖,其他事務可以并發讀但是不能寫。寫的時候加排它鎖,其他事務既不能并發讀也不能并發寫。