Spring @Transactional未提交问题:长事务引发MySQL超时疑问
问题描述
工作中遇到长事务问题,该长事务导致更新查询触发MySqlTimeoutException。出现问题的查询语句如下:
-- fileDAO.updateFile -- file_id 是主键。related_board_id 列无索引,类型为int。 UPDATE file SET related_board_id = 1 WHERE file_id = 1
对应的问题代码如下:
@Service public class FileService { @Autowired private FileDAO fileDAO; // 已配置PlatformTransactionManager bean。Transactional使用默认设置。 @Transactional public void saveFileList(List<File> fileList) { for (File file: fileList) { fileDAO.updateFile(file); } } }
我们使用Spring 4.3.x版本,当时正在存储1200个文件,未生成该事务的错误日志,但事务未提交。有两个疑问:
- 为何会出现长事务?
- 若长事务原因是保存过多文件,为何事务未提交且无报错?
问题解答
一、长事务产生的原因
- 单事务批量执行更新:
saveFileList方法被@Transactional标记,1200次updateFile操作全部在同一个事务中执行。每次更新都会占用数据库连接,事务持续时间随操作次数线性增加,最终超出MySQL的超时阈值,触发MySqlTimeoutException。 - 默认事务属性的影响:Spring 4.3.x中
@Transactional默认传播行为为REQUIRED,隔离级别取数据库默认(通常是REPEATABLE READ)。该隔离级别下事务会持有锁直到结束,加上1200次循环的累积耗时,进一步拉长了事务执行时间。
二、事务未提交且无报错的原因
- 异常未被捕获或日志未记录:如果某次更新触发超时异常,但异常被Spring事务管理器包裹,或者你的日志配置未记录事务相关异常,就会看不到报错日志。此时若异常未被方法内部捕获,Spring会自动回滚事务,你看到的“未提交”实际是事务被回滚。
- 数据库主动终止事务:当事务执行时间超过MySQL的
wait_timeout或innodb_lock_wait_timeout设置时,数据库会主动断开连接或终止事务。这种场景下应用端可能无法及时捕获到异常,导致事务既没提交,也没有报错日志输出。 - 异常被隐性吞掉:如果循环中某步操作抛出异常但被代码中的try-catch块吞掉,事务会因方法提前终止而无法执行到提交逻辑,最终数据库会因连接超时或事务超时强制回滚事务,且不会在业务日志中留下记录。
内容的提问来源于stack exchange,提问作者changuk
相关产品推荐
相关产品推荐

