一、问题出现
1.1 异常
在一次操作中出现数据库死锁,异常栈如下
org.springframework.dao.CannotAcquireLockException:
### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may involve TestTableMapper.batchInsert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO test_table
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:267)
... ...
at com.sun.proxy.$Proxy672.batchInsert(Unknown Source)
at TestTableServiceImpl.saveTestTable(TestTableServiceImpl.java:294)
... 业务调用栈
at .TaskThreadPool$2.run(TaskThreadPool.java:85)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
... ...
可以看到 出现了CannotAcquireLockException,无法获取到锁,通过搜索引擎查询到,该异常通常是死锁引起的
1.2 代码如下
主线程
@Service
public class MainService {
@Resource
private SubService subService;
@Transactional
public void update() {
List<Long> orgList = new ArrayList<Long>();
... ...
// 操作业务表 TestTable
subService.delete(data);
// 开启子线程保存数据
CountDownLatch countDownLatch =
taskThreadPool.runAsyncWithLatch(
orgList,
orgId -> {
subService.save(data);
}
);
}
}
子线程
@Service
public class SubService {
@Resource
TestTableMapper testTableMapper;
public void save(List<Object> dataList) {
testTableMapper.update(dataList);
testTableMapper.batchInsert(dataList);
}
public void delete(List<Object> dataList) {
testTableMapper.delete(dataList);
}
}
运行上述代码,将在SubService 的 update,或者batchInsert处异常
二、问题排查
最开始没有注意到主线程中有对testTable这个表的删除操作。
2.1 猜想 主线程开启子线程,多个线程之间的事务导致死锁
猜测是主线程开启子线程的时候,子线程对数据库的操作开启了新的事务(子线程没有加开启事务的注解),多个子线程执行的时候,因为有更新和批量插入,造成的死锁。
验证
在子线程的save方法中打印一下子线程的事务
log.info("transactionName:{},level:{}", TransactionSynchronizationManager.getCurrentTransactionName(), TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
结果如下
... ...
INFO SubService : currentTransactionName:null,level:null
INFO SubService : currentTransactionName:null,level:null
INFO SubService : currentTransactionName:null,level:null
INFO SubService : currentTransactionName:null,level:null
INFO SubService : currentTransactionName:null,level:null
INFO SubService : currentTransactionName:null,level:null
可以看到子线程根本没有使用事务,打印了6条日志,说明有6个子线程,猜想错误
2.2 继续排查
从数据库入手排序,查看数据库的事务情况
# 锁等待的对应关系
select * from information_schema.innodb_lock_waits;
# 当前出现的锁
select * from information_schema.innodb_locks;
# 当前运行的所有事务
select * from information_schema.innodb_trx;
# 当前线程详情
show full processlist;
结果如下



可以看到有6个事务在等待21326114这个事务,查看当前运行的所有事务,这个事务正在运行中
联系到2.1中,我们发现只有6个子线程,那就是子线程都在等待另一个线程了,检查代码发现,主线程中还有一个对testTable表的删除操作
2.3 结论
主线程对表TestTable进行了删除操作,并开启了事务,子线程对TestTable进行操作的时候,因为无法获得表而一直等待,直到等待超时
三、问题解决
3.1 主线程不使用事务
把主线程的update 方法上的事务去掉,让所有数据都不在事务中运行,可以解决该问题
3.2 让主线程中对TestTable的操作不适用事务
因为直接去掉主线程update方法上的事务影响比较大,所以可以只让主线程中对TestTable的操作不使用事务
在subService.delete上使用注解
@Transactional(propagation = Propagation.NOT_SUPPORTED)
3.3 使用spring的多线程事务
spring不支持在多线程中使用事务,需要自己实现,可以参考
Spring多线程事务处理
四、注意的点
4.1 关于Spring 的事务
Spring 在开启事务并使用多线程的时候,是不会传递给子线程的,主线程会等子线程运行结束之后再提交事务,所以如果在主线程和子线程操作同一张表,就会造成死锁的问题
3436

被折叠的 条评论
为什么被折叠?



