Mysql事務回滾的問題探究
文章目錄
前提
XXX平臺導出的mysql建表語句沒有指定存儲引擎,而mysql默認使用MyISAM。
但是MyISAM是不支持事務的。在mysql中,唯有InnoDb是支持事務的。
目的
驗證Spring提供的標籤@Transactional,以及XXX提供的TransactionComponent組件在不同的存儲引擎下的工作實況。
建表語句
借用XXX-BATCH工程中的“batch_cli_user”表部分字段進行實驗。
CREATE TABLE `BATCH_CLI_USER` (
`protocol_no` VARCHAR(64) NOT NULL COMMENT '協議號',
`user_id` VARCHAR(32) COMMENT '用戶標識,錄入人ID',
`user_name` VARCHAR(80) COMMENT '用戶名稱',
`contact` VARCHAR(32) COMMENT '聯繫人',
`telephone` VARCHAR(16) COMMENT '電話號碼',
`mobile_no` VARCHAR(16) COMMENT '電話號碼',
CONSTRAINT `pk_BATCH_CLI_USER` PRIMARY KEY (`protocol_no`)
) ENGINE = MYISAM;
以及使用Innodb存儲引擎的建表語句
CREATE TABLE `BATCH_CLI_USER` (
`protocol_no` VARCHAR(64) NOT NULL COMMENT '協議號',
`user_id` VARCHAR(32) COMMENT '用戶標識,錄入人ID',
`user_name` VARCHAR(80) COMMENT '用戶名稱',
`contact` VARCHAR(32) COMMENT '聯繫人',
`telephone` VARCHAR(16) COMMENT '電話號碼',
`mobile_no` VARCHAR(16) COMMENT '電話號碼',
CONSTRAINT `pk_BATCH_CLI_USER` PRIMARY KEY (`protocol_no`)
) ENGINE = INNODB;
實驗思路
通過兩次對相同主鍵的插入,若事務生效,則在第二次插入報主鍵衝突的時候,應當發生回滾,使第一條的插入語句也被回滾,若事務不生效,則查詢後可以觀察到該記錄。
實驗過程
測試代碼
如果發送事務回滾,則預期爲null正確。
@Test
public void testRollbackForMysql() throws Exception { batchCliUserMapper.deleteByPrimaryKey("testProtocolNo004");
BatchCliUser batchCliUser = new BatchCliUser();
batchCliUser.setUserId("testUser004");
batchCliUser.setProtocolNo("testProtocolNo004"); batchCustomerInfoService.testRollbackForMysql(batchCliUser);
BatchCliUser result1 = batchCliUserMapper.selectByPrimaryKey("testProtocolNo004"); Assert.assertTrue(null == result1);
}
1. MyISAM與@Transactional
代碼
@Override@Transactional(rollbackFor = Exception.class)
public void testRollbackForMysql(BatchCliUser batchCliUser) throws Exception {
// 開啓事務,連續插入兩次,看事務是否會回滾掉第一次的插入
LOGGER.info("開始第一次插入");
batchCliUserMapper.insertSelective(batchCliUser);
LOGGER.info("開始第二次插入");
batchCliUserMapper.insertSelective(batchCliUser);}
結果
代碼在第二次插入時拋出org.springframework.dao.DuplicateKeyException。但是在數據庫中仍然可以查詢到該記錄,回滾並未生效。
2. MyISAM與TransactionComponent組件
代碼
@Override
public void testRollbackForMysql(BatchCliUser batchCliUser) throws Exception {
// 顯式開啓事務,連續插入兩次,看事務是否會回滾掉第一次的插入
try {
transactionComponent.begin();
LOGGER.info("開始第一次插入");
batchCliUserMapper.insertSelective(batchCliUser);
LOGGER.info("開始第二次插入");
batchCliUserMapper.insertSelective(batchCliUser);
transactionComponent.commit();
} catch (Exception e) {
transactionComponent.rollback();
throw e;
}
}
結果
與實驗1相同,回滾並未生效。
3. INNODB與@Transactional
代碼
與實驗1相同
結果
成功回滾,數據庫中查無記錄。
4. INNODB與TransactionComponent組件
代碼
與實驗2相同
結果
與實驗3相同,成功回滾,數據庫中查無記錄。
後續探究
TransactionTemplate
Spring提供的事務管理模板,驗證可以事務回滾。【且相對XXX提供的組件,更方便設置隔離級別,事務傳播特性等】
@Autowired
public TransactionTemplate transactionTemplate;
@Override
public void testRollbackForMysql(BatchCliUser batchCliUser) throws Exception {
// 顯式開啓事務,連續插入兩次,看事務是否會回滾掉第一次的插入
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
LOGGER.info("開始第一次插入");
batchCliUserMapper.insertSelective(batchCliUser);
LOGGER.info("開始第二次插入");
batchCliUserMapper.insertSelective(batchCliUser);
}
});
}
xml中的Bean配置
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="testTransactionManager"></property>
</bean>
深入探究
- 三種方式均可完成回滾,那麼其到底有什麼共同點,在DataSourceTransactionManager類中有如下方法,在debug時發現:
protected void doRollback(DefaultTransactionStatus status) { DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
} try {
con.rollback();
} catch (SQLException var5) {
throw new TransactionSystemException("Could not roll back JDBC transaction", var5);
}
}
其核心代碼在con.rollback()這一行中,交由各個Connection接口的實現類自己實現。
PS: @Transactional註解還需要配置代理,配置如下後:
<!-- 事務管理器 -->
<bean id="testTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="gapsDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="testTransactionManager" proxy-target-class="true"/>
配置好後@Transactional就可以正常回滾了。
-
Mysql事務中到底怎麼完成回滾?
調試con.rollback()方法中,可以看到調用了以下方法:
private void rollbackNoChecks() throws SQLException { try { synchronized(this.getConnectionMutex()) { if (!(Boolean)this.useLocalTransactionState.getValue() || this.session.getServerSession().inTransactionOnServer()) { this.session.execSQL((Query)null, "rollback", -1, (NativePacketPayload)null, false, this.nullStatementResultSetFactory, this.database, (ColumnDefinition)null, false); } } } catch (CJException var5) { throw SQLExceptionsMapping.translateException(var5, this.getExceptionInterceptor()); } }
方法中執行了execSQL((Query)null, “rollback”,***),去完成了回滾操作。
結論
-
@Transactional、TransactionComponent、TransactionTemplate均可以完成在Mysql的INNODB存儲引擎上的事務回滾,但不能在默認的MyISAM存儲引擎完成回滾。
-
TransactionComponent、TransactionTemplate使用方法類似,需要手動使用代碼控制事務。@Transactional註解在開發上一般使用在實現類的方法上,使用方便,代碼量少,粒度較顯式使用會粗一點。
-
三種方法均需配置ResourceTransactionManager的Bean交於Spring管理事務。
-
開發時導出數據庫建表語句建議直接顯式聲明其存儲引擎,以防有些數據庫並未設置默認引擎爲INNODB。
(修改數據庫默認存儲引擎:在配置文件my.cnf中的 [mysqld] 下面加入default-storage-engine=INNODB)
default-storage-engine=INNODB
可以在數據庫執行sql show engines查看默認的存儲引擎: