MySQL事务隔离级别和Spring事务关系介绍
事務(wù)隔離級別介紹
| 未提交讀(Read uncommitted) | 可能 | 可能 | 可能 |
| 已提交讀(Read committed) | 不可能 | 可能 | 可能 |
| 可重復(fù)讀(Repeatable read) | 不可能 | 不可能 | 可能 |
| 可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
接下來一次來驗證每個隔離級別的特性,首先我們先建一張表,我們建立賬戶表account用來測試我們的事務(wù)隔離級別:
| 1 2 3 4 5 6 7 | CREATE TABLE account (`id` int(11) NOT NULL AUTO_INCREMENT,`customer_name` varchar(255) NOT NULL,`money` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE `uniq_name` USING BTREE (customer_name) ) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRIT |
RU (read uncommitted)讀未提交隔離級別
首先我們開啟Console A,然后設(shè)置session事務(wù)隔離級別為read uncommitted; 然后同樣開啟Console B,設(shè)置成read uncommitted;
| 1 2 3 4 5 6 7 8 9 10 | mysql> set session transaction isolation level read uncommitted; Query OK, 0 rows affected (0.03 sec)mysql> select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-UNCOMMITTED | +------------------------+ 1 rows in set (0.03 sec) |
我們兩個console的事務(wù)隔離級別都是read uncommitted,下面測試RU級別會發(fā)生的情況
小結(jié):
可以發(fā)現(xiàn)RU模式下,一個事務(wù)可以讀取到另一個未提交(commit)的數(shù)據(jù),導(dǎo)致了臟讀。如果B事務(wù)回滾了,就會造成數(shù)據(jù)的不一致。RU是事務(wù)隔離級別最低的。
RC (read committed)讀提交隔離級別
現(xiàn)在我們將事務(wù)隔離級別設(shè)置成RC (read committed)
| 1 | set session transaction isolation level read uncommitted; |
小結(jié)
我們在RC模式下,可以發(fā)現(xiàn)。在console B沒有提交數(shù)據(jù)修改的commit的時候,console A是讀不到修改后的數(shù)據(jù)的,這就避免了在RU模式中的臟讀,但是有一個問題我們會發(fā)現(xiàn),在console A同一個事務(wù)中。兩次select的數(shù)據(jù)不一樣,這就存在了不可重復(fù)讀的問題.PS:RC事務(wù)隔離級別是Oracle數(shù)據(jù)庫的默認隔離級別.
RR (Repeatable read)可重復(fù)讀隔離級別
小結(jié):
在RR級別中,我們解決了不可重復(fù)讀的問題,即在這種隔離級別下,在一個事務(wù)中我們能夠保證能夠獲取到一樣的數(shù)據(jù)(即使已經(jīng)有其他事務(wù)修改了我們的數(shù)據(jù))。但是無法避免幻讀,幻讀簡單的解釋就是在數(shù)據(jù)有新增的時候,也無法保證兩次得到的數(shù)據(jù)不一致,但是不同數(shù)據(jù)庫對不同的RR級別有不同的實現(xiàn),有時候或加上間隙鎖來避免幻讀。
innoDB 解決了幻讀
前面的定義中RR級別是可能產(chǎn)生幻讀,這是在傳統(tǒng)的RR級別定義中會出現(xiàn)的。但是在innoDB引擎中利用MVCC多版本并發(fā)控制解決了這個問題
這算是幻讀嗎?在標準的RR隔離級別定義中是無法解決幻讀問題的,比如我要保證可重復(fù)讀,那么我們可以在我們的結(jié)果集的范圍加一個鎖(between 1 and 11),防止數(shù)據(jù)更改.但是我們畢竟不是鎖住真?zhèn)€表,所以insert數(shù)據(jù)我們并不能保證他不插入。所以是有幻讀的問題存在的。但是innodb引擎解決了幻讀的問題,基于MVCC(多版本并發(fā)控制):在InnoDB中,會在每行數(shù)據(jù)后添加兩個額外的隱藏的值來實現(xiàn)MVCC,這兩個值一個記錄這行數(shù)據(jù)何時被創(chuàng)建,另外一個記錄這行數(shù)據(jù)何時過期(或者被刪除)。 在實際操作中,存儲的并不是時間,而是事務(wù)的版本號,每開啟一個新事務(wù),事務(wù)的版本號就會遞增。所以當(dāng)我們執(zhí)行update的時候,當(dāng)前事務(wù)的版本號已經(jīng)更新了?所以也算是幻讀??(存疑)主要是gap間隙鎖+MVCC解決幻讀問題?
串行化隔離級別:
所有事物串行,最高隔離級別,性能最差
存在的問題?
在RR模型,我們雖然避免了幻讀,但是存在一個問題,我們得到的數(shù)據(jù)不是數(shù)據(jù)中實時的數(shù)據(jù),如果是對實時數(shù)據(jù)比較敏感的業(yè)務(wù),這是不現(xiàn)實的。
對于這種讀取歷史數(shù)據(jù)的方式,我們叫它快照讀 (snapshot read),而讀取數(shù)據(jù)庫當(dāng)前版本數(shù)據(jù)的方式,叫當(dāng)前讀 (current read)。很顯然,在MVCC中:
- 快照讀:就是select
- select * from table ….;
- 當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,處理的都是當(dāng)前的數(shù)據(jù),需要加鎖。
- select * from table where ? lock in share mode;
- select * from table where ? for update;
- insert;
- update ;
- delete;
事務(wù)的隔離級別實際上都是定義了當(dāng)前讀的級別,MySQL為了減少鎖處理(包括等待其它鎖)的時間,提升并發(fā)能力,引入了快照讀的概念,使得select不用加鎖。而update、insert這些“當(dāng)前讀”,就需要另外的模塊來解決了。
比如,我們有以下的訂單業(yè)務(wù)場景,我們隊一個商品下單的操作,我們得首先檢查這個訂單的數(shù)量還剩多少,然后下單。
事務(wù)1:
| 1 2 | select num from t_goods where id=1; update t_goods set num=num-$mynum where id=1; |
事務(wù)2:
| 1 2 | select num from t_goods where id=1; update t_goods set num=num-$mynum where id=1; |
假設(shè)這個時候數(shù)量只有1,我們下單也是只有1.如果在并發(fā)的情況下,事務(wù)1查詢到還有一單準備下單,但是這個時候事務(wù)2已經(jīng)提交了。訂單變成0.這個事務(wù)1在執(zhí)行update,就會造成事故。
| 1 2 | select num,version from t_goods where id=1; update t_goods set num=num-1,version=verison+1 where id=1 and version=${version} |
Spring管理事務(wù)的方式。
編程式事務(wù)
編程式事務(wù)就是利用手動代碼編寫事務(wù)相關(guān)的業(yè)務(wù)邏輯,這種方式比較復(fù)雜、啰嗦,但是更加靈活可控制(個人比較喜歡)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public void testTransactionTemplate() {TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); //設(shè)置事務(wù)隔離級別transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設(shè)置為required傳播級別....transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //事務(wù)塊jdbcTemplate.update(INSERT_SQL, "test"); }}); } |
聲明式事務(wù)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!--設(shè)置所有匹配的方法,然后設(shè)置傳播級別和事務(wù)隔離--><tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="put*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* org.transaction..service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config> |
| 1 | <tx:annotation-driven transaction-manager="transactioManager" /><!--開啟注解的方式--> |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";Propagation propagation() default Propagation.REQUIRED;//傳播級別Isolation isolation() default Isolation.DEFAULT;//事務(wù)隔離級別int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//事務(wù)超時時間boolean readOnly() default false;//只讀事務(wù)Class<? extends Throwable>[] rollbackFor() default {};//拋出哪些異常 會執(zhí)行回滾String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};//不回滾的異常名稱} //transaction注解可以放在方法上或者類上 |
我們在這里不對兩種事務(wù)編程做過多的講解
Spring事務(wù)傳播:
事務(wù)傳播行為:
Spring管理的事務(wù)是邏輯事務(wù),而且物理事務(wù)和邏輯事務(wù)最大差別就在于事務(wù)傳播行為,事務(wù)傳播行為用于指定在多個事務(wù)方法間調(diào)用時,事務(wù)是如何在這些方法間傳播的,Spring共支持7種傳播行為
為了演示事務(wù)傳播行為,我們新建一張用戶表
| 1 2 3 4 5 6 | EATE TABLE user (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255) NOT NULL,`pwd` varchar(255) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0; |
Required:
必須有邏輯事務(wù),否則新建一個事務(wù),使用PROPAGATION_REQUIRED指定,表示如果當(dāng)前存在一個邏輯事務(wù),則加入該邏輯事務(wù),否則將新建一個邏輯事務(wù),如下圖所示;
測試的代碼如下,在account插入的地方主動回滾
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public int insertAccount(final String customer, final int money) {transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設(shè)置為required傳播級別int re= transactionTemplate.execute(new TransactionCallback<Integer>() {public Integer doInTransaction( TransactionStatus status) {int i = accountDao.insertAccount(customer, money);status.setRollbackOnly();//主動回滾return i;}});return re; }public int inertUser(final String username, final String password) {transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設(shè)置為required傳播級別transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {int i = userDao.inertUser(username, password);int hahha = accountService.insertAccount("hahha", 2222); // status.setRollbackOnly();System.out.println("user==="+i);System.out.println("account===="+hahha);}});return 0;} |
按照required的邏輯,代碼執(zhí)行的邏輯如下:
所以在這種情況下,兩個事務(wù)屬于同一個事務(wù),一個回滾則兩個任務(wù)都回滾。
RequiresNew:
創(chuàng)建新的邏輯事務(wù),使用PROPAGATION_REQUIRES_NEW指定,表示每次都創(chuàng)建新的邏輯事務(wù)(物理事務(wù)也是不同的)如下圖所示:
Supports:
支持當(dāng)前事務(wù),使用PROPAGATION_SUPPORTS指定,指如果當(dāng)前存在邏輯事務(wù),就加入到該邏輯事務(wù),如果當(dāng)前沒有邏輯事務(wù),就以非事務(wù)方式執(zhí)行,如下圖所示:
NotSupported:
不支持事務(wù),如果當(dāng)前存在事務(wù)則暫停該事務(wù),使用PROPAGATION_NOT_SUPPORTED指定,即以非事務(wù)方式執(zhí)行,如果當(dāng)前存在邏輯事務(wù),就把當(dāng)前事務(wù)暫停,以非事務(wù)方式執(zhí)行。
Mandatory:
必須有事務(wù),否則拋出異常,使用PROPAGATION_MANDATORY指定,使用當(dāng)前事務(wù)執(zhí)行,如果當(dāng)前沒有事務(wù),則拋出異常(IllegalTransactionStateException)。當(dāng)運行在存在邏輯事務(wù)中則以當(dāng)前事務(wù)運行,如果沒有運行在事務(wù)中,則拋出異常
Never
不支持事務(wù),如果當(dāng)前存在是事務(wù)則拋出異常,使用PROPAGATION_NEVER指定,即以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常(IllegalTransactionStateException)
Nested:
嵌套事務(wù)支持,使用PROPAGATION_NESTED指定,如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行,如果當(dāng)前不存在事務(wù),則創(chuàng)建一個新的事務(wù),嵌套事務(wù)使用數(shù)據(jù)庫中的保存點來實現(xiàn),即嵌套事務(wù)回滾不影響外部事務(wù),但外部事務(wù)回滾將導(dǎo)致嵌套事務(wù)回滾。
Nested和RequiresNew的區(qū)別:
Nested使用JDBC 3的保存點(save point)實現(xiàn),即如果使用低版本驅(qū)動將導(dǎo)致不支持嵌套事務(wù)。
使用嵌套事務(wù),必須確保具體事務(wù)管理器實現(xiàn)的nestedTransactionAllowed屬性為true,否則不支持嵌套事務(wù),如DataSourceTransactionManager默認支持,而HibernateTransactionManager默認不支持,需要設(shè)置來開啟。
總結(jié)
以上是生活随笔為你收集整理的MySQL事务隔离级别和Spring事务关系介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java JDK代理、CGLIB、Asp
- 下一篇: MySQL中述职类型的长度问题