Mysql事务回滚的问题探究
文章目录
- Mysql事务回滚的问题探究
- 前提
- 目的
- 建表语句
- 实验思路
- 实验过程
- 测试代码
- 1. MyISAM与@Transactional
- 代码
- 结果
- 2. MyISAM与TransactionComponent组件
- 代码
- 结果
- 3. INNODB与@Transactional
- 代码
- 结果
- 4. INNODB与TransactionComponent组件
- 代码
- 结果
- 后续探究
- TransactionTemplate
- 深入探究
- 结论
前提
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查看默认的存储引擎: