<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺談Spring的事務(wù)隔離級(jí)別與傳播行為

          共 4509字,需瀏覽 10分鐘

           ·

          2020-11-27 17:43

          ? ? ??

          這篇文章以一個(gè)問題開始,如果你知道答案的話就可以跳過不看啦@(o???)@

          Q:在一個(gè)批量任務(wù)執(zhí)行的過程中,調(diào)用多個(gè)子任務(wù)時(shí),如果有一些子任務(wù)發(fā)生異常,只是回滾那些出現(xiàn)異常的任務(wù),而不是整個(gè)批量任務(wù),請問在Spring中事務(wù)需要如何配置才能實(shí)現(xiàn)這一功能呢?

          隔離級(jí)別

          隔離性(Isolation)作為事務(wù)特性的一個(gè)關(guān)鍵特性,它要求每個(gè)讀寫事務(wù)的對象對其他事務(wù)的操作對象能相互分離,即該事務(wù)提交前對其他事務(wù)都不可見,在數(shù)據(jù)庫層面都是使用鎖來實(shí)現(xiàn)。

          事務(wù)的隔離級(jí)別從低到高有以下四種:

          • READ UNCOMMITTED(未提交讀):這是最低的隔離級(jí)別,其含義是允許一個(gè)事務(wù)讀取另外一個(gè)事務(wù)沒有提交的數(shù)據(jù)。READ UNCOMMITTED是一種危險(xiǎn)的隔離級(jí)別,在實(shí)際開發(fā)中基本不會(huì)使用,主要是由于它會(huì)帶來臟讀問題。

          時(shí)間事務(wù)1事務(wù)2備注
          1讀取庫存為100------
          2扣減庫層50---剩余50
          3
          扣減庫層50---
          4
          提交事務(wù)庫存保存為0
          5回滾事務(wù)---事務(wù)回滾為0

          臟讀對于要求數(shù)據(jù)一致性的應(yīng)用來說是致命的,目前主流的數(shù)據(jù)庫的隔離級(jí)別都不會(huì)設(shè)置成READ UNCOMMITTED。不過臟讀雖然看起來毫無用處,但是它主要優(yōu)點(diǎn)是并發(fā)能力高,適合那些對數(shù)據(jù)一致性沒有要求而追求高并發(fā)的場景。

          • READ COMMITTED(讀寫提交): 它是指一個(gè)事務(wù)只能讀取另外一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù),不能讀取未提交的數(shù)據(jù)。READ COMMITTED會(huì)帶來不可重復(fù)讀的問題:


          時(shí)間事務(wù)1事務(wù)2備注
          1讀取庫存為1

          2扣減庫存
          事務(wù)未提交
          3
          讀取庫存為1
          4提交事務(wù)
          庫存變成0
          5
          扣減庫存庫存為0,無法扣減

          不可重復(fù)讀和臟讀的區(qū)別是:臟讀讀取到的是未提交的數(shù)據(jù),而不可重復(fù)讀讀到的確實(shí)已經(jīng)提交的數(shù)據(jù),但是違反了數(shù)據(jù)庫事務(wù)一致性的要求。

          一般來說,不可重復(fù)讀的問題是可以接受的,因?yàn)槠渥x到的是已經(jīng)提交的數(shù)據(jù),本身并不會(huì)帶來很大的問題。因此,很多數(shù)據(jù)庫如(ORACLE,SQL SERVER)將其默認(rèn)隔離級(jí)別設(shè)置為READ COMMITTED,允許不可重復(fù)讀的現(xiàn)象。

          • REPEATABLE READ (可重復(fù)讀):可重復(fù)讀的目標(biāo)是為了克服READ COMMITED中出現(xiàn)的不可重復(fù)讀,它指在同一個(gè)事務(wù)內(nèi)的查詢都是與事務(wù)開始時(shí)刻一致,以上表為例,在REPEATABLE READ隔離級(jí)別下它會(huì)發(fā)生如下變化:

          時(shí)間事務(wù)1事務(wù)2
          備注
          1讀取庫存為1

          2扣減庫存
          事務(wù)未提交
          3
          讀取庫存不允許讀取,等待事務(wù)1提交
          4提交事務(wù)
          庫存變成0
          5
          讀取庫存庫存為0,無法扣減

          REPEATABLE READ雖然解決了不可重復(fù)讀問題,但是他又會(huì)帶來幻讀問題,幻讀是指,在一個(gè)事務(wù)中,第一次查詢某條記錄,發(fā)現(xiàn)沒有,但是,當(dāng)試圖更新這條不存在的記錄時(shí),竟然能成功,并且,再次讀取同一條記錄,它就神奇地出現(xiàn)了。

          時(shí)間事務(wù)A事務(wù)2備注
          1beginbegin
          2
          讀取id為100的數(shù)據(jù)沒有數(shù)據(jù)
          3插入id為100的數(shù)據(jù)

          4提交事務(wù)

          5
          讀取id為100的數(shù)據(jù)沒有數(shù)據(jù)
          6
          更新id為100的數(shù)據(jù)成功
          7
          讀取id為100的數(shù)據(jù)讀取成功
          8
          提交事務(wù)

          事務(wù)B在第2步第一次讀取id=99的記錄時(shí),讀到的記錄為空,說明不存在id=99的記錄。隨后,事務(wù)A在第3步插入了一條id=99的記錄并提交。事務(wù)B在第5步再次讀取id=99的記錄時(shí),讀到的記錄仍然為空,但是,事務(wù)B在第6步試圖更新這條不存在的記錄時(shí),竟然成功了,并且,事務(wù)B在第8步再次讀取id=99的記錄時(shí),記錄出現(xiàn)了。

          • SERIALIZABLE(串行化):數(shù)據(jù)庫最高的隔離級(jí)別,它要求所有的SQL都會(huì)按照順序執(zhí)行,這樣可以克服上述所有隔離出現(xiàn)的各種問題,能夠完全包住數(shù)據(jù)的一致性。

          Spring中配置隔離級(jí)別

          在Spring項(xiàng)目中配置隔離級(jí)別只需要做如下操作

          @Transactional(isolation = Isolation.SERIALIZABLE)
          public int insertUser(User user){
          return userDao.insertUser(user);
          }

          上面的代碼中我們使用了串行化的隔離級(jí)別來包住數(shù)據(jù)的一致性,這使它將阻塞其他的事務(wù)進(jìn)行并發(fā),所以它只能運(yùn)用在那些低并發(fā)而又需要保證數(shù)據(jù)一致性的場景下。

          隔離級(jí)別字典:

          DEFAULT(-1),  ## 數(shù)據(jù)庫默認(rèn)級(jí)別
          READ_UNCOMMITTED(1),
          READ_COMMITTED(2),
          REPEATABLE_READ(4),
          SERIALIZABLE(8);

          傳播行為

          在Spring中,當(dāng)一個(gè)方法調(diào)用另外一個(gè)方法時(shí),可以讓事務(wù)采取不同的策略工作,如新建事務(wù)或者掛起當(dāng)前事務(wù)等,這便是事務(wù)的傳播行為。

          定義

          在Spring的事務(wù)機(jī)制中對數(shù)據(jù)庫存在7種傳播行為,通過枚舉類Propagation定義。

          public enum Propagation {
          /** * 需要事務(wù),默認(rèn)傳播性行為。
          * 如果當(dāng)前存在事務(wù),就沿用當(dāng)前事務(wù),否則新建一個(gè)事務(wù)運(yùn)行子方法
          */
          REQUIRED(0),
          /** * 支持事務(wù),如果當(dāng)前存在事務(wù),就沿用當(dāng)前事務(wù),
          * 如果不存在,則繼續(xù)采用無事務(wù)的方式運(yùn)行子方法
          */
          SUPPORTS(1),
          /** * 必須使用事務(wù),如果當(dāng)前沒有事務(wù),拋出異常
          * 如果存在當(dāng)前事務(wù),就沿用當(dāng)前事務(wù)
          */
          MANDATORY(2),
          /** * 無論當(dāng)前事務(wù)是否存在,都會(huì)創(chuàng)建新事務(wù)允許方法
          * 這樣新事務(wù)就可以擁有新的鎖和隔離級(jí)別等特性,與當(dāng)前事務(wù)相互獨(dú)立
          */
          REQUIRES_NEW(3),
          /** * 不支持事務(wù),當(dāng)前存在事務(wù)時(shí),將掛起事務(wù),運(yùn)行方法
          */
          NOT_SUPPORTED(4),
          /** * 不支持事務(wù),如果當(dāng)前方法存在事務(wù),將拋出異常,否則繼續(xù)使用無事務(wù)機(jī)制運(yùn)行
          */
          NEVER(5),
          /** * 在當(dāng)前方法調(diào)用子方法時(shí),如果子方法發(fā)生異常
          * 只回滾子方法執(zhí)行過的SQL,而不回滾當(dāng)前方法的事務(wù)
          */
          NESTED(6);
          ......
          }

          日常開發(fā)中基本只會(huì)使用到REQUIRED(0),REQUIRES_NEW(3),NESTED(6)三種。

          NESTEDREQUIRES_NEW是有區(qū)別的。NESTED傳播行為會(huì)沿用當(dāng)前事務(wù)的隔離級(jí)別和鎖等特性,而REQUIRES_NEW則可以擁有自己獨(dú)立的隔離級(jí)別和鎖等特性。

          NESTED的實(shí)現(xiàn)主要依賴于數(shù)據(jù)庫的保存點(diǎn)(SAVEPOINT)技術(shù),SAVEPOINT記錄了一個(gè)保存點(diǎn),可以通過ROLLBACK TO SAVEPOINT來回滾到某個(gè)保存點(diǎn)。如果數(shù)據(jù)庫支持保存點(diǎn)技術(shù)時(shí)就啟用保存點(diǎn)技術(shù);如果不支持就會(huì)新建一個(gè)事務(wù)去執(zhí)行代碼,也就相當(dāng)于REQUIRES_NEW。

          Transactional自調(diào)用失效

          如果一個(gè)類中自身方法的調(diào)用,我們稱之為自調(diào)用。如一個(gè)訂單業(yè)務(wù)實(shí)現(xiàn)類OrderServiceImpl中有methodA方法調(diào)用了自身類的methodB方法就是自調(diào)用,如:

          @Transactional
          public void methodA(){
          for (int i = 0; i < 10; i++) {
          methodB();
          }
          }

          @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
          public int methodB(){
          ......
          }

          在上面方法中不管methodB如何設(shè)置隔離級(jí)別和傳播行為都是不生效的。即自調(diào)用失效。

          這主要是由于@Transactional的底層實(shí)現(xiàn)原理是基于AOP實(shí)現(xiàn),而AOP的原理是動(dòng)態(tài)代理,在自調(diào)用的過程中是類自身的調(diào)用,而不是代理對象去調(diào)用,那么就不會(huì)產(chǎn)生AOP,于是就發(fā)生了自調(diào)用失敗的現(xiàn)象。

          要克服這個(gè)問題,有2種方法:

          • 編寫兩個(gè)Service,用一個(gè)Service的methodA去調(diào)用另外一個(gè)Service的methodB方法,這樣就是代理對象的調(diào)用,不會(huì)有問題;

          • 在同一個(gè)Service中,methodA不直接調(diào)用methodB,而是先從Spring IOC容器中重新獲取代理對象`OrderServiceImpl·,獲取到后再去調(diào)用methodB。說起來有點(diǎn)亂,還是show you the code。

          public class OrderServiceImpl implements OrderService,ApplicationContextAware {
          private ApplicationContext applicationContext = null;

          @Override
          public void setApplicationContext(ApplicationContext applicationContext) {
          this.applicationContext = applicationContext;
          }

          @Transactional
          public void methodA(){
          OrderService orderService = applicationContext.getBean(OrderService.class);
          for (int i = 0; i < 10; i++) {
          orderService.methodB();
          }
          }

          @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
          public int methodB(){
          ......
          }

          }

          上面代碼中我們先實(shí)現(xiàn)了ApplicationContextAware接口,然后通過applicationContext.getBean()獲取了OrderService的接口對象。這個(gè)時(shí)候獲取到的是一個(gè)代理對象,也就能正常使用AOP的動(dòng)態(tài)代理了?;氐阶铋_始的那個(gè)問題,看完這篇文章是不是有答案了呢?


          好了,各位朋友們,本期的內(nèi)容到此就全部結(jié)束啦,如果看完有所收獲,別忘記三連喲,點(diǎn)贊、轉(zhuǎn)發(fā)、在看,我不挑!?(只收藏不點(diǎn)贊都是耍流氓...嘻嘻)

          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩黄色网址 | 国内激情久久久久 | 久久久久福利视频 | 国产A片精品 | 一级aa视频 |