1.數(shù)據(jù)庫ACID、事務(wù)隔離級別
2.spring事務(wù)相關(guān)的API
3.spring聲明式事務(wù)
4.spring事務(wù)傳播行為
5.sring事務(wù)原理
1.數(shù)據(jù)庫ACID、事務(wù)隔離級別
此章節(jié)作為理論基礎(chǔ)先行篇。
事務(wù)四個特性:
①原子性(Atomicity)
sql作為一個整體,要么全部成功,要么全部失敗回滾。
②一致性(Consistency)
sql的執(zhí)行完之后保持整體的一致性。
③隔離性(Isolation)
用于并發(fā)控制,實現(xiàn)并發(fā)執(zhí)行的事務(wù)能夠按照順序執(zhí)行
④持久性(Durability)
事務(wù)提交后,數(shù)據(jù)會持久化到磁盤中
在高并發(fā)場景下,要完全滿足ACID是很困難的,除非將事務(wù)的執(zhí)行設(shè)置為串行化,但是這樣會導(dǎo)致性能下降,因此根據(jù)業(yè)務(wù)場景不同對事務(wù)的要求不同,數(shù)據(jù)庫設(shè)計了四種隔離級別,供用戶自行選擇。
| 隔離級別 | 臟讀 | 不可重復(fù)度 | 幻讀 |
|---|---|---|---|
| 讀未提交(read uncommitted) | √ | √ | √ |
| 讀已提交(read committed)oracle默認(rèn)隔離級別 | ? | √ | √ |
| 可重復(fù)讀(repeatable read)mysql默認(rèn)隔離級別 | ? | ? | √ |
| 串行化(serializable) | ? | ? | ? |
讀未提交:
事務(wù)A可以讀取其他事務(wù)未提交的修改

讀已提交:
事務(wù)A只能讀取其他事務(wù)已提交的修改

可重復(fù)讀:
事務(wù)A在開啟事務(wù)后,所讀取的數(shù)據(jù)①當(dāng)前事務(wù)未修改的數(shù)據(jù)讀取的結(jié)果是開啟事務(wù)之前一致②當(dāng)前事務(wù)修改的數(shù)據(jù)再次讀取是當(dāng)前事務(wù)修改的值。

串行化
數(shù)據(jù)庫每次讀寫都會加鎖,讀讀鎖不會產(chǎn)生沖突,讀寫鎖,寫寫鎖時會產(chǎn)生沖突,例如下圖,事務(wù)讀取改行數(shù)據(jù)時會加讀鎖,導(dǎo)致事務(wù)B掛起,當(dāng)事務(wù)A commit后釋放讀鎖,事務(wù)B繼續(xù)執(zhí)行。

不同的數(shù)據(jù)隔離級別可能會導(dǎo)致的問題解釋:
臟讀:
當(dāng)前事務(wù)讀取了其他事務(wù)未提交的修改數(shù)據(jù)
不可重復(fù)讀:
當(dāng)前事務(wù)前后讀取的同一條數(shù)據(jù)結(jié)果不一致。
幻讀:
當(dāng)前事務(wù)內(nèi),讀取一個范圍內(nèi)的記錄,前后結(jié)果不一致(記錄減少或記錄增加)
比如select count(1) from table T where a>1 結(jié)果為5,在當(dāng)前事務(wù)內(nèi)再次執(zhí)行發(fā)現(xiàn)結(jié)果為10
對于隔離級別為可重復(fù)讀下,mysql通過MVCC(多版本并發(fā)控制)基本可以幫我們解決掉幻讀問題,但是對于update/delete問題還是解決不了的。
讀提交、不可重復(fù)讀 的隔離級別之所以可以將數(shù)據(jù)隔離,是因為數(shù)據(jù)庫引擎引入的視圖(read-view)的概念,可以對數(shù)據(jù)生成快照,結(jié)合回滾日志實現(xiàn)的。
此處只是簡單提一下,目前打算放在之后數(shù)據(jù)庫專欄分析該問題。
2.spring事務(wù)相關(guān)的API
TransactionDefinition 事務(wù)定義相關(guān)類
PlatformTransactionManager 事務(wù)管理類
TransactionStatus 事務(wù)運行時相關(guān)的狀態(tài)



3.spring聲明式事務(wù)
為了簡化對事物的控制,spring提出了聲明式事務(wù),簡化了對事務(wù)的操作。
- spring配置文件
<!--配置包掃描-->
<context:component-scan base-package="com.ckd"/>
<!--配置數(shù)據(jù)源-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://master:3306/dck"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--JDBCTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"/>
</bean>
<!--配置事務(wù)管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!--支持事務(wù)注解-->
<tx:annotation-driven transaction-manager="txManager"/>
之后就可以用@Transactionnal注解了
4.spring事務(wù)傳播行為
| 類別 | 事務(wù)傳播行為 | 說明 |
|---|---|---|
| 支持當(dāng)前事務(wù) | PROPAGATION_REQUIRED | 當(dāng)前存在事務(wù),則加入到該事務(wù)中,沒事務(wù)的話,新建一個事務(wù)(默認(rèn)傳播行為) |
| ~ | PROPAGATION_SUPPORTS | 當(dāng)前存在事務(wù)加入到該事務(wù)中,如果當(dāng)前沒有事務(wù),則以非事務(wù)的方式運行 |
| ~ | PROPAGATION_MANDATORY | 支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),則拋出異常 |
| 不支持當(dāng)前事務(wù) | PROPAGATION_REQUIRES_NEW | 不管當(dāng)前有沒有事務(wù),都會新建一個事務(wù) |
| ~ | PROPAGATION_NOT_SUPPORT | 以非事務(wù)的方式運行,如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起 |
| ~ | PROPAGATION_NEVER | 以非事務(wù)的方式運行,如果當(dāng)前存在事務(wù),則拋出異常 |
| 嵌套事務(wù) | PROPAGATION_NESTED | 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則進(jìn)行與PROPAGATION_REQUIRED類似的操作 |
PROPAGATION_REQUIRES_NEW、 PROPAGATION_NESTED這兩個事務(wù)比較疑惑,因此單獨提出來分析一下。
PROPAGATION_REQUIRES_NEW:
如下圖在同一個線程中,在執(zhí)行MethodA()時開啟一個事務(wù),調(diào)用serviceB.methodB()時會重新開啟一個事務(wù),執(zhí)行methodA時的事務(wù)暫時掛起,由AbstractPlatformTransactionManager負(fù)責(zé)相應(yīng)的線程及事務(wù)信息的保存處理(具體源碼沒研究過)。MethodB()執(zhí)行完commit之后,MethodA恢復(fù)之前事務(wù)狀態(tài),繼續(xù)執(zhí)行。
因此結(jié)論是:
MethodB執(zhí)行的異?;貪L并不會影響methodA的回滾操作,兩個事務(wù)互不影響。
class ServiceA{
@Transactional(propagation = Propagation.REQUIRED)
methodA(){
serviceB.methodB();
}
}
class ServiceB{
@Transactional(propagation = Propagation.REQUIRES_NEW)
methodB(){
}
}
PROPAGATION_NESTED:
PROPAGATION_NESTED作為一個嵌套事務(wù),實際上是serviceB.methodB()作為methodA執(zhí)行時開啟的事務(wù)的一個子事務(wù),兩者本質(zhì)上是同一個事務(wù),在執(zhí)行到serviceB.methodB()會通過savePoint(參見TransactionStatus類)機(jī)制記錄執(zhí)行位置,當(dāng)methodB發(fā)生異常時,會通過savePoint將methodB執(zhí)行的sql語句回滾,methodA并不受影響(前提時異常捕獲了)。
class ServiceA{
@Transactional(propagation = Propagation.REQUIRED)
methodA(){
serviceB.methodB();
}
}
class ServiceB{
@Transactional(propagation = Propagation.NESTED)
methodB(){
}
}