首页 > 代码库 > 关于spring Transactional的一个故事

关于spring Transactional的一个故事

前言:今天在做一个spring事务的时候,本来以为很简单就会完成,但是却遇到了一点曲折,spring的事务需要依赖mysql的事务隔离级别,下面就把这个过程记录下来。

事情的起因是这样的,由于在更新一个出金金额和相应插入一条出金记录的时候,没有加事务,导致概率性的事件出现了,出金记录增加了,但是由于出金记录在update的时候由于其他事务的占用原因,并没有执行,最开始的时候没有注意到这个原因,因为日志里面并没有出错,代码逻辑并没有出现问题,但是问题出现了,那么经过思考,应该是由于另外一处线程的事务影响到了update语句的执行,就做了模拟测试,如下

START TRANSACTION;
UPDATE money_user SET dayintomoney = 10 WHERE uid = 2;
insert into money_transfer (
		id,serial_number) VALUES(1,2);
UPDATE money_user SET dayintomoney = 10 WHERE uid = 2;
由于下面的insert语句和update语句没有加事务,就导致insert 语句插入了,但是update语句因为上一个事务没有提交的原因没有执行。

修改:

后来就做了一下处理spring的方法事务超时单位是秒

	@Transactional(timeout=2)
	public int updateForzenMoney(MoneyTransfer moneyTransfer, int uid) {
		// 如果是出金请求,则预扣除冻结资金
		if (moneyTransfer.getType().intValue() == 1) {
			moneyTransferService.updateTotalmoneyForPerTransfermoney(uid, moneyTransfer.getAmount());
			// 将请求插入money_transfer表中
			moneyTransferService.addMoneyTransfer(moneyTransfer);
			return 1;
		} else {
			moneyTransferService.addMoneyTransfer(moneyTransfer);
			return 2;
		}
	}
先去尝试update语句,再进行数据插入,这样update语句执行的时候,会因为另外一个事务的影响,就会出现timeout两秒的时间,然后进行回滚,但是实际在执行的过程中,我先把另外一个事务执行起来,再来执行该方法的时候,发现事务回滚的时间远远大于2秒的时间,为什么呢,

继续:

我先是想到了mybatis(我的项目是spring+mybatis+mysql)的事务执行超时设置,

timeout单位是毫秒
这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的最大等待值。默认不设置(驱动自行处理)。
然后就做了如下处理
<update id="updateTotalmoneyForPerTransfermoneyOut" parameterType="hashmap" timeout="2000">
继续执行,发现执行时间依然远远大于2+2+2,接近50毫秒,经过一系列的实验,突然想到是不是mysql的默认事务超时时间的原因,因为mysql的innodb默认超时时间为50s,而我的money表刚好是innodb引擎,然后
mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set
ok,找到了他,那么到底是不是呢,修改一下

mysql> set
 innodb_lock_wait_timeout = 10;
Query OK, 0 rows affected

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 10    |
+--------------------------+-------+
1 row in set
继续执行,发现执行时间接近了10秒左右,然后再将10秒设置为20秒,ok,测试出来的超时时间大概是20秒,说明这个思路是正确的,但是为什么spring的事务超时时间没有起到作用呢,继续调查

mysql> set innodb_lock_wait_timeout = 1
;
Query OK, 0 rows affected

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 1     |
+--------------------------+-------+
1 row in set
把mysql的时间再设置短一点,然后再次把spring的事务超时设置为5秒,把mybatis的超时去掉

	@Transactional(timeout=5)
	public int updateForzenMoney(MoneyTransfer moneyTransfer, int uid) {
然后再执行,看日志时间

DEBUG 2014-12-12 16:53:49,784 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: UPDATE money_us
DEBUG 2014-12-12 16:53:49,785 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 
DEBUG 2014-12-12 16:54:00,795
事务回滚的时间差不多刚好是两个statement的事务执行时间5+5=10秒的时间。

结论:

虽然没有再调查mybatis的事务超时时间,但是可以确认的是,spring的事务超时时间和mysql的事务超时时间是相互影响的,我最后确认的方案是,修改mysql的innodb超时时间为20m,然后去掉Java方法上的超时时间

@Transactional<span style="white-space:pre">	</span>public int updateForzenMoney(MoneyTransfer moneyTransfer, int uid) {
再次,测试,事务的回滚时间大概是20m。

总结:什么问题的发生都是有原因的,之间也是相互关联的,那么只要我们耐住性子一步步去尝试和调查就会找到答案。









关于spring Transactional的一个故事