您好,登錄后才能下訂單哦!
小編給大家分享一下MySQL中本地事務的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
事務是邏輯上的一組操作,要么都執行,要么都不執行,一榮俱榮,一損俱損。數據庫事務有嚴格的定義,必須滿足4個特性。
原子性:事務是最小的執行單位,不允許分割。事務的原子性確保動作要么全部完成,要么完全不起作用;
一致性:執行事務前后,數據保持一致,多個事務對同一個數據讀取的結果是相同的,事務操作成功前后,數據庫所處的狀態和它的業務規則是一致的。在這些事務特性中,數據一致性是最終目標,其它特性都是為達到這個目標采取的措施、要求或者手段。
ACID中的一致性和CAP中的一致性有什么區別?
二者完全不是一個事情,數據庫對于 ACID 中的一致性的定義是這樣的:如果一個事務原子地在一個一致地數據庫中獨立運行,那么在它執行之后,數據庫的狀態一定是一致的。對于這個概念,它的第一層意思就是對于數據完整性的約束,包括主鍵約束、引用約束以及一些約束檢查等等,在事務的執行的前后以及過程中不會違背對數據完整性的約束,所有對數據庫寫入的操作都應該是合法的,并不能產生不合法的數據狀態。而第二層意思其實是指邏輯上的對于開發者的要求,我們要在代碼中寫出正確的事務邏輯,比如銀行轉賬,事務中的邏輯不可能只扣錢或者只加錢,這是應用層面上對于數據庫一致性的要求。即,數據庫 ACID 中的一致性對事務的要求不止包含對數據完整性以及合法性的檢查,還包含應用層面邏輯的正確。
CAP 定理中的數據一致性,其實是說分布式系統中的各個節點中對于同一數據的拷貝有著相同的值。
隔離性:并發訪問數據庫時,一個用戶的事務不被其他事務所干擾,各并發事務之間數據庫是獨立的,采用數據庫鎖機制來保證事務的隔離性;
持久性:一個事務被提交之后。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響。
在典型的應用程序中,多個事務并發運行,經常會操作相同的數據來完成各自的任務(多個用戶對統一數據進行操作)。并發雖然是必須的,但可能會導致以下的問題:
臟讀(Dirtyread):當一個事務正在訪問數據并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是“臟數據”,依據“臟數據”所做的操作可能是不正確的。
丟失修改(Losttomodify):指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那么在第一個事務中修改了這個數據后,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱為丟失修改。例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
不可重復讀(Unrepeatableread):指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那么,在第一個事務中的兩次讀數據之間,由于第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱為不可重復讀。
幻讀(Phantomread):幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接著另一個并發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。
不可重復度和幻讀區別:不可重復讀的重點是修改,幻讀的重點在于新增或者刪除。例1(同樣的條件,你讀取過的數據,再次讀取出來發現值不一樣了):事務1中的A先生讀取自己的工資為1000的操作還沒完成,事務2中的B先生就修改了A的工資為2000,導致A再讀自己的工資時工資變為2000;這就是不可重復讀。
例2(同樣的條件,第1次和第2次讀出來的記錄數不一樣):假某工資單表中工資大于3000的有4人,事務1讀取了所有工資大于3000的人,共查到4條記錄,這時事務2又插入了一條工資大于3000的記錄,事務1再次讀取時查到的記錄就變為了5條,這樣就導致了幻讀。
SQL標準定義了四個隔離級別,隔離性和一致性其實是一個需要開發者去權衡的問題,為數據庫提供什么樣的隔離性層級也就決定了數據庫的性能以及可以達到什么樣的一致性。
READ-UNCOMMITTED(讀取未提交):最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀。
READ-COMMITTED(讀取已提交):允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生。(MySQL可重復讀隔離級別的實現原理)
REPEATABLE-READ(可重復讀):對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生。
SERIALIZABLE(可串行化):最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。
隔離級別 | 臟讀 | 不可重復讀 | 幻影讀 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
表-數據并發問題在不同隔離級別的出現
這里需要注意的是:與SQL標準不同的地方在于InnoDB存儲引擎在REPEATABLE-READ(可重讀)事務隔離級別下使用的是Next-KeyLock鎖算法,因此可以避免幻讀的產生,這與其他數據庫系統(如SQLServer)是不同的。所以說InnoDB存儲引擎的默認支持的隔離級別是REPEATABLE-READ(可重讀)已經可以完全保證事務的隔離性要求,即達到了SQL標準的SERIALIZABLE(可串行化)隔離級別。因為隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀取提交內容):,但是你要知道的是InnoDB存儲引擎默認使用REPEATABLE-READ(可重讀)并不會有任何性能損失。
InnoDB存儲引擎在分布式事務的情況下一般會用到SERIALIZABLE(可串行化)隔離級別。
事務的原子性和持久性是由事務日志(transaction log)保證的,回滾日志用于對事務的影響進行撤銷,重做日志在錯誤處理時對已經提交的事務進行重做。它們能保證兩點:
發生錯誤或者需要回滾的事務能夠成功回滾(原子性);
在事務提交后,數據沒來得及寫會磁盤就宕機時,在下次重新啟動后能夠成功恢復數據(持久性);
UndoLog的原理很簡單,為了滿足事務的原子性,在操作任何數據之前,首先將數據備份到一個地方(這個存儲數據備份的地方稱為UndoLog)。然后進行數據的修改。如果出現了錯誤或者用戶執行了ROLLBACK語句,系統可以利用Undo Log中的備份將數據恢復到事務開始之前的狀態,UndoLog并不能將數據庫物理地恢復到執行語句或者事務之前的樣子;它是邏輯日志,當回滾日志被使用時,它只會按照日志邏輯地將數據庫中的修改撤銷掉看,可以理解為我們在事務中使用的每一條INSERT都對應了一條DELETE,每一條UPDATE也都對應一條相反的UPDATE語句。
RedoLog記錄的是新數據的備份, 保障的是事務的持久性和一致性。在事務提交前,只要將RedoLog持久化即可,不需要將數據持久化。當系統崩潰時,雖然數據沒有持久化,但是RedoLog已經持久化。系統可以根據RedoLog的內容,將所有數據恢復到最新的狀態。 (深入分析可以參考面向信仰編程-淺入深出MySQL中事務的實現-回滾日志、重做日志)
redo log和undo log的過程分析
eg : 假設有2個數值,分別為A和B,值為1,2
1 start transaction;
2 記錄 A=1 到undo log;
3 update A = 3;
4 記錄 A=3 到redo log;
5 記錄 B=2 到undo log;
6 update B = 4;
7 記錄B = 4 到redo log;
8 將redo log刷新到磁盤
9 commit
在1-8的任意一步系統宕機,事務未提交,該事務就不會對磁盤上的數據做任何影響.
如果在8-9之間宕機,恢復之后可以選擇回滾,也可以選擇繼續完成事務提交,因為此時redo log已經持久化
若在9之后系統宕機,內存映射中變更的數據還來不及刷回磁盤,那么系統恢復之后,可以根據redo log把數據刷回磁盤
數據庫對于隔離級別的實現就是使用并發控制機制對在同一時間執行的事務進行控制,限制不同的事務對于同一資源的訪問和更新,而最重要也最常見的并發控制機制,主要有鎖、時間戳(即樂觀鎖,并不是真正的鎖機制,而是一種思想)、多版本MVCC。
Spring為事務管理提供了一致的編程模板,在高層建立了統一的事務抽象。也就是說,不管是選擇Spring JDBC、Hibernate、JPA還是選擇MyBatis,Spring都可以讓用戶使用統一的編程模型進行事務管理。
Spring為事務管理提供了一致的編程模板,在高層次建立了統一的事務抽象。像Spring DAO為不同的持久化技術實現提供模板類一樣,Spring事務管理繼承了這一風格,也提供了事務模板類TransactionTemplate。通過TransactionTemplate并配合使用事務回調TransactionCallback指定具體的持久化操作就可以通過編程方式實現事務管理,而無須關注資源獲取、復用、釋放、事務同步和異常處理的操作。
在Spring事務管理SPI的抽象層主要包括3個接口,分別是PlatformTransactionManager、TransactionDefinition和TransactionStatus,它們位于org.springframework.transaction包中。3者關系如圖:
圖-Spring事務管理SPI抽象
其中,TransactionDefinition用于描述事務的隔離級別,超時時間、是否為只讀事務和事務傳播規則等控制事務具體行為的事務屬性。這些事務屬性可以通過XML配置、注解描述或手工編程的方式設置。PlatformTransactionManager根據TransactionDefinition提供的事務屬性配置信息創建事務,并用TransactionStatus描述這個激活事務的狀態。
TransactionDefinition
事務隔離:TransactionDefinition使用了java.sql.Connection接口中同名的4個隔離級別,此外,TransactionDefinition還定義了一個默認的隔離級別,它表示使用底層數據庫的默認隔離級別。
事務傳播:通常在一個事務中執行的所有代碼都會同一事務的上下文中。但是Spring也提供了幾個可選的事務傳播類型,例如簡單地參與到現有的事務中,或者掛起當前的事務,創建一個新事務。
事務超時:事務在超時前能運行多久,超過時間后,事務被回滾。有些事務管理器不支持事務過期的功能,這時如果設置TIMEOUT_DEFAULT等值時將拋出異常。
只讀狀態:只讀事務不修改任何數據,主要用于優化,如果更改數據就會拋出異常。
spring允許通過XML或者注解元數據的方式為一個有事務要求的服務類方法配置事務屬性,這些信息作為Spring事務管理框架的輸入,Spring將自動按照事務屬性信息的指示,為目標方法提供相應的事務支持。
TransactionStatus
TransactionStatus代表一個事務的具體運行狀態,事務管理器通過該接口獲取事務的運行期狀態信息,也可以通過該接口間接地回滾事務,它相比于在拋出異常時回滾事務的方式更具有可控性。
PlatformTransactionManager
PlatformTransactionManager是事務的最高層抽象,它提供了3個接口方法:
TransactionStatus getTransaction(TransactionDefinition definition):該方法根據事務定義信息從事務環境中返回一個已存在的事務,或者創建一個新的事務,并用TransactionStatus描述這個事務的狀態。
commit(TransactionStatus status):根據事務的狀態提交事務,如果事務狀態已經被標識為rollback-only,該方法將執行一個回滾事務的操作。
rollback(TransactionStatus status):回滾事務,當提交事務拋出異常時,回滾會被隱式執行。
Spring將事務管理委托給底層具體的持久化實現框架完成,因此Spring為不同的持久化框架提供了PlatformTransactionManager接口的實現類,如下圖:
圖-不同持久化技術對應的事務管理器實現類
這些事務管理器都是對特定事務實現框架的代理,這樣我們就可以通過spring的高級抽象,對不同種類的事務實現使用相同的方式進行管理,而不用關心具體的實現。要實現事務管理,首先要在Spring中配置好相應的事務管理器,為事務管理器指定數據資源及一些其它事務管理控制屬性。
Spring將JDBC的Connection、Hibernate的Session等訪問數據庫的連接或會話對象統稱為資源。這些資源在同一時刻是不能多線程共享的,為了讓DAO、Service能做到singleton,Spring的事務同步管理器類org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal為不同事務線程提供了獨立的資源副本,同時維護事務配置的屬性和運行狀態信息。
在一個service接口中可能會調用另一個service接口的方法,以共同完成一個完整的業務操作,Spring通過事務傳播行為控制當前的事務如何傳播到被嵌套調用的目標服務接口方法中。Spring在TransactionDefinition接口中規定了7種類型的事務傳播行為,如下圖
圖-事務傳播行為類型
Spring為編程式事務管理提供了模板類org.springframework.transaction.support.TransactionTemplate,和那些持久化模板類一樣,TransactionTemplate也是線程安全的。TransactionTemplate有2個重要的方法:
// 設置事務管理器 void setTransactionManager(PlatformTransactionManager transactionManager) // 在TransactionCallback回調接口中定義需要以事務的方式組織的數據訪問邏輯 Object execute(TransactionCallback action) TransactionCallback接口只有一個方法:Object doInTransaction(TransactionStatus status)。如果操作不會返回結果,可以使用TransactionCallback的子接口TransactionCallbackWithoutResult。
Spring Boot中使用@Transactional注解配置事務管理
是否用了Spring,就一定要用Spring事務管理器,否則就無法進行數據的持久化操作呢?答案是否定的,脫離了事務性,DAO照樣可以順利地進行數據操作。對于強調速度的應用,數據庫本身可能就不支持事務,如MyISAM引擎的數據庫,這是無需配置事務管理器,即使配置了,也是沒有實際用處的。
將面向接口編程奉為圭臬,過分強制面向接口編程除了會帶來更多的的類文件,并不會有什么好處。Spring事務管理支持在Controller層直接添加事務注解并生效,因此事務管理并不一定強制應用必須嚴格分層,可以根據實際應用出發,根據實際需要進行編程。
除了事務的傳播行為,對于事務的其它特性,Spring是借助底層資源的功能來完成的,無非充當了一個代理的角色。但是Spring事務的傳播行為卻是Spring憑借自身的框架提供的功能。
不同服務方法之間的嵌套調用
例如對于調用鏈Service1#method1()->Service2#method2()->Service3#method3(),那么這三個方法通過Spring的事務傳播機制都可以工作在同一個事務中。
同一個服務不同方法的嵌套調用(自我調用)
首先調用的是AOP代理對象而不是目標對象,首先執行事務切面,事務切面內部通過TransactionInterceptor環繞增強進行事務的增強,即進入目標方法之前開啟事務,退出目標方法時提交/回滾事務。目標對象內部的自我調用將無法實施切面中的增強。
public interface AService { public void a(); public void b(); } @Service() public class AServiceImpl1 implements AService{ @Transactional(propagation = Propagation.REQUIRED) public void a() { // 此處的this指向目標對象,因此調用this.b()將不會執行b事務切面,即不會執行事務增強(通過日志可以觀察到) this.b(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { } }
如何解決?
方法一:調用AOP代理對象的b方法即可執行事務切面進行事務增強
需要開啟暴露Aop代理到ThreadLocal支持,并將this.b()修改為((AService) AopContext.currentProxy()).b();
這種通過ThreadLocal暴露Aop代理對象適合解決所有場景(不管是singleton Bean還是prototype Bean)的AOP代理獲取問題(即能解決目標對象的自我調用問題);
方法二:通過初始化方法在目標對象中注入代理對象
這種方式不是很靈活,所有需要自我調用的實現類必須重復實現代碼
@Service public class AServiceImpl3 implements AService{ @Autowired //① 注入上下文 private ApplicationContext context; private AService proxySelf; //② 表示代理對象,不是目標對象 @PostConstruct //③ 初始化方法 private void setSelf() { //從上下文獲取代理對象(如果通過proxtSelf=this是不對的,this是目標對象) //此種方法不適合于prototype Bean,因為每次getBean返回一個新的Bean proxySelf = context.getBean(AService.class); } @Transactional(propagation = Propagation.REQUIRED) public void a() { proxySelf.b(); //④ 調用代理對象的方法 這樣可以執行事務切面 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { } }
方法三:通過BeanPostProcessor在目標對象中注入代理對象
需要注意到循環依賴和非singleton bean的影響,以下方式能解決singleton之間的循環依賴問題,但是不能解決循環依賴中包含prototype Bean的自我調用問題。
// 即我們自定義的BeanPostProcessor (InjectBeanSelfProcessor) // 如果發現我們的Bean是實現了該標識接口就調用setSelf注入代理對象。 public interface BeanSelfAware { void setSelf(Object proxyBean); } @Component public class InjectBeanSelfProcessor implements BeanPostProcessor, ApplicationContextAware { private ApplicationContext context; //① 注入ApplicationContext public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(!(bean instanceof BeanSelfAware)) { //② 如果Bean沒有實現BeanSelfAware標識接口 跳過 return bean; } if(AopUtils.isAopProxy(bean)) { //③ 如果當前對象是AOP代理對象,直接注入 ((BeanSelfAware) bean).setSelf(bean); } else { //④ 如果當前對象不是AOP代理,則通過context.getBean(beanName)獲取代理對象并注入 //此種方式不適合解決prototype Bean的代理對象注入 ((BeanSelfAware)bean).setSelf(context.getBean(beanName)); } return bean; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } }
擴展:靈活使用事務注解并非一定需要接口
如果在一個業務方法中包含查詢、修改等邏輯,但其實只有這些修改操作需要實施業務增強,這是應該怎么處理?可以讓改service實現BeanSelfAware接口,然后在service注入代理對象,通過代理對象
@Service public class AServiceImpl1 implements AService,BeanSelfAware{ private AServiceImpl1 self; @Override public void setSelf(AServiceImpl1 proxy) { this.self = proxy; } @Override public void a() { // ... 其它查詢等無需事務邏輯 // 需要事務增強的修改邏輯 self.m(); } @Transactional public void m() { } }
簡書-Spring中的Bean是線程安全的嗎?
在相同線程中進行相互的嵌套調用的事務方法工作在相同的事務中,如果這些相互嵌套調用的方法工作在不同線程中,則不同的線程下的事務方法工作在獨立的事務中。
如果用戶采用了一種高端的ORM技術(Hibernate、JPA、JDO),同時還采用了一種JDBC技術(Spring JDBC、Mybatis),由于前者的會話Session是對后者連接Connection的封裝,Spring會足夠智能地在同一個事務線程中讓前者的會話封裝后者的連接,所以只要只要直接采用前者前者的事務管理器就可以了。
圖-混合數據訪問技術所對應的事務管理器
由于Spring事務管理是基于接口代理或者動態字節碼技術,通過AOP實施事務增強的,雖然Spring依然支持AspectJ在類的加載期間實施增強,但這種方法很少使用,這里不做討論。
對于基于接口動態代理的AOP事務增強來說,由于接口的方法都必須是public的,這就要求實現類的實現方法也必須是public的(不能是protected、private)的,同時不能使用static修飾符。所以,可以實施接口動態代理的方法只能是public或public final修飾符的方法,其它方法都不能被動態代理,相應地也就不能實施AOP增強,換句話說即不能進行Spring事務的增強。
基于CGLib字節碼動態代理的方案是通過擴展被增強類,動態創建起子類的方式進行AOP增強植入的,由于使用final、static、private修飾符的方法都不能被子類覆蓋,相應地這些方法無法實施AOP增強。所以方法簽名必須特別注意這些修飾符的使用,以免成為事務管理的漏網之魚。
需要注意的是,我們說這些方法不能被Spring進行AOP事務增強,是指這些方法不能啟動事務,但是外層方法的事務上下文依舊可以順利地傳播到這些方法中。這些不能被事務增強的方法和可被事務增強的方法的唯一區別在于“是否可以主動開啟一個新事務”,前者可以而后者不可以,對于事務傳播行為來說,二者是完全相同的。
圖-Spring事務管理漏網之魚
以上是“MySQL中本地事務的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。