排查 @Transactional 失效
1 背景
一个凌晨跑批需求,要在一个事务中批量插入20000条记录,但发现执行到3000条遇到异常,前2999条插入成功没有回滚。
2 排查过程
- 确认 @Transactional 注解是 org.springframework.transaction.annotation.Transactional。
- 确认事务传播级别是 Propagation.REQUIRED。
- 从异常堆栈中确认触发了回滚。
- 确认存储引擎是 innodb。(MyISAM 不支持事务)
- 联系DBA 解析 /data/tmp/binlog.info,定位到凌晨执行的 DML,举个例子:
# at 164367644
#200326 0:50:29 server id 23523306 end_log_pos 164367753 CRC32 0x91d1a906 Query thread_id=133109632 exec_time=0 error_code=0
SET TIMESTAMP=1585155029/*!*/;
SET @@session.sql_mode=1342177280/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=45/*!*/;
BEGIN
/*!*/;
# at 164367753
#200326 0:50:29 server id 23523306 end_log_pos 164367926 CRC32 0x30ea9289 Query thread_id=133109632 exec_time=0 error_code=0
SET TIMESTAMP=1585155029/*!*/;
UPDATE `t_user_ssj` SET `phone`='15999615969' WHERE (`id`='10140123')
/*!*/;
# at 164367926
#200326 0:50:29 server id 23523306 end_log_pos 164367957 CRC32 0xb91416ea Xid = 4055151432
COMMIT/*!*/;
可以发现 2999 个update 全部在 各自的事务 中执行。
5. MySql 默认开启 AutoCommit,除非显式开启 BEGIN、START,并且大多数CP实现默认开启 AutoCommit,但 org.springframework.transaction.PlatformTransactionManager 的实现应该会调用 java.sql.Connection#setAutoCommit(false),参考 org.springframework.jdbc.datasource.DataSourceTransactionManager:224。DBA开启 general_log 后让我们再模拟一次,然而发现 general_log 没日志,翻车(general_log 日志量很大,一般是关闭的)。
6. 项目使用了公司定制的 starter,事务管理默认 org.springframework.orm.jpa.JpaTransactionManager,CP默认 DBCP。DBCP 默认开启 AutoCommit,JpaTransactionManager 居然不会像 DataSourceTransactionManager 一样主动关闭 AutoCommit。
3 结论
JpaTransactionManager 没有在事务开启前关闭 AutoCommit,或者补一个 BEGIN,导致单sql单事务执行。
解决方法是在 starter 创建DBCP的地方,关掉 AutoCommit,这种方法只对 DBCP 有效,换别的连接池就需要注意下了。
Reference
[1] 无