背景
最近在搭一个SSH框架的时候,测试hibernate + spring声明式事务的时候死活回滚不成功,反映的现象是不管抛出RuntimeException还是spring tx自己的DataAccessException都无法将已经插入的记录回滚掉,一开始是以为框架搭建的配置文件有问题,后来发现即使使用原生的jdbc rollback也无法回滚,此时才意识到可能是mysql数据库的问题。
分析
根据前面提到的思路,当SSH的事务无法正常回滚的时候也找不到配置的问题所在,就采用最简单的JDBC来测试一下事务回滚的是否正常,果然发现了问题。采用类似下面的代码,来做测试
@Test
public void testTransaction() {
try {
// 初始化mysql连接
this.initMysql();
// 设置不自动提交
conn.setAutoCommit(false);
Statement statement = conn.createStatement();
// 执行插入
statement.executeUpdate("insert into test VALUES(1,'a',18)");
throw new RuntimeException("Test transaction exception");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (RuntimeException e) {
// 这里模拟捕捉类似spring事务管理的runtime exception
System.out.println("Catched transaction exception");
try {
// 回滚,这是最关键的一行
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
运行该junit测试代码之后发现,虽然正常捕捉到了RuntimeException也执行了conn.rollback(),但是记录依然在表里面没有被回滚掉。这个时候仔细研究了一下这张表,发现建表语句为
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
其他地方都正常,那唯一有可能出错的就是 ENGINE=MyISAM 这里了,查询资料后发现
MyISAM:这个是默认类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的顺序访问方法) 的缩写,它是存储记录和文件的标准方法。与其他存储引擎比较,MyISAM具有检查和修复表格的大多数工具。 MyISAM表格可以被压缩,而且它们支持全文搜索。它们不是事务安全的,而且也不支持外键。如果事物回滚将造成不完全回滚,不具有原子性。如果执行大量的SELECT,MyISAM是更好的选择。
明确说明了MyISAM不支持事务,这也就是问题的所在,把引擎改成InnoDB之后,上述测试代码就正常了。
总结
虽然MyISAM这个引擎不支持事务,但它并不是没有作用的,对于不需要事务的流水表或事实表,它就有更好的查询效率。 这里补充说明下两种引擎说明
MyISAM:这个是默认类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的 顺序访问方法) 的缩写,它是存储记录和文件的标准方法.与其他存储引擎比较,MyISAM具有检查和修复表格的大多数工具. MyISAM表格可以被压缩,而且它们支持全文搜索.它们不是事务安全的,而且也不支持外键。如果事物回滚将造成不完全回滚,不具有原子性。如果执行大量 的SELECT,MyISAM是更好的选择。
InnoDB:这种类型是事务安全的.它与BDB类型具有相同的特性,它们还支持外键.InnoDB表格速度很快.具有比BDB还丰富的特性,因此如果需要一个事务安全的存储引擎,建议使用它.如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表, 对于支持事物的InnoDB类型的表,影响速度的主要原因是AUTOCOMMIT默认设置是打开的,而且程序没有显式调用BEGIN 开始事务,导致每插入一条都自动Commit,严重影响了速度。可以在执行sql前调用begin,多条sql形成一个事物(即使autocommit打 开也可以),将大大提高性能
附查询和修改的sql
查看mysql支持的所有存储引擎
show engines;
查看mysql当前默认的存储引擎
show variables like '%storage_engine%';
查看某个表用了什么引擎
在显示结果里参数engine后面的就表示该表当前用的存储引擎
show create table 表名;
修改引擎
ALTER TABLE my_table ENGINE=InnoDB
这里需要注意的是,如果表里面已经有数据了,且数据量大的时候进行修改可能会比较慢。如果数据比较重要,安全起见,可以采用将原数据表备份,新建InnoDB的数据表,再导入的形式来进行。