Categories
程式開發

MySQL-技術專題-事務和並發一致性問題


什麼是事務

事務指的是滿足ACID 特性的一組操作,通過Commit 提交一個事務,也使用Rollback 進行回滾。

事務是並發控制的單位,一系列操作組成的工作單元,該工作單元內的操作是不可分割的,也就是事務具有原子性,一個事務中的一系列的操作要么全部成功,要么一個都不做,所有操作必須成功完成,否則在每個操作中所作的所有更改都會被撤消。

事務的結束有兩種,當事務中的所有步驟全部成功執行時,事務提交。如果其中一個步驟失敗,將發生回滾操作,撤消撤消之前到事務開始時的所以操作。

事務的ACID

1. 原子性(Atomicity)

事務被視為不可分割的最小單元,事務的所有操作要么全部提交成功,要么全部失敗回滾。

回滾可以用回滾日誌來實現,回滾日誌記錄著事務所執行的修改操作,在回滾時反向執行這些修改操作即可。

2. 一致性(Consistency)

數據庫在事務執行前後都保持一致性狀態。在一致性狀態下,所有事務對一個數據的讀取結果都是相同的。

3. 隔離性(Isolation)

一個事務所做的修改在最終提交以前,對其它事務是不可見的。

4. 持久性(Durability)

一旦事務提交,則其所做的修改將會永遠保存到數據庫中。即使系統發生崩潰,事務執行的結果也不能丟失。使用重做日誌來保證持久性。

事務的ACID 特性概念簡單,但不是很好理解,主要是因為這幾個特性不是一種平級關係:

1、只有滿足一致性,事務的執行結果才是正確的。

2、在無並發的情況下,事務串行執行,隔離性一定能夠滿足。此時只要能滿足原子性,就能滿足一致性。

3、在並發的情況下,多個事務並行執行,事務不僅要滿足原子性,還需要滿足隔離性,才能滿足一致性。

4、事務滿足持久化是為了能應對數據庫崩潰的情況。

並發一致性問題

在並發環境下,事務的隔離性很難保證,因此會出現很多並發一致性問題。

T1是指事務1,T2是指事務2

丟失修改:兩個事務同時操作相同數據,後提交的事務會覆蓋先提交的事務處理結果,通過樂觀鎖就可以解決,悲觀鎖也可以。

例如:T1和T2兩個事務都對一個數據進行修改,T 1 先修改,T 2 隨後修改,T 2 的修改覆蓋了T 1 的修改。

讀臟數據:事務A讀取到了事務B已經修改但尚未提交的數據,如果事務B回滾,A讀取的數據無效,不符合一致性

例如:T1修改一個數據,T 2 隨後讀取這個數據。如果T 1 撤銷了這次修改,那麼T 2 讀取的數據是臟數據。

不可重複讀:事務A讀取到了事務B已經提交的修改數據,不符合隔離性,也不符合一致性

例如:T 2 讀取一個數據,T 1 對該數據做了修改。如果T 2 再次讀取這個數據,此時讀取的結果和第一次讀取的結果不同。

幻讀:事務A讀取到了事務B提交的新增數據,不符合隔離性,也不符合一致性

例如:T 1 讀取某個範圍的數據,T 2 在這個範圍內插入新的數據,T 1 再次讀取這個範圍的數據,此時讀取的結果和和第一次讀取的結果不同。

產生並發性一致性問題,那肯定是不符合一致性,只有滿足了一致性,事務的執行結果才是正確的。

產生並發不一致性問題主要原因是破壞了事務的隔離性,解決方法是通過並發控制來保證隔離性。並發控制可以通過封鎖來實現,但是封鎖操作需要用戶自己控制,相當複雜。

數據庫管理系統提供了事務的隔離級別,讓用戶以一種更輕鬆的方式處理並發一致性問題。

MySQL的共享鎖和排它鎖

1、共享鎖

