事务使用不当的坑,踩过不止一个两个了,只是,柳暗花明又一坑~
某个项目用的mysql数据库和druid连接池,在测试环境经常报connection disable,如下:
相信大部分人能一眼看出,这是由于数据库主动断开了某个连接,而程序还在继续使用该connection。由报错信息可知,mysql服务器的wait_timeout只有60s,简直离谱!正常来说其默认的超时时间应为8小时。如报错建议,第一反应是调整mysql超时时间,但是该数据库属于其他开发商,无法修改超时配置。
但是什么样的数据库操作会耗时1分多钟呢?分析方法具体代码
操作如下:
1、先查询某条记录;
2、调用第三方接口推送该记录;
3、把推送结果插入日志表;
代码是没有问题的,那会是连接池没有设置testWhileIdle为true的原因?
配置是没问题的,这样会在获取空闲连接的时候先测试该连接是否正常。
没办法,只能走一遍源码,然后就发现这个方法居然开启了事务!
原来是直接在类上打了@transactional,那基本能确定问题原因了,使用事务,同一个数据源必定使用同一个connection,事务开启的时候,事务管理器会把当前的connection放进ThreadLocal里,所有在事务里的数据库操作都会通过DataSourceUtil获取该connection。
所以步骤1查询的时候会把当前的connection放进ThreadLocal,然后步骤2如果耗时超过60s的话,步骤3通过ThreadLocal获取的connection必然是超时的,这样就导致了步骤2即使在第61s成功返回结果(虽然很过分,但理论上是可能的),步骤3也会报错,根本无法记录日志。
解决方法也很简单,上面的操作根本不需要事务,把@transactional去掉就行了,这样在步骤3的时候会获取新的connection(且通过validationQuery验证有效性)。
实际开发过程中,出现这种问题的几率应该是比较小的(除非如上,数据库超时时间设置足够小,事务内操作耗时足够久),但是这种编码习惯有待商榷,直接给service类声明事务,尽管方便,但如果不能保证该类在拓展方法的时候是单纯持久层操作,还是谨慎为上,事务应该只在有需要的时候才开启。