本文約5000字裙品,建議閱讀時(shí)間10分鐘
關(guān)于數(shù)據(jù)庫(kù)的事務(wù)帝火,相信每個(gè)碼農(nóng)都有接觸,也相信都遇到過(guò)與之相關(guān)的坑风罩。
本文旨在歸納總結(jié)下事務(wù)的概念讶踪、原理及使用。
本文針對(duì)的主要是MySQL的事務(wù)機(jī)制以及Spring 的事務(wù)管理泊交。
什么是事務(wù)
簡(jiǎn)單挑明下事務(wù)的概念乳讥,從說(shuō)事務(wù)都會(huì)舉的一個(gè)實(shí)際問(wèn)題引出事務(wù)的概念:
用戶(hù)A想要給用戶(hù)B轉(zhuǎn)賬100元,那么需要做的事情是:
- 查詢(xún)A賬戶(hù)的信息
- 如大于100廓俭,從A賬戶(hù)中取出100
- 查詢(xún)B賬戶(hù)的信息
- 如果B賬戶(hù)一切正常云石,往B賬戶(hù)中存入100
上面的流程不要太過(guò)注重細(xì)節(jié),闡述流程用研乒,其中明顯會(huì)有幾個(gè)問(wèn)題汹忠,比如,A雹熬、B賬戶(hù)處于異常狀態(tài)宽菜,不能進(jìn)行交易;A的余額不足100等等
當(dāng)出現(xiàn)問(wèn)題的時(shí)候竿报,這整個(gè)轉(zhuǎn)賬的動(dòng)作都是失敗的铅乡,我們需要保證A賬戶(hù)的錢(qián)不能少,B賬戶(hù)的錢(qián)也不能多烈菌,都還是原來(lái)的金額阵幸。
自此便是事務(wù)的概念:
數(shù)據(jù)庫(kù)事務(wù)(簡(jiǎn)稱(chēng):事務(wù))是數(shù)據(jù)庫(kù)管理系統(tǒng)執(zhí)行過(guò)程中的一個(gè)邏輯單位,由一個(gè)有限的數(shù)據(jù)庫(kù)操作序列構(gòu)成芽世。
一個(gè)數(shù)據(jù)庫(kù)事務(wù)通常包含了一個(gè)序列的對(duì)數(shù)據(jù)庫(kù)的讀/寫(xiě)操作挚赊。它的存在包含有以下兩個(gè)目的:
- 為數(shù)據(jù)庫(kù)操作序列提供了一個(gè)從失敗中恢復(fù)到正常狀態(tài)的方法,同時(shí)提供了數(shù)據(jù)庫(kù)即使在異常狀態(tài)下仍能保持一致性的方法济瓢。
- 當(dāng)多個(gè)應(yīng)用程序在并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí)荠割,可以在這些應(yīng)用程序之間提供一個(gè)隔離方法,以防止彼此的操作互相干擾旺矾。
當(dāng)事務(wù)被提交給了DBMS蔑鹦,則DBMS需要確保該事務(wù)中的所有操作都成功完成且其結(jié)果被永久保存在數(shù)據(jù)庫(kù)中,如果事務(wù)中有的操作沒(méi)有成功完成宠漩,則事務(wù)中的所有操作都需要被回滾举反,回到事務(wù)執(zhí)行前的狀態(tài);同時(shí),該事務(wù)對(duì)數(shù)據(jù)庫(kù)或者其他事務(wù)的執(zhí)行無(wú)影響扒吁,所有的事務(wù)都好像在獨(dú)立的運(yùn)行。
事務(wù)的四大特性(ACID)
原子性(Atomicity):事務(wù)作為一個(gè)整體被執(zhí)行,包含在其中的對(duì)數(shù)據(jù)庫(kù)的操作要么全部被執(zhí)行雕崩,要么都不執(zhí)行魁索。
一致性(Consistency):事務(wù)應(yīng)確保數(shù)據(jù)庫(kù)的狀態(tài)從一個(gè)一致?tīng)顟B(tài)轉(zhuǎn)變?yōu)榱硪粋€(gè)一致?tīng)顟B(tài)。一致?tīng)顟B(tài)的含義是數(shù)據(jù)庫(kù)中的數(shù)據(jù)應(yīng)滿足完整性約束盼铁。
隔離性(Isolation):多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)粗蔚,一個(gè)事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行。
持久性(Durability):已被提交的事務(wù)對(duì)數(shù)據(jù)庫(kù)的修改應(yīng)該永久保存在數(shù)據(jù)庫(kù)中饶火。
隔離性-隔離級(jí)別
并發(fā)的狀態(tài)下鹏控,事務(wù)會(huì)出現(xiàn)如下一些問(wèn)題:
臟讀
當(dāng)一個(gè)事務(wù)允許讀取另外一個(gè)事務(wù)修改但未提交的數(shù)據(jù)時(shí),就可能發(fā)生臟讀肤寝。
當(dāng)一個(gè)事務(wù)正在訪問(wèn)數(shù)據(jù)并且對(duì)數(shù)據(jù)進(jìn)行了修改当辐,而這種修改還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí)另外一個(gè)事務(wù)也訪問(wèn)這個(gè)數(shù)據(jù)鲤看,然后使用了這個(gè)數(shù)據(jù)缘揪。因?yàn)檫@個(gè)數(shù)據(jù)是還沒(méi)有提交的數(shù)據(jù),那么另外一個(gè)事務(wù)讀到的這個(gè)數(shù)據(jù)是臟數(shù)據(jù)义桂,依據(jù)臟數(shù)據(jù)所做的操作可能是不正確的找筝。不可重復(fù)讀
一個(gè)事務(wù)內(nèi),多次讀同一個(gè)數(shù)據(jù)慷吊。在這個(gè)事務(wù)還沒(méi)有結(jié)束時(shí)袖裕,另一個(gè)事務(wù)也訪問(wèn)該同一數(shù)據(jù)。那么溉瓶,在第一個(gè)事務(wù)的兩次讀數(shù)據(jù)之間陆赋。由于第二個(gè)事務(wù)的修改,那么第一個(gè)事務(wù)讀到的數(shù)據(jù)可能不一樣嚷闭,這樣就發(fā)生了在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的攒岛,因此稱(chēng)為不可重復(fù)讀,即原始讀取不可重復(fù)胞锰。幻影讀
在事務(wù)執(zhí)行過(guò)程中灾锯,當(dāng)兩個(gè)完全相同的查詢(xún)語(yǔ)句執(zhí)行得到不同的結(jié)果集。這種現(xiàn)象稱(chēng)為“幻影讀(phantom read)”
當(dāng)事務(wù)沒(méi)有獲取范圍鎖的情況下執(zhí)行SELECT ... WHERE操作可能會(huì)發(fā)生“幻影讀”嗅榕。
“幻影讀”是不可重復(fù)讀的一種特殊場(chǎng)景:當(dāng)事務(wù)1兩次執(zhí)行SELECT ... WHERE檢索一定范圍內(nèi)數(shù)據(jù)的操作中間顺饮,事務(wù)2在這個(gè)表中創(chuàng)建了(如INSERT)了一行新數(shù)據(jù),這條新數(shù)據(jù)正好滿足事務(wù)1的“WHERE”子句凌那。
需要指出的是事務(wù)1執(zhí)行了兩遍同樣的查詢(xún)語(yǔ)句兼雄,第二次查詢(xún)可能會(huì)得到不同的結(jié)果集。
隔離級(jí)別
為了解決上述問(wèn)題帽蝶,引入了不同的隔離級(jí)別來(lái)解決:
-
未提交讀
未提交讀(READ UNCOMMITTED)是最低的隔離級(jí)別赦肋。允許“臟讀”(dirty reads),事務(wù)可以看到其他事務(wù)“尚未提交”的修改。 -
提交讀
在提交讀(READ COMMITTED)級(jí)別中佃乘,基于鎖機(jī)制并發(fā)控制的DBMS需要對(duì)選定對(duì)象的寫(xiě)鎖一直保持到事務(wù)結(jié)束囱井,但是讀鎖在SELECT操作完成后馬上釋放(因此“不可重復(fù)讀”現(xiàn)象可能會(huì)發(fā)生,見(jiàn)下面描述)趣避。和前一種隔離級(jí)別一樣庞呕,也不要求“范圍鎖”。 -
可重復(fù)讀
在可重復(fù)讀(REPEATABLE READS)隔離級(jí)別中程帕,基于鎖機(jī)制并發(fā)控制的DBMS需要對(duì)選定對(duì)象的讀鎖(read locks)和寫(xiě)鎖(write locks)一直保持到事務(wù)結(jié)束住练,但不要求“范圍鎖”,因此可能會(huì)發(fā)生“幻影讀”愁拭。 -
可串行化
最高的隔離級(jí)別讲逛。在基于鎖機(jī)制并發(fā)控制的DBMS實(shí)現(xiàn)可串行化,要求在選定對(duì)象上的讀鎖和寫(xiě)鎖保持直到事務(wù)結(jié)束后才能釋放敛苇。在SELECT 的查詢(xún)中使用一個(gè)“WHERE”子句來(lái)描述一個(gè)范圍時(shí)應(yīng)該獲得一個(gè)“范圍鎖”(range-locks)妆绞。這種機(jī)制可以避免“幻影讀”(phantom reads)現(xiàn)象。當(dāng)采用不基于鎖的并發(fā)控制時(shí)不用獲取鎖枫攀。但當(dāng)系統(tǒng)探測(cè)到幾個(gè)并發(fā)事務(wù)有“寫(xiě)沖突”的時(shí)候括饶,只有其中一個(gè)是允許提交的。
隨著隔離級(jí)別的提高来涨,能夠避免的問(wèn)題也越多图焰,但同時(shí)帶來(lái)的開(kāi)銷(xiāo)也迅速增加:
隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻影讀 |
---|---|---|---|
未提交讀 | 可能發(fā)生 | 可能發(fā)生 | 可能發(fā)生 |
提交讀 | - | 可能發(fā)生 | 可能發(fā)生 |
可重復(fù)讀 | - | - | 可能發(fā)生 |
可序列化 | - | - | - |
MySQL事務(wù)的原理
MySQL的事務(wù)分類(lèi)
扁平事務(wù)
扁平事務(wù)(Flat Transactions)是事務(wù)類(lèi)型中最簡(jiǎn)單但使用最頻繁的事務(wù)。在扁平事務(wù)中蹦掐,所有的操作都處于同一層次技羔,由BEGIN/START TRANSACTION開(kāi)始事務(wù),由COMMIT/ROLLBACK結(jié)束卧抗,且都是原子的藤滥,要么都執(zhí)行,要么都回滾社裆。因此扁平事務(wù)是應(yīng)用程序成為原子操作的基本組成模塊拙绊。帶有保存節(jié)點(diǎn)的扁平事務(wù)
帶有保存節(jié)點(diǎn)的扁平事務(wù)(Flat Transactions with Savepoints)允許事務(wù)在執(zhí)行過(guò)程中回滾到較早的一個(gè)狀態(tài),而不是回滾所有的操作泳秀。保存點(diǎn)(Savepoint)用來(lái)通知系統(tǒng)應(yīng)該記住事務(wù)當(dāng)前的狀態(tài)标沪,以便當(dāng)之后發(fā)生錯(cuò)誤時(shí),事務(wù)能回到保存點(diǎn)當(dāng)時(shí)的狀態(tài)嗜傅。
對(duì)于扁平事務(wù)來(lái)說(shuō)金句,在事務(wù)開(kāi)始時(shí)隱式地設(shè)置了一個(gè)保存點(diǎn),回滾時(shí)只能回滾到事務(wù)開(kāi)始時(shí)的狀態(tài)鏈?zhǔn)聞?wù)
鏈?zhǔn)聞?wù)(Chained Transaction)是指一個(gè)事務(wù)由多個(gè)子事務(wù)鏈?zhǔn)浇M成吕嘀。前一個(gè)子事務(wù)的提交操作和下一個(gè)子事務(wù)的開(kāi)始操作合并成一個(gè)原子操作违寞,這意味著下一個(gè)事務(wù)將看到上一個(gè)事務(wù)的結(jié)果贞瞒,就好像在一個(gè)事務(wù)中進(jìn)行的一樣。這樣坞靶,在提交子事務(wù)時(shí)就可以釋放不需要的數(shù)據(jù)對(duì)象憔狞,而不必等到整個(gè)事務(wù)完成后才釋放蝴悉。
鏈?zhǔn)聞?wù)與帶保存節(jié)點(diǎn)的扁平事務(wù)不同的是彰阴,鏈?zhǔn)聞?wù)中的回滾僅限于當(dāng)前事務(wù),相當(dāng)于只能恢復(fù)到最近的一個(gè)保存節(jié)點(diǎn)拍冠,而帶保存節(jié)點(diǎn)的扁平事務(wù)能回滾到任意正確的保存點(diǎn)尿这。但是,帶有保存節(jié)點(diǎn)的扁平事務(wù)中的保存點(diǎn)是易失的庆杜,當(dāng)發(fā)生系統(tǒng)崩潰是射众,所有的保存點(diǎn)都將消失,這意味著當(dāng)進(jìn)行恢復(fù)時(shí)晃财,事務(wù)需要從開(kāi)始處重新執(zhí)行叨橱。嵌套事務(wù)
嵌套事務(wù)(Nested Transaction)是一個(gè)層次結(jié)構(gòu)框架。由一個(gè)頂層事務(wù)(top-level transaction)控制著各個(gè)層次的事務(wù)断盛。頂層事務(wù)之下嵌套的事務(wù)成為子事務(wù)(subtransaction),其控制著每一個(gè)局部的操作罗洗,子事務(wù)本身也可以是嵌套事務(wù)。
MySQL本身是不支持嵌套事務(wù)的钢猛,每次start transaction 都會(huì)隱式提交伙菜。分布式事務(wù)
分布式事務(wù)(Distributed Transactions)通常是一個(gè)在分布式環(huán)境下運(yùn)行的扁平事務(wù),因此需要根據(jù)數(shù)據(jù)所在位置訪問(wèn)網(wǎng)絡(luò)中不同節(jié)點(diǎn)的數(shù)據(jù)庫(kù)資源命迈,分布式環(huán)境下運(yùn)行的扁平事務(wù).
牽涉到分布式贩绕,就默默埋下一個(gè)大坑好了,寫(xiě)起來(lái)也是能有一篇文章壶愤。
MySQL事務(wù)控制
BEGIN或START TRANSACTION淑倾;顯式地開(kāi)啟一個(gè)事務(wù);
COMMIT征椒;也可以使用COMMIT WORK娇哆,不過(guò)二者是等價(jià)的。COMMIT會(huì)提交事務(wù)陕靠,并使已對(duì)數(shù)據(jù)庫(kù)進(jìn)行的所有修改稱(chēng)為永久性的迂尝;
ROLLBACK;有可以使用ROLLBACK WORK剪芥,不過(guò)二者是等價(jià)的垄开。回滾會(huì)結(jié)束用戶(hù)的事務(wù)税肪,并撤銷(xiāo)正在進(jìn)行的所有未提交的修改溉躲;
SAVEPOINT identifier榜田;SAVEPOINT允許在事務(wù)中創(chuàng)建一個(gè)保存點(diǎn),一個(gè)事務(wù)中可以有多個(gè)SAVEPOINT锻梳;
RELEASE SAVEPOINT identifier箭券;刪除一個(gè)事務(wù)的保存點(diǎn),當(dāng)沒(méi)有指定的保存點(diǎn)時(shí)疑枯,執(zhí)行該語(yǔ)句會(huì)拋出一個(gè)異常辩块;
ROLLBACK TO identifier;把事務(wù)回滾到標(biāo)記點(diǎn)荆永;
SET TRANSACTION废亭;用來(lái)設(shè)置事務(wù)的隔離級(jí)別。InnoDB存儲(chǔ)引擎提供事務(wù)的隔離級(jí)別有READ UNCOMMITTED具钥、READ COMMITTED豆村、REPEATABLE READ和SERIALIZABLE。
MYSQL 事務(wù)處理主要有兩種方法:
用 BEGIN, ROLLBACK, COMMIT來(lái)實(shí)現(xiàn)
BEGIN 開(kāi)始一個(gè)事務(wù)
ROLLBACK 事務(wù)回滾
COMMIT 事務(wù)確認(rèn)直接用 SET 來(lái)改變 MySQL 的自動(dòng)提交模式:
SET AUTOCOMMIT=0 禁止自動(dòng)提交
SET AUTOCOMMIT=1 開(kāi)啟自動(dòng)提交
Tips
在 MySQL 命令行的默認(rèn)設(shè)置下骂删,事務(wù)都是自動(dòng)提交的掌动,即執(zhí)行 SQL 語(yǔ)句后就會(huì)馬上執(zhí)行 COMMIT 操作。因此要顯式地開(kāi)啟一個(gè)事務(wù)務(wù)須使用命令 BEGIN 或 START TRANSACTION宁玫,或者執(zhí)行命令 SET AUTOCOMMIT=0粗恢,用來(lái)禁止使用當(dāng)前會(huì)話的自動(dòng)提交。
在 MySQL 中只有使用了 Innodb 數(shù)據(jù)庫(kù)引擎的數(shù)據(jù)庫(kù)或表才支持事務(wù)撬统。
事務(wù)用來(lái)管理 insert, update, delete 語(yǔ)句
在MySQL數(shù)據(jù)庫(kù)中适滓,默認(rèn)的隔離級(jí)別為Repeatable read (可重復(fù)讀);
Spring的事務(wù)管理
OK, 到這叨叨了一大波原理恋追,Talk is cheap凭迹,Show me the code。下面用代碼來(lái)說(shuō)下Spring對(duì)事務(wù)是如何進(jìn)行管理的苦囱。
Spring通過(guò)AOP嗅绸,抽象統(tǒng)一的接口來(lái)進(jìn)行聲明式事務(wù)管理,好處是它將事務(wù)管理代碼從業(yè)務(wù)方法中分離出來(lái)撕彤,并且讓開(kāi)發(fā)按人員可以不用關(guān)注具體事務(wù)管理方式的實(shí)現(xiàn)鱼鸠;
可采用xml配置或者注解來(lái)實(shí)現(xiàn)。
以下是基于Spring 4.X 的通用事務(wù)管理配置文件:
此處采用通用bean配置羹铅,其他配置方式蚀狰,可以參照
Spring事務(wù)配置的五種方式和spring里面事務(wù)的傳播屬性和事務(wù)隔離級(jí)別
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<!--
常見(jiàn)的TranszctionManager類(lèi)
org.springframework.orm.jpa.JpaTransactionManager 使用JPA持久化使用該事務(wù)管理器
org.springframework.orm.hibernate3.HibernateTransactionManager Hibernate3.o事務(wù)管理器
org.springframework.jdbc.datasource.DataSourceTransactionManager SpringJdbc或ibatis等基于DataSource數(shù)據(jù)源持久化技術(shù)時(shí)使用管理器
org.springframework.orm.jdo.JdoTransactionManager 使用JDO持久化使用該事務(wù)管理器
org.springframework.transaction.jta.JtaTranszctionManager 具有多個(gè)數(shù)據(jù)源的全局事務(wù)使用事務(wù)管理器
目前普遍使用DataSourceTransactionManager居多,配合MyBatis
-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="txTransactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="txManager" />
<property name="transactionAttributes">
<!--
用正則匹配掃描方法职员,以save麻蹋,update,delete開(kāi)頭的方法焊切,使用事務(wù)管理
Spring在TransactionDefinition接口中規(guī)定了7中類(lèi)型的事務(wù)傳播行為扮授,規(guī)定了事務(wù)方法和事務(wù)方法發(fā)生嵌套調(diào)用時(shí)事務(wù)如何傳播的芳室。
propagation_required:如果當(dāng)前沒(méi)有事務(wù),就新建一個(gè)事務(wù)刹勃,如果已經(jīng)存在堪侯,就加入到存在的事務(wù)中,這是最常見(jiàn)的選擇
propagation_supports:支持當(dāng)前的事務(wù)荔仁,如果當(dāng)前沒(méi)有事務(wù)伍宦,就按非事務(wù)模式執(zhí)行
propagation_mandatory:使用當(dāng)前事務(wù),如果當(dāng)前沒(méi)有事務(wù)咕晋,拋出異常
propagation_requires_new:新建事務(wù)雹拄,如果當(dāng)前存在事務(wù)收奔,把當(dāng)前事務(wù)掛起
propagation_not_supported:以非事務(wù)方式執(zhí)行掌呜,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起
propagation_never:以非事務(wù)方式執(zhí)行坪哄,如果當(dāng)前存在事務(wù)质蕉,則拋出異常
propagation_nested:如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行翩肌,如果沒(méi)有事務(wù)模暗,則執(zhí)行與propagation_required類(lèi)似的操作
回滾策略:
在碰到哪些異常的時(shí)候回滾,
支持Exception念祭,CheckedException兑宇,以及自定義異常
- 表示拋出該異常時(shí)需要回滾
+表示即使拋出該異常事務(wù)同樣要提交
-->
<props>
<prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="delete*">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>
<!--AOP切片,在配置的包中進(jìn)行掃描匹配的方法進(jìn)行事務(wù)管理-->
<bean id="txTransactionPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="txTransactionInterceptor"/>
</property>
<property name="patterns">
<list>
<value>com.test.service.impl.*.*</value>
</list>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="proxyTargetClass" value="true"></property>
<property name="beanNames">
<value>*ServiceImpl</value>
</property>
<property name="interceptorNames">
<list>
<value>txTransactionPointcutAdvisor</value>
</list>
</property>
</bean>
</beans>
以下是基于Spring 4.X 的注解式實(shí)現(xiàn)的例子:
//添加事務(wù)注解
//1.使用 propagation 指定事務(wù)的傳播行為, 即當(dāng)前的事務(wù)方法被另外一個(gè)事務(wù)方法調(diào)用時(shí)如何使用事務(wù), 取值見(jiàn)上XML配置注解
//2.使用 isolation 指定事務(wù)的隔離級(jí)別, 最常用的取值為 READ_COMMITTED粱坤,參見(jiàn)隔離級(jí)別隶糕,支持DEFAULT,READ_UNCOMMITTED站玄,READ_COMMITTED枚驻,REPEATABLE_READ,SERIALIZABLE
//3.默認(rèn)情況下 Spring 的聲明式事務(wù)對(duì)所有的運(yùn)行時(shí)異常進(jìn)行回滾株旷,也可以通過(guò)對(duì)應(yīng)的屬性進(jìn)行設(shè)置再登,通常情況下去默認(rèn)值即可,取值見(jiàn)上XML配置注解
//no-rollback-for 對(duì)應(yīng)xml中的+Exception
//4.使用 readOnly 指定事務(wù)是否為只讀. 表示這個(gè)事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù),這樣可以幫助數(shù)據(jù)庫(kù)引擎優(yōu)化事務(wù). 若真的事一個(gè)只讀取數(shù)據(jù)庫(kù)值的方法, 應(yīng)設(shè)置 readOnly=true,或者直接不使用事務(wù)
//5.使用 timeout 指定強(qiáng)制回滾之前事務(wù)可以占用的時(shí)間
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
rollbackFor = { Exception.class },
//noRollbackFor = {Exception.class},
readOnly=false,
timeout=3)
public void crud(){
try {
doCURD();
//這里為了說(shuō)明晾剖,拋異常時(shí)锉矢,如果沒(méi)有使用try catch ,則spring會(huì)回滾齿尽,doCURD中對(duì)數(shù)據(jù)庫(kù)的操作都不會(huì)執(zhí)行
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
//如果用了try catch沽损,在catch里面再拋出一個(gè)指定異常,這樣出了異常才會(huì)回滾
//但是加上這句之后拋了異常就能回滾(有這句代碼就不需要再手動(dòng)拋出運(yùn)行時(shí)異常了)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
遇到的坑
- 嵌套事務(wù)的傳遞
當(dāng)Service A 調(diào)用Service B時(shí)雕什,就會(huì)產(chǎn)生邏輯上的嵌套事務(wù)缠俺,Spring 的處理如下:
PROPAGATION_REQUIRED 如果當(dāng)前線程中已經(jīng)存在事務(wù), 方法調(diào)用會(huì)加入此事務(wù), 如果當(dāng)前沒(méi)有事務(wù)显晶,就新建一個(gè)事務(wù)
PROPAGATION_REQUIRES_NEW 啟動(dòng)一個(gè)新的, 不依賴(lài)于環(huán)境的 "內(nèi)部" 事務(wù). 這個(gè)事務(wù)將被完全 commited 或 rolled back 而不依賴(lài)于外部事務(wù), 它擁有自己的隔離范圍, 自己的鎖, 等等. 當(dāng)內(nèi)部事務(wù)開(kāi)始執(zhí)行時(shí), 外部事務(wù)將被掛起, 內(nèi)務(wù)事務(wù)結(jié)束時(shí), 外部事務(wù)將繼續(xù)執(zhí)行。
另一方面, PROPAGATION_NESTED 開(kāi)始一個(gè) "嵌套的" 事務(wù), 它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù). 嵌套事務(wù)開(kāi)始執(zhí)行時(shí), 它將取得一個(gè) savepoint. 如果這個(gè)嵌套事務(wù)失敗, 我們將回滾到此 savepoint. 嵌套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會(huì)被提交壹士。
由此PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于, PROPAGATION_REQUIRES_NEW 完全是一個(gè)新的事務(wù), 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 嵌套事務(wù)也會(huì)被 commit, 這個(gè)規(guī)則同樣適用于 roll back磷雇。
當(dāng)數(shù)據(jù)庫(kù)處理結(jié)果與預(yù)期不符,可以檢查配置是否合理躏救。
異常的回滾
在使用try catch塊時(shí)需要注意回滾策略唯笙,詳見(jiàn)上java代碼MySQL的引擎
當(dāng)MySQL的引擎配置為MyISAM(默認(rèn))時(shí),是不支持事務(wù)的盒使,Spring配置得再6崩掘,也不會(huì)起作用,目前普遍使用InnoDB少办,可以參考
MyISAM與InnoDB兩者之間區(qū)別與選擇苞慢,詳細(xì)總結(jié),性能對(duì)比使用Spring MVC時(shí)英妓,加載順序不當(dāng)導(dǎo)致事務(wù)無(wú)效
spring的容器(applicationContext)和springMVC的(applicationContext)是不同的挽放。
spring容器加載得時(shí)候,優(yōu)先加載ServletContextListener(對(duì)應(yīng)spring.xml)產(chǎn)生的父容器蔓纠,而springMVC(對(duì)應(yīng)springMVC.xml)產(chǎn)生的是子容器辑畦。子容器Controller進(jìn)行掃描裝配時(shí)裝配的@Service注解的實(shí)例是沒(méi)有經(jīng)過(guò)事務(wù)加強(qiáng)處理,
即沒(méi)有事務(wù)處理能力的Service腿倚。而父容器進(jìn)行初始化的Service是保證事務(wù)的增強(qiáng)處理能力的纯出。如果不在子容器中將Service除去掉,此時(shí)得到的將是原樣的無(wú)事務(wù)處理能力的Service敷燎。
所以暂筝,我們應(yīng)把掃描Service的工作放在spring.xml中。讓Service和事務(wù)注解存在于同一個(gè)容器中懈叹,這樣配置的事務(wù)注解就能起作用了乖杠。
總結(jié)
總結(jié)一下:
- 本文闡述了事務(wù)的基本概念:是由一個(gè)有限的數(shù)據(jù)庫(kù)操作序列構(gòu)成在系統(tǒng)執(zhí)行過(guò)程中的一個(gè)邏輯單位
- 事務(wù)的四大特性(ACID):原子性(A),一致性(C)澄成,隔離性(I)胧洒,持久性(Durability)
- 事務(wù)的隔離級(jí)別:未提交讀(READ UNCOMMITTED),在提交讀(READ COMMITTED)墨状,在可重復(fù)讀(REPEATABLE READS)卫漫,可串行化(Serializable)
- 隔離級(jí)別解決對(duì)應(yīng)的問(wèn)題:臟讀,不可重復(fù)讀肾砂,幻影讀
- MySQL的事務(wù)類(lèi)型:扁平事務(wù)列赎,帶有保存節(jié)點(diǎn)的扁平事務(wù),鏈?zhǔn)聞?wù)镐确,嵌套事務(wù)包吝,分布式事務(wù)
- MySQL的事務(wù)控制:BEGIN, ROLLBACK, COMMIT以及SET AUTOCOMMIT兩種方式
- Spring對(duì)事務(wù)的管理饼煞,以及常見(jiàn)的一些問(wèn)題
謝謝閱讀,希望能對(duì)你有所幫助诗越。
思考題
留個(gè)思考題給大家砖瞧,思考一下數(shù)據(jù)庫(kù)的事務(wù)隔離機(jī)制的實(shí)現(xiàn)和多線程中的并發(fā)控制有何異同,我們有緣再聊聊這個(gè)問(wèn)題
引用:
以及其他一些博文的零碎引用嚷狞,深表感謝