Spring boot项目 MySQL数据库死锁----父线程开启事务导致数据库死锁

一、问题出现

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 在开启事务并使用多线程的时候,是不会传递给子线程的,主线程会等子线程运行结束之后再提交事务,所以如果在主线程和子线程操作同一张表,就会造成死锁的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值