1. 數(shù)據(jù)庫事務特征
1.1 ACID特性
事務 (Transaction) 是數(shù)據(jù)庫系統(tǒng)中一系列操作的一個邏輯單元煮岁,所有操作要么全部成功要么全部失敗
幅狮。
事務是區(qū)分文件存儲系統(tǒng)
與 Nosql
數(shù)據(jù)庫重要特性之一茂蚓,其存在的意義是為了保證即使在并發(fā)情況下也能掙錢的執(zhí)行crud操作蠢护。怎樣才算是正確的呢幻碱?這時提出了事務需要保證的四個特性即ACID
-
A:原子性(atomicity)
一個事務 (transaction) 中的所有操作,要么全部完成灌侣,要么全部不完成,不會結束在中間某個環(huán)節(jié)裂问。事務在執(zhí)行過程中發(fā)生錯誤侧啼,會被回滾 (Rollback) 到事務開始前的狀態(tài),就像這個事務從來沒有執(zhí)行過一樣堪簿。原子性表現(xiàn)為操作不能被分割痊乾。
C:一致性(consistency)
在事務開始之前和事務結束以后,數(shù)據(jù)庫的完整性沒有被破壞椭更。數(shù)據(jù)庫要一直處于一致的狀態(tài)哪审,事務開始前是一個一致狀態(tài),事務結束后是另一個一致狀態(tài)虑瀑,事務將數(shù)據(jù)庫從一個一致狀態(tài)轉移到另一個一致狀態(tài)
湿滓。I:隔離性(isolation)
數(shù)據(jù)庫允許多個并發(fā)事務同時對其數(shù)據(jù)進行讀寫和修改的能力畏腕,隔離性可以防止多個事務并發(fā)執(zhí)行時由于交叉執(zhí)行而導致數(shù)據(jù)的不一致
。事務隔離分為不同級別茉稠,包括讀未提交 (read uncommitted)描馅、讀已提交(read committed)、可重復讀 (repeatable read) 和串行化 (Serializable)而线。D:持久性(durability)
事務外理結束后铭污,對數(shù)據(jù)的修改就是永久的
,即便系統(tǒng)故障也不會丟失膀篮。
擴展
WAL原則
InnoDB 的 ARIES 三原則 Write Ahead Logging (WAL)
- 日志成功寫入后事務就不會丟失嘹狞,后續(xù)由checkpoint機制來保證磁盤物理文件與redo log達到一致性;
- 利用redo log來記錄變更后的數(shù)據(jù)誓竿,即redo里記錄事務數(shù)據(jù)變更后的值磅网;
- 利用undo log來記錄變更前的數(shù)據(jù),即undo里記錄事務數(shù)據(jù)變更前的值筷屡,用于回滾和其他事務多版本讀涧偷。
并發(fā)事務控制
-
單版本控制-鎖
鎖用獨占的方式來保證在只有一個版本的情況下事務之間相互隔離。在MySQL事務中毙死,鎖的實現(xiàn)與隔離級別有關系,在RR (Repeatable Read) 隔離級別下扼倘,MySQL為了解
決幻讀的問題确封,以犧牲并行度為代價,通過 Gap 鎖來防止數(shù)據(jù)的寫入再菊,而這種鎖爪喘,因為其并行度不夠,
沖突很多纠拔,經(jīng)常會引起死鎖”#現(xiàn)在流行的 Row 模式可以避免很多沖突甚至死鎖問題,所以推薦默認使用Row+
RC (Read Committed) 模式的隔離級別绿语,可以很大程度上提高數(shù)據(jù)庫的讀寫并行度秃症。
-
多版本控制 - MVCC
在數(shù)據(jù)庫中,為了實現(xiàn)高并發(fā)的數(shù)據(jù)訪問吕粹,對數(shù)據(jù)進待多版本處理种柑,并通過事務的可見性來保證事務能看到自己應該看到的數(shù)據(jù)版本。
MVCC最大的好處是讀不加鎖匹耕,讀寫不沖突
聚请。每一次對數(shù)據(jù)庫的修改,都會在 Undo 日志中記錄當前修改記錄的事務號及修改前數(shù)據(jù)狀態(tài)的存儲地址(即ROLL_PTR),以便在必要的時候可以回滾到老的數(shù)據(jù)版本驶赏。例如炸卑,一個讀事務查詢到當前記錄,而最新的事務還未提交煤傍,根據(jù)原子性盖文,讀事務看不到最新數(shù)據(jù)。
1.2 事務隔離級別
在高并發(fā)的情況下蚯姆,要完全保證其ACID特性是非常困難的五续,除非把所有的事務串行化執(zhí)行,但帶來的負面的影響將是性能大打折扣龄恋。很多時候我們有些業(yè)務對事務的要求是不一樣的疙驾,所以數(shù)據(jù)庫中設計了四種隔離級別,供用戶基于業(yè)務進行選擇郭毕。
數(shù)據(jù)庫默認隔離級別:
- Oracle中默認級別:Read committed
- MySQL中默認級別:Repeatable read(在數(shù)據(jù)庫內(nèi)均表現(xiàn)為大寫)
# 查看 mysql 的默認隔離級別
SELECT @@tx_isolation
# 設置為讀未提交
SET tx_isolation='read-uncommitted';
# 設置為讀已提交
SET tx_isolation='read-committed';
# 設置為可重復讀
SET tx_isolation='REPEATABLE-READ';
# 設置為串行化
SET tx_isolation='SERIALIZABLE'
隔離級別 | 臟讀(Dirty Read) | 不可重復讀(NonRepeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
未提交讀(Read uncommitted) | 可能 | 可能 | 可能 |
已提交讀(Read committed) | 不可能 | 可能 | 可能 |
可重復讀(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
臟讀
一個事務讀取到另一個事務未提交的更新數(shù)據(jù)
session_1
# 設置為讀未提交
SET tx_isolation='read-uncommitted';
BEGIN;
INSERT INTO `tbl_employee` (`last_name`,`email`, `gender`)
VALUES ( '2222', '111111@qq.com', '1' );
# 1. 執(zhí)行到此處它碎,執(zhí)行session_2
ROLLBACK;
# 3. 執(zhí)行到此處,再執(zhí)行session_2
COMMIT; -- 無用
session_2
# 設置為讀未提交
SET tx_isolation='read-uncommitted';
SELECT * FROM `tbl_employee`;
# 2. 執(zhí)行完成显押,session_1 事務未提交扳肛,卻讀出了數(shù)據(jù);回到session_1執(zhí)行回滾
# 4. 此時查詢不到數(shù)據(jù)煮落,說明第2步出現(xiàn)臟讀
不可重復讀
在同一事務中敞峭,多次讀取同一數(shù)據(jù)返回的結果有所不同,換句話說蝉仇,后續(xù)讀取可以讀到另一事務已提交的更新數(shù)據(jù)。相反殖蚕,“可重復讀”在同一事務中多次讀取數(shù)據(jù)時轿衔,能夠保證所讀數(shù)據(jù)一樣,也就是后續(xù)讀取不能讀到另一事務已提交的更新數(shù)據(jù)睦疫。
事務B修改數(shù)據(jù)導致當前事務A前后讀取數(shù)據(jù)不一致害驹,側重點在于事務B的修改
當前事務讀到了其他事務修改的數(shù)據(jù)
session_1
# 設置為讀已提交
SET tx_isolation='read-committed';
BEGIN;
SELECT * FROM `tbl_employee`;
# 1. 執(zhí)行session_2中內(nèi)容
SELECT * FROM `tbl_employee`;
# 3. 和前面查詢的數(shù)據(jù)不一致
COMMIT;
session_2
# 設置為讀已提交
SET tx_isolation='read-committed';
UPDATE `tbl_employee` SET email = "222222@qq.com" WHERE id = 5;
# 2. 執(zhí)行session_1中內(nèi)容
幻讀
查詢表中一條數(shù)據(jù),如果不存在就插入一條蛤育,并發(fā)的時候卻發(fā)現(xiàn)宛官,里面居然有兩條相同的數(shù)據(jù)。
事務A修改表中數(shù)據(jù)瓦糕,此時事務B插入一條新數(shù)據(jù)底洗,事務A查詢發(fā)現(xiàn)表中還有沒修改的數(shù)據(jù),像是出現(xiàn)幻覺
事務A讀到了事務B新增的數(shù)據(jù)咕娄,導致結果不一致亥揖,側重點在于事務B新增數(shù)據(jù)
session_1
# 設置為可重復讀
SET tx_isolation='REPEATABLE-READ';
BEGIN;
SELECT * FROM `tbl_employee` WHERE last_name = "jack";
# 1. 此時在session_2事務插入數(shù)據(jù)
SELECT * FROM `tbl_employee` WHERE last_name = "jack";
INSERT INTO `tbl_employee` (`last_name`,`email`, `gender`)
VALUES ( 'jack', '333333333@qq.com', '1' );
SELECT * FROM `tbl_employee` WHERE last_name = "jack";
UPDATE `tbl_employee` SET email = "444444444@qq.com" WHERE last_name = "jack";
SELECT * FROM `tbl_employee` WHERE last_name = "jack";
COMMIT;
session_2
# 設置為可重復讀
SET tx_isolation='REPEATABLE-READ';
INSERT INTO `tbl_employee` (`last_name`,`email`, `gender`)
VALUES ( 'jack', '1111111@qq.com', '1' );
2. Spring事務應用及源碼分析
2.1 Spring事務相關API
Spring 事務是在數(shù)據(jù)庫事務的基礎上進行封裝擴展,其主要特性如下:
- 支持原有的數(shù)據(jù)庫事務的隔離級別,加入了
事務傳播
的概念 - 提供多個事務的合并或隔離的功能
- 提供聲明式事務费变,讓業(yè)務代碼與事務分離摧扇,事務變得更易用(AOP)
Spring 提供了事務相關接口:
-
TransactionDefinition
事務定義:事務的隔離級別和事務的傳播行為
-
TransactionAttribute
事務屬性,實現(xiàn)了對回滾規(guī)則的擴展(外理異常)
-
PlatformTransactionManager
事務管理器
-
TransactionStatus
事務運行時狀態(tài)
相關實現(xiàn)類
-
TransactionIntercenter
事務攔截器挚歧,實現(xiàn)了 MethodInterceptor
-
TransactionAspectSupport
事務切面支持扛稽,內(nèi)部類Transactionlnfo封裝了事務相關屬性
TransactionAspectSupport.Transactionlnfo
2.2 編程式事務
public class SpringTransactionExample {
private static String url = "jdbc:mysql://127.0.0.1:3306/test";
private static String user = "root";
private static String password = "root";
public static void main(string[] args) {
// 獲取數(shù)據(jù)源
final Datasource ds = new DriverManagerDatasource(url, user, password);
//編程式事務
final TransactionTemplate template = new TransactionTemplate();
// 設置事務管理器
template.setTransactionManager(new DataSourceTransactionManager(ds));
template.execute(new TransactionCa11back<0bject>() {
@override
public object doInTransaction(Transactionstatus status) {
Connection conn = DatasourceUtils.getConnection(ds);
object savePoint = null;
try {
{
// 插入
PreparedStatement prepare = conn.preparestatement("insert INTO account (accountName,user,money) VALUES (?, ?, ?)");
preparp.setstring(1, "111");
prepare.setstring(2, "aaa");
prepare.setInt(3, 10000);
prepare.executeUpdate():;
}
//設置保存點
savepoint = status.createSavepoint();
{
//捕入
PreparedStatement prepare = conn.preparestatement("insert INTO account (accountName.user,money) VALUES (?, ?, ?)");
prepare.setstring(1,"222");
prepare.setstring(2, "bbb");
prepare.setInt(3, 10000);
prepare.executeupdate() ;
}
{
//更新
Preparedstatement prepare = conn.preparestatement("UPDATE account SET money= money+100 where user=?");
prepare.setstring(1, "aaa");
prepare.executeUpdate();
//int i=1/0;
}
} catch (SQLException e) {
e.printstackTrace();
} catch (Exception e) {
System.out.print1n("更新失敗");
if (savePoint != null) [
status.ro11backTosavepoint(savePoint);
} else {
status.setRollbackOnly();
}
}
return null;
}
});
}
2.3 聲明式事務
Xml配置
添加tx名字空間
xmlns:tx="http://www.springframework.org/schema/tx"
配置事務管理器和數(shù)據(jù)源
<bean id="datasource"
class="org.springframework.jdbc.datasource.DriverManagerDatasource">
<constructor-arg name="ur1" value="jdbc:mysq1://127 .0.0.1/test"/>
<constructor-arg name="username" va]ue="root"/>
<constructor-arg name="password" value="root"/>
</bean>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="datasource" ref="datasource"/>
</bean>
事務切面配置
<!-- 事物切面配置 -->
<tx:advice id="advice" transaction-manager="txManager"
<tx:attributes>
<tx:method name="update*" propagation-"REQUIRED" read-only="false" rollback-for="Java.1ang.Exception"/>
<tx:method name="add*" propagation="REQUIRED" read-only="false"/>
</tx:attributess>
</tx:advice>
<!-- 事物切入點 -->
<aop:config>
<aop:pointcut expression="execution(* bat.ke.qq.com.service.*.*(..))" id="txPointCut"/>
<aop:advisor advice-ref="advice" pointcut-ref="txPointCut"/>
</aop:config>
@Transactional
<!-- 開啟事務控制的注解支持 -->
<tx:annotation-driven transaction-manager="txManager"/>
事務主機配置,作用于類滑负,方法上
屬性名 | 說明 |
---|---|
name | 當在配置文件中有多個TransactionManager在张,可以用該屬性指定選擇哪個事務管理器 |
propagation | 事務的傳播行為,默認值為REOUIRED |
isolation | 事務的隔離度橙困,默認值采用DEFAULT |
timeout | 事務的超時時間瞧掺,默認值為-1。如果超過該時間限制但事務還沒有完成凡傅,則自動回滾事務 |
read-only | 指定事務是否為只讀事務辟狈,默認值為false;為了忽略那些不需要事務的方法夏跷,比如讀取數(shù) 據(jù)哼转,可以設置 read-only 為 true。 |
rollback-for | 用于指定能夠觸發(fā)事務回滾的異常類型槽华,如果有多個異常類型需要指定壹蔓,各類型之間可以通 過逗號分隔 |
no-rollback-for | 拋出 no-rollback-for 指定的異常類型,不回滾事務 |
Java Configuration
@EnableTransactionManagement
利用 TransactionManagementConfigurationSelector 向容器中注冊兩個組件
-
AutoProxyRegistrar
給容器中注冊一個 InfrastructureAdvisorAutoProxyCreator 的后置處理器猫态,返回一個代理對象 (增強器)佣蓉,代理對象執(zhí)行方法利用攔截器鏈進行調用
-
ProxyTransactionManagementConfiguration 是一個 @Configuration
- 給容器中注冊事務增強器 transactionAcvisor
- AnnotationTransactionAttributesource 解析事務注解
- 事務欄截器 transactionInterceptor
Spring的事務傳播機制
多個事務方法相互調用時,事務如何在這些方法之間進行傳播亲雪,Spring中提供了 `7種` 不同的傳播特性勇凭,來保證事務的正常執(zhí)行
- REQUIRED:默認的傳播特性,如果當前沒有事務义辕,則新建一個事務虾标,如果當前存在事務,則加入這個事務
- SUPPORTS:當前存在事務灌砖,則加入當前事務璧函,如果當前沒有事務,則以非事務的方式執(zhí)行
- MANDATORY:當前存在事務基显,則加入當前事務蘸吓,如果當前事務不存在,則拋出異常
- REQUIRED_NEW :創(chuàng)建一個新事務续镇,如果存在當前事務美澳,則掛起該事務
- NOT_SUPPORTED:以非事務方式執(zhí)行,如果存在當前事務,則掛起當前事務
- NEVER:不使用事務制跟,如果當前事務存在舅桩,則拋出異常
- NESTED:如果當前事務存在,則在嵌套事務中執(zhí)行雨膨,否則REQUIRED的操作一樣
NESTED 和 REQUIRED_NEW 的區(qū)別:
REQUIRED_NEW 是新建一個事務并且新開始的這個事務與原有事務無關擂涛,而 NESTED 則是當前存在事務時會開啟一個嵌套事務,在 NESTED 情況下聊记,父事務回滾時撒妈,子事務也會回滾,而 REQUIRED_NEW 情況下排监,原有事務回滾狰右,不會影響新開啟的事務
NESTED 和 REQUIRED 的區(qū)別:
REQUIRED 情況下,調用方存在事務時舆床,則被調用方和調用方使用同一個事務棋蚌,那么被調用方出現(xiàn)異常時,由于共用一個事務挨队,所以無論是否 catch異常谷暮,事務都會回滾,而在 NESTED 情況下盛垦,被調用方發(fā)生異常時湿弦,調用方可以 catch其異常,這樣只有子事務回滾腾夯,父事務不會回滾
Spring事務的實現(xiàn)原理
在使用Spring框架的時候颊埃,可以有兩種事務的實現(xiàn)方式,一種是編程式事務蝶俱,有用內(nèi)自己通過代碼來撐制事務的處理邏輯竟秫,還有一種是聲明式事務,通過 `@Transactional` 注解來實現(xiàn)跷乐。
其實事務的操作本來應該是由數(shù)據(jù)庫來進行控制,但是為了方便用戶進行業(yè)務邏輯的操作趾浅,Spring對事務功能進行了擴展實現(xiàn)愕提,一般我們很少會用編程式事務,更多的是通過添加 @Transactional 注解來進行實現(xiàn)皿哨,當添加此注解之后事務的自動功能就會關閉浅侨,有Spring框架來幫助進行控制。
其實事務操作是AOP的一個核心體現(xiàn)证膨,當一個方法添加 @Transactional 注解之后如输,Spring會基于這個類生成個代理對象,會將這個代理對象作為bean,當使用這個代理對象的方法的時候不见,如果有事務處理澳化,那么會先把事務的自動提交給關系,然后去執(zhí)行具體的業(yè)務邏輯稳吮,如果執(zhí)行邏輯沒有出現(xiàn)異常缎谷,那么代理邏輯就會直接提交如果出現(xiàn)任何異常情況,那么直接進行回滾操作灶似,當然用戶可以控制對哪些異常進行回滾操作列林。
Spring事務失效場景
bean對象沒有被Spring容器管理
方法的訪間修飾符不是public
切點是否配置正確
-
自身調用問題
因為this不是代理對象,可以配置
expose-proxy="true"
酪惭,就可以通過AopContext.currentProx()
獲取
到當前類的代理對象<!-- expose-proxy="true" 類內(nèi)部可以獲取到當前類的代理對象 --> <aop:aspectj-autoproxy expose-proxy="true"/>
@EnableAspectJAutoProxy(exposeProxy = true)
也可以注入當前bean
數(shù)據(jù)源沒有配置事務管理器
數(shù)據(jù)庫不支持事務
導常被捕獲
-
異常類型錯誤或者配置錯誤
默認只支持RuntimeException 和 Error希痴,不支持檢查異常
想要支持檢查一異常需配置rollbackFor
@Transactional(rollbackFor = Exception.class)