文章目录

写在前面

在一次测试过程中,mysql执行update、insert、delete卡住,导致springboot项目在执行sql语句时间过长,然后直接报错退出了。
于是乎,开始了一系列的问题排查。。。。

考虑是否锁表

首先考虑是不是表被锁了,发现的确是表被锁了,执行的update、insert、delete语句一直在等待。
-- 查看表正在被使用的人数
show OPEN TABLES where In_use > 0;

-- 显示完整的进程列表
-- 使用该命令我们发现,有很多update、insert、detele是waitting状态,考虑到是锁表的问题
show processlist;

-- 杀死任务
-- 使用show processlist;显示正在等待的语句,然后kill掉,我们发现如果再执行这条sql,似乎并没有什么效果
kill id;

-- 生成批量杀除锁表进程语句 Kill 进程id
-- 最终,我们将该用户的所有进程都杀掉,insert、delete、update语句可以正常执行了!
SELECT concat('KILL ',id,';') FROM information_schema.processlist WHERE user='cxf';
通过以上分析,我们初步考虑是锁表的原因。
但是!!!
即使有waitting状态的sql,用springboot程序执行的语句不能执行,但是直接使用navicat执行的sql语句可以正常被执行。
而且,将该用户的所有进程都杀掉,过了一段时间还是会出现锁表的问题!
于是乎,我们认定问题的源头并不是出在这里,不是单单锁表这么简单。

考虑代码原因

该问题是突然出现的,所以肯定是改了哪行代码,意外出现的这个问题。
于是开始翻代码。。。

spring编程式事务

先来学习一下spring的事务,spring有两种事务的写法,一种是声明式事务,就是使用@Transactional注解开启。
另一种开启事务的方式就是编程式事务。

private final DataSourceTransactionManager transactionManager;

public void excute(){
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = this.transactionManager.getTransaction(definition);
try{
// dosomthing();
this.transactionManager.commit(status);
} catch(){
this.transactionManager.rollback(status);
}
}

以上就是编程式事务的写法。
而我们代码中,dosomthing中,这样写的:

if(xxx){
return;
}

并没有将事务回滚或者提交!
于是造成了问题。

问题解决了

spring事务支持原理:spring的事务支持原理是先将mysql连接会话的自动提交属性关闭,即将当前会话的autoCommit属性设置为false,然后将该连接绑定到该线程中,在该事务中的所有数据库操作都是使用同一个线程,所有的数据库操作完成后才主动去做commit操作完成事务。

所以,当事务没有提交而是提前return出去时,会话的状态并不会改变,autoCommit属性一直为false,这就导致当其他请求使用该数据库连接会话操作数据库时,事务将无法自动提交。

所以!!!造成了其他sql语句无法提交,一直在waitting,让我们误以为是锁表造成的!

写在最后

spring编程式事务其实挺坑的,能不用还是别用了。。。
但是,某些场景下声明式事务没法使用的时候,还是得用到编程式事务的。
所以,使用编程式事务的时候,开启事务一定要记得提交或者回滚!!!