排查 @Transactional 失效

1 背景

一个凌晨跑批需求,要在一个事务中批量插入20000条记录,但发现执行到3000条遇到异常,前2999条插入成功没有回滚。

2 排查过程

  1. 确认 @Transactional 注解是 org.springframework.transaction.annotation.Transactional。
  2. 确认事务传播级别是 Propagation.REQUIRED。
  3. 从异常堆栈中确认触发了回滚。
  4. 确认存储引擎是 innodb。(MyISAM 不支持事务)
  5. 联系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] 无

posted @ 2020-03-26 10:48  Casaa  阅读(364)  评论(0编辑  收藏  举报