mybatis 没有强制commit导致事务泄露|select for update 造成的血案

it2026-08-04  12

背景:

我们需要从表里取数-打标记-然后处理。因为并发执行,所以我们要保证取数-标记是一个事务,于是我们取数时用了for update操作。

代码如下,其中有部分是伪代码,明白逻辑即可,暂且把涉及到的表记作A表。(全文只用到这一张表)

SqlSession sqlSession = this.sessionFactory.openSession(false); Log.info("get SqlSession=%s", sqlSession); try { TaskDownloadMapper taskDownloadMapper = sqlSession.getMapper(TaskDownloadMapper.class); TaskDownloadModel taskDownloadModel = taskDownloadMapper.getForUpdate(); if (taskDownloadModel != null) { Log.info("update taskDownloadModel status,model:%s", JSONObject.toJSONString(taskDownloadModel)); taskDownloadModel.setStatus(DbDef.TASK_DOWNLOAD_STATUS_RUNNING); taskDownloadMapper.updateStatusById(taskDownloadModel); //更新状态 Log.info("update taskDownloadModel success"); } return taskDownloadModel; }finally { Log.info("commit sqlSession ..."); sqlSession.commit(); //提交事务 }

上图代码10秒钟轮询一次,上线之后,发现往A表插入数据时报如下错误:

### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

 

获取锁超时,排查问题,查了一下MySQL的事务

 

1、查看数据库当前的进程,看一下有无正在执行的慢SQL记录线程。

mysql> show processlist;

2、查看当前的事务

当前运行的所有事务

mysql> SELECT * FROM information_schema.INNODB_TRX;

当前出现的锁

mysql> SELECT * FROM information_schema.INNODB_LOCKs;

锁等待的对应关系

mysql> SELECT * FROM information_schema.INNODB_LOCK_waits;

解释:看事务表INNODB_TRX,里面是否有正在锁定的事务线程,看看ID是否在show processlist里面的sleep线程中,如果是,就证明这个sleep的线程事务一直没有commit或者rollback而是卡住了。

 

经排查发现事务有很多,都是上述代码引起的,代码中确实有写commit 但是最终还是没有提交上去,这是什么情况,接下来我们查看了一下mybatis的源码

我们写的是没有参数的commit,所以默认是false,继续看 this.isCommitOrRollbackRequired(force) 方法

 

,其中autoCommit是false,force是false,焦点就落在了dirty上,只要dirty是true,那么就会提交,反之则不提交。我们继续看dirty的赋值情况

 

dirty默认是false,只有在update中才是true,而insert,update ,delete最终都会走到这个方法,意味着,只要这个session中有插入/更新/删除操作,那么dirty就是true,那么commit就会主动提交。反之则不会提交。而我们的代码中,如果没有需要消费的东西,那么就不会进行update操作,那就造成事务泄露,导致其他人往表里插入数据时报错。

这是一个坑,大家避免踩坑。

解决方案:

1. 强制commit,直接调用commit(true) 方法,因为强制commit所以不需要判断dirty 状态。

2. commit之后进行close操作,colse操作也会把事务结束,防止事务泄露。

思考:mybatis在设计是为什么要把dirty默认为false,为什么不会主动提交呢?

最新回复(0)