共享鎖也叫讀鎖(Shared Lock),簡稱S鎖,原理:一個事務獲取了一個數據行的共享鎖,其他事務能獲得該行對應的共享鎖,但不能獲得排他鎖,即一個事務在讀取一個數據行的時候,其他事務也可以讀,但不能對該數據行進行增刪改。

2、排他鎖

排他鎖也叫寫鎖(Exclusive Lock),簡稱x鎖,原理:一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖(排他鎖或者共享鎖),即一個事務在讀取一個數據行的時候,其他事務不能對該數據行進行增刪改查。

隔離級別

MySQL-技術專題-事務和並發一致性問題 1

1、讀未提交(READ_UNCOMMITED)

(1)事務對當前被讀取的數據不加鎖。

(2)事務在更新某數據的瞬間(發生更新的瞬間),必須先對其加行級共享鎖,直到事務結束才釋放。

理解:

1、事務B更改了某個數據,此時加的是行級共享鎖,事務A還是可以讀取到這個更改了的數據,如果事務B回滾,事務A讀取到的數據發生變化,造成臟讀。

2、事務A讀取某個數據,事務B對其修改並提交,事務A再次讀取,得到的是不一樣的結果,造成不可重複讀

3、同理會造成幻讀,因為增加了新的一行數據,那行數據沒有添加排它鎖

2、讀已提交(READ_COMMITED)

(1)事務對當前被讀取的數據加行級共享鎖(當讀到時才加鎖),一旦讀完,立即釋放行級共享鎖。

(2)事務更新某數據的瞬間(就是發生更新的瞬間),必須對其加行級排他鎖,直到事務結束才釋放。

理解:由(2)可以知道,事務B修改某個數據的時候,對當前行添加了行級排它鎖,於是事務A直到事務結束才能訪問這個數據,如果這個數據發生了回滾,也不會造成臟讀,但如果數據沒有回滾,兩次查到的數據不一致,造成不可重複讀,也會造成幻讀,因為只是對修改的行加了排它鎖,其他行沒有添加。

3、可重複讀(REPEATABLE_READ)

(1)事務讀取某數據的瞬間(就是開始讀取的瞬間),必須先對其加行級共享鎖,事務結束才釋放。

(2)事務更新某數據的瞬間(就是發生更新的瞬間),必須先對其加行級排他鎖,事務結束才釋放。

理解:事務A讀取某個數據的時候,添加了行級共享鎖,只能讀不能改,因此不會造成修改,也就不存在臟讀和不可重複讀,但是幻讀還是可能存在的,因為只是當前行添加了排它鎖,其他行沒有鎖。

4、可串行化(SERIALIZABLE)

(1)事務在讀取數據時,必須先對其加表級共享鎖 ,直到事務結束才釋放。

(2)事務在更新數據時,必須先對其加表級排他鎖,直到事務結束才釋放。

理解:整個表都添加了鎖,也就不存在增刪改了,但是這樣會大大降低數據庫的性能,所以不建議使用這個隔離級別。

不可重複讀和幻讀的區別

不可重複讀重點在於 update 和 delete ,而幻讀的重點在於 insert。如果使用鎖機制來實現這兩種隔離級別,在可重複讀中,該sql 第一次讀取到數據後,就將這些數據加鎖,其它事務無法修改這些數據,就可以實現可重複讀了。

但這種方法卻無法鎖住insert 的數據,所以當事務A 先前讀取了數據,或者修改了全部數據,事務B 還是可以insert數據提交,這時事務A 就會發現莫名其妙多了一條之前沒有的數據,這就是幻讀,不能通過行鎖來避免。

需要Serializable 隔離級別,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這麼做可以有效的避免幻讀、不可重複讀、臟讀等問題,但會極大的降低數據庫的並發能力。所以說不可重複讀和幻讀最大的區別,就在於如何通過鎖機制來解決他們產生的問題。

MySQL默認的隔離級別是:可重複讀(REPEATABLE_READ)