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。但是在数据库中仍然可以查询到该记录,回滚并未生效。

mysql回滚一张表 mysql事务回滚_sql

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>

深入探究

  1. 三种方式均可完成回滚,那么其到底有什么共同点,在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就可以正常回滚了。

  1. 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”,***),去完成了回滚操作。

结论

  1. @Transactional、TransactionComponent、TransactionTemplate均可以完成在Mysql的INNODB存储引擎上的事务回滚,但不能在默认的MyISAM存储引擎完成回滚
  2. TransactionComponent、TransactionTemplate使用方法类似,需要手动使用代码控制事务。@Transactional注解在开发上一般使用在实现类的方法上,使用方便,代码量少,粒度较显式使用会粗一点。
  3. 三种方法均需配置ResourceTransactionManager的Bean交于Spring管理事务。
  4. 开发时导出数据库建表语句建议直接显式声明其存储引擎,以防有些数据库并未设置默认引擎为INNODB。
    (修改数据库默认存储引擎:在配置文件my.cnf中的 [mysqld] 下面加入default-storage-engine=INNODB)
default-storage-engine=INNODB

可以在数据库执行sql show engines查看默认的存储引擎:

mysql回滚一张表 mysql事务回滚_mysql回滚一张表_02