事務(wù)(Transaction)是并發(fā)控制的單位,是用戶定義的一個(gè)操作序列。這些操作要么都做,要么都不做,是一個(gè)不可分割的工作單位
事務(wù)的隔離級(jí)別
臟讀:事務(wù)1更新數(shù)據(jù),未提交時(shí),事務(wù)2讀取,事務(wù)2讀取的是事務(wù)1修改后的值,此時(shí)事務(wù)1出錯(cuò)回滾,導(dǎo)致事務(wù)2臟讀;
不可重復(fù)讀:事務(wù)1多次查詢,查詢1與查詢2期間,事務(wù)2修改了數(shù)據(jù)并提交,事務(wù)1中查詢1與查詢2獲取的數(shù)據(jù)不一致
幻讀:事務(wù)1多次查詢,查詢1與查詢2期間,事務(wù)2新增了數(shù)據(jù),事務(wù)1中查詢1與查詢2獲取的數(shù)據(jù)不一致
- READ UNCOMMITTED(讀未提交): 事務(wù)執(zhí)行時(shí)可以看到其他事務(wù)“沒有提交的新增+修改”記錄??梢宰x到一個(gè)事務(wù)的中間過(guò)程,違背了事務(wù)的隔離性,基本不會(huì)使用。
結(jié)果:導(dǎo)致臟讀,導(dǎo)致不可重復(fù)讀,導(dǎo)致幻讀- READ COMMITTED (讀已提交) : 事務(wù)執(zhí)行時(shí)可以看到其他事務(wù)“已經(jīng)提交的新增+修改”記錄。
結(jié)果:避免臟讀,導(dǎo)致不可重復(fù)讀,可能導(dǎo)致幻讀- REPEATABLE READ(可重復(fù)讀): 事務(wù)執(zhí)行時(shí)可以看到其他事務(wù)“已經(jīng)提交的新增”記錄。
結(jié)果:避免臟讀,避免不可重復(fù)讀,可能導(dǎo)致幻讀(mvcc機(jī)制優(yōu)化)(了解間隙鎖,也可解決幻讀)- SERIALIZABLE (串行化): 事務(wù)執(zhí)行時(shí)看不到其他事務(wù)對(duì)數(shù)據(jù)庫(kù)所做的更新。兩個(gè)事務(wù)實(shí)際上是串行化方式運(yùn)行。
結(jié)果:避免臟讀,避免不可重復(fù)讀,避免幻讀
Spring隔離界別體現(xiàn)
@Transactional(isolation = Isolation.DEFAULT)
Isolation.DEFAULT 即默認(rèn)與數(shù)據(jù)庫(kù)一致
查看mysql數(shù)據(jù)庫(kù)隔離級(jí)別
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
Mysql默認(rèn)的事務(wù)隔離級(jí)別是REPEATABLE-READ。
SERIALIAZABLE如何保證隔離級(jí)別
SERIALIAZABLE事務(wù)隔離級(jí)別采用使用 2PL(Two-Phase Locking,兩階段鎖)機(jī)制來(lái)控制本地事務(wù)的并發(fā),保證隔離性。2PL 是將鎖操作分為加鎖和解鎖兩個(gè)階段,并且保證兩個(gè)階段完全不相交。加鎖階段,只加鎖,不放鎖。解鎖階段,只放鎖,不加鎖。在一個(gè)本地事務(wù)中,每執(zhí)行一條更新操作之前,都會(huì)先獲取對(duì)應(yīng)的鎖資源,只有獲取鎖資源成功才會(huì)執(zhí)行該操作,并且一旦獲取了鎖資源就會(huì)持有該鎖資源直到本事務(wù)執(zhí)行結(jié)束。MySQL 通過(guò)這種 2PL 機(jī)制,可以保證在本地事務(wù)執(zhí)行過(guò)程中,其他并發(fā)事務(wù)不能操作相同資源,從而實(shí)現(xiàn)了事務(wù)隔離。由于讀寫都需要上鎖,性能差。
MVCC(Multi Version Concurrency Control,多版本并發(fā)控制)
mvcc查用快照版本,增刪改用最新版本,超賣等場(chǎng)景解決方案之一
MySql通過(guò)內(nèi)部機(jī)制避免幻讀,在rr隔離級(jí)別下幾乎達(dá)到了串行化隔離級(jí)別的效果。
InnoDB是在undolog中實(shí)現(xiàn)的MVCC,通過(guò)undolog可以找回?cái)?shù)據(jù)的歷史版本。
MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個(gè)隔離級(jí)別下工作。其他兩個(gè)隔離級(jí)別和MVCC不兼容, 因?yàn)?READ UNCOMMITTED 總是讀取最新的數(shù)據(jù)行, 而不是符合當(dāng)前事務(wù)版本的數(shù)據(jù)行。而 SERIALIZABLE 則會(huì)對(duì)所有讀取的行都加鎖。
InnoDB存儲(chǔ)引擎在數(shù)據(jù)庫(kù)每行數(shù)據(jù)的后面添加了三個(gè)字段
事務(wù)ID(DB_TRX_ID)字段: 用來(lái)標(biāo)識(shí)最近一次對(duì)本行記錄做修改(insert|update)的事務(wù)的標(biāo)識(shí)符, 即最后一次修改(insert|update)本行記錄的事務(wù)id。
回滾指針(DB_ROLL_PTR)字段: 指寫入回滾段(rollback segment)的 undo log record (撤銷日志記錄)。
DB_ROW_ID字段: 表中沒有主鍵或合適的唯一索引, 也就是無(wú)法生成聚簇索引的時(shí)候, InnoDB會(huì)幫我們自動(dòng)生成聚集索引, 聚簇索引會(huì)使用DB_ROW_ID的值來(lái)作為主鍵; 如果我們有自己的主鍵或者合適的唯一索引, 那么聚簇索引中也就不會(huì)包含 DB_ROW_ID 了。
對(duì)于刪除操作,mysql底層會(huì)記錄好被刪除的數(shù)據(jù)行的回滾事務(wù)id,對(duì)于更新操作,mysql底層會(huì)新增一條相同數(shù)據(jù)并記錄好對(duì)應(yīng)的事務(wù)id。
在RR級(jí)別下,快照讀(簡(jiǎn)單的select操作)是通過(guò)MVVC和undo log來(lái)實(shí)現(xiàn)的
當(dāng)前讀(select ... lock in share mode,select ... for update,insert,update,delete)是通過(guò)加record lock(記錄鎖)和gap lock(間隙鎖)來(lái)實(shí)現(xiàn)的。
事務(wù)的傳播行為
1:PROPAGATION_REQUIRED
如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù),如果已經(jīng)存在一個(gè)事務(wù)中,則加入該事務(wù)。
比如說(shuō),ServiceB.methodB的事務(wù)級(jí)別定義為PROPAGATION_REQUIRED, 那么由于執(zhí)行ServiceA.methodA的時(shí)候,ServiceA.methodA已經(jīng)起了事務(wù),這時(shí)調(diào)用ServiceB.methodB,ServiceB.methodB看到自己已經(jīng)運(yùn)行在ServiceA.methodA的事務(wù)內(nèi)部,就不再起新的事務(wù)。而假如ServiceA.methodA運(yùn)行的時(shí)候發(fā)現(xiàn)自己沒有在事務(wù)中,他就會(huì)為自己分配一個(gè)事務(wù)。這樣,在ServiceA.methodA或者在ServiceB.methodB內(nèi)的任何地方出現(xiàn)異常,事務(wù)都會(huì)被回滾。即使ServiceB.methodB的事務(wù)已經(jīng)被提交,但是ServiceA.methodA在接下來(lái)fail要回滾,ServiceB.methodB也要回滾。
2:PROPAGATION_SUPPORTS
如果當(dāng)前在事務(wù)中,即以事務(wù)的形式運(yùn)行,如果當(dāng)前不再一個(gè)事務(wù)中,那么就以非事務(wù)的形式運(yùn)行
3:PROPAGATION_MANDATORY
必須在一個(gè)事務(wù)中運(yùn)行,否則就要拋出異常。
4:PROPAGATION_REQUIRES_NEW
新建事務(wù),如果當(dāng)前事務(wù)存在,則把當(dāng)前事務(wù)掛起
比如我們?cè)O(shè)計(jì)ServiceA.methodA的事務(wù)級(jí)別為PROPAGATION_REQUIRED,ServiceB.methodB的事務(wù)級(jí)別為PROPAGATION_REQUIRES_NEW,那么當(dāng)執(zhí)行到ServiceB.methodB的時(shí)候,ServiceA.methodA所在的事務(wù)就會(huì)掛起,ServiceB.methodB會(huì)起一個(gè)新的事務(wù),等待ServiceB.methodB的事務(wù)完成以后,他才繼續(xù)執(zhí)行。他與PROPAGATION_REQUIRED 的事務(wù)區(qū)別在于事務(wù)的回滾程度了。因?yàn)镾erviceB.methodB是新起一個(gè)事務(wù),那么就是存在兩個(gè)不同的事務(wù)。如果ServiceB.methodB已經(jīng)提交,那么ServiceA.methodA失敗回滾,ServiceB.methodB是不會(huì)回滾的。如果ServiceB.methodB失敗回滾,如果他拋出的異常被ServiceA.methodA捕獲,ServiceA.methodA事務(wù)仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。
比如ServiceA.methodA的事務(wù)級(jí)別是PROPAGATION_REQUIRED ,而ServiceB.methodB的事務(wù)級(jí)別是PROPAGATION_NOT_SUPPORTED ,那么當(dāng)執(zhí)行到ServiceB.methodB時(shí),ServiceA.methodA的事務(wù)掛起,而他以非事務(wù)的狀態(tài)運(yùn)行完,再繼續(xù)ServiceA.methodA的事務(wù)。
6:PROPAGATION_NEVER
以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),則拋出異常。
假設(shè)ServiceA.methodA的事務(wù)級(jí)別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務(wù)級(jí)別是PROPAGATION_NEVER ,那么ServiceB.methodB就要拋出異常了。
7:PROPAGATION_NESTED
理解Nested的關(guān)鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區(qū)別是,PROPAGATION_REQUIRES_NEW另起一個(gè)事務(wù),將會(huì)與他的父事務(wù)相互獨(dú)立,而Nested的事務(wù)和他的父事務(wù)是相依的,他的提交是要等和他的父事務(wù)一塊提交的。也就是說(shuō),如果父事務(wù)最后回滾,他也要回滾的。而Nested事務(wù)的好處是他有一個(gè)savepoint。
解決Transactional注解不回滾
1、檢查你方法是不是public的,不能作用在方法的內(nèi)部的任何方法上
2、你的異常類型是不是unchecked異常,即運(yùn)行時(shí)異常 (java里面將派生于Error或者RuntimeException(比如空指針,數(shù)組越界,1/0)的異常稱為unchecked異常,其他繼承自java.lang.Exception得異常統(tǒng)稱為Checked Exception,如IOException、TimeoutException等)。如果想check異常也想回滾怎么辦,注解上面寫明異常類型即可@Transactional(rollbackFor=Exception.class)。類似的還有norollbackFor,自定義不回滾的異常
3、數(shù)據(jù)庫(kù)引擎要支持事務(wù),如果是MySQL,注意表要使用支持事務(wù)的引擎,比如innodb,如果是myisam,事務(wù)是不起作用的
4、是否開啟了對(duì)注解的解析(Springboot不需關(guān)注,Springboot會(huì)自動(dòng)配置,5同)
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
5、spring是否掃描到你這個(gè)包,如下是掃描到org.test下面的包
<context:component-scan base-package="org.test" ></context:component-scan>
6、檢查是不是同一個(gè)類中的方法調(diào)用(如a方法調(diào)用同一個(gè)類中的b方法)
7、異常是不是被你catch住了
解決不能作用在方法的任何內(nèi)部方法上
1.注入本身service,在當(dāng)前處理service注入自己,然后內(nèi)部調(diào)用 推薦
2.(1).在未定義事務(wù)的方法調(diào)用帶有事務(wù)的方法使用AopContext.currentProxy()((AfterSaleManager)AopContext.currentProxy()).innerRefund(goodsReturnsApply); 可用
(2).spring配置文件配置
<aop:aspectj-autoproxy expose-proxy="true"/>
3、重新定義對(duì)象 applicationContext.getBean() 未測(cè)試
4、使用@Aspect注解 未測(cè)試
希望在編譯期間進(jìn)行織入(weaving),還是編譯后(post-compile)或是運(yùn)行時(shí)(run-time)。Spring只支持運(yùn)行時(shí)織入,你可以選擇使用AspectJ編譯后(post-compile)或載入時(shí)(load-time)織入。Spring基于代理模式(使用CGLIB),它有一個(gè)使用限制,即無(wú)法在使用final修飾的bean上應(yīng)用橫切關(guān)注點(diǎn)。
借鑒:https://juejin.im/post/5b90cbf4e51d450e84776d27