Spring 事務的使用
Spring 事務有編程式事務和聲明式事務。
常用的聲明式事務使用 @Transaction 註解就可以開啟事務。編程式事務不常用暫且不表。
@Transaction 是有參數的,我們可以設置事務的隔離級別,如:
@Transactional(isolation = Isolation.REPEATABLE_READ)
想要知道參數的意義,我們就要知道 Spring 事務的原理。
Spring 事務的原理
Spring 事務其實就是數據庫事務。
事務有四種隔離級別,使用如下:
讀未提交
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
讀已提交
@Transactional(isolation = Isolation.READ_COMMITTED)
可重複讀
@Transactional(isolation = Isolation.REPEATABLE_READ)
串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
下面我們依次講解這四個隔離級別有什麼用。
我們先以 MySQL 數據庫為例子,MySQL 的 InnoDB 引擎支持事務,提供了 行鎖 和 表鎖 。
先下定義,加鎖 是為了防止併發的時候出錯。 行鎖 的性能要優於 表鎖,不加鎖 的性能要優於 行鎖。
得到結論,保證在併發不出錯的情況下能 不加鎖 就 不加鎖,能加 行鎖 就不要加 表鎖。
ok,下面開始分析四個隔離級別都加的什麼鎖。
讀未提交。
這個級別不加鎖,那麼程序裡使用這個隔離級別會出現什麼問題呢?
如果都是 select 語句,什麼問題都不會有,就用這個級別。如果有 update 語句,那就可能有問題了。
我們先提出這麼一個問題:
假如一個事務 A 先對某行數據進行修改,然後又改回去了。如果有另一個事務 B ,剛好卡在 A 的兩個操作中間對這行數據進行了一個讀取,那麼讀到的就是髒數據。
讀已提交。
針對上一個隔離級別的問題怎麼改進呢?對將要修改的數據加行鎖(對 update 語句加鎖),注意,注意,注意,加鎖了,安全了但是性能下降了。事務 A 對這行數據要修改,先加個鎖,直到結束才釋放鎖,事務 B 就沒辦法卡在 A 的操作中間對數據進行操作了。
但仍然有問題:
假如一個事務 A 先對某行數據進行讀取,然後進行加一保存。如果有另一個事務 B ,剛好卡在 A 的兩個操作中間對這行數據進行了一個讀取並加一保存,那麼數據就混亂了。
這個問題也可以表述為:
假如一個事務 A 先對某行數據進行讀取,然後進行讀取。如果有另一個事務 B ,剛好卡在 A 的兩個操作中間對這行數據進行了一個修改,那麼 A 兩次讀取的數據就不一樣了。
第一個表述更好理解,第二個表述主要為了突出了 不可重複讀 這幾個字,迎合下一個級別 可重複讀。
可重複讀。
針對上一個隔離級別的問題怎麼改進呢?對將要讀取的數據也加行鎖(對 select 語句加鎖),注意,注意,注意,上一個只有 update 語句加鎖,這個連 select 語句也加鎖了,安全了但是性能下降了。事務 A 對這行數據要讀取,先加個鎖,直到結束才釋放鎖,事務 B 就沒辦法卡在 A 的讀取的中間對數據進行操作了。
這個隔離級別仍然有問題:
假如事務 B 有一個 insert 語句,卡在事務 A 的某些語句中間插了一條數據,那麼就可能造成數據混亂,比如,我們對整個表操作, x 字段的值加 50,y 字段的值減 50,事務 B 卡在中間插入的數據只執行了 y 字段減 50,數據就混亂了。
串行化。
針對上一個隔離級別的問題,串行化加的是表鎖,什麼問題都不會有,但是性能很差。
綜上所述,事務隔離級別的選擇其實是我們程序併發情況下會出哪種錯誤,就用相應的隔離級別,在併發不出錯的前提下,儘可能的不加鎖,或者加行鎖而不是表鎖,這樣才能提高性能。