一次关于@Transactional和FutureTask.get()引起的锁资源竞争和阻塞问题。
前提:本人纯新手,所以讲的不对的望各位指出。
1.背景
由于学了多线程的知识,所以想着对接口能否进行优化,例如对多个数据库表的操作是否可以以多线程的形式进行以加快速度?
如下图所示:主要的数据库表操作有三部分:1.对订单表进行插入并返回dingdanID 2. 根据返回的dingdanID对图片表进行插入操作 3.根据返回的dingdanID对支付流水表进行插入操作
2.细节点
- @Transactional 在主线程中,希望的是如果图片插入失败或者支付流水插入失败可以连订单插入一起回滚
- 插入订单并返回dingdanID的操作在主线程中,插入tupian和zhifuLS的操作在子线程中
- 在主线程调用 FutureTask.get()方法希望获取返回值以判断是否插入成功
3.效果
- 如果调用FutureTask.get(),不加过期时间
翻译如下:
这个异常 java.util.concurrent.ExecutionException 表示在执行 FutureTask.get() 方法时发生了异常。
异常的原因是 org.springframework.dao.CannotAcquireLockException,表示无法获取数据库锁。
在具体的异常信息中,可以看到 java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction,表示在尝试执行批量更新操作时,超过了锁等待超时时间。
这种情况通常发生在并发操作中,多个事务同时尝试获取相同的资源锁,而其中一个事务在等待锁的过程中超过了设定的超时时间。
效果就是 订单表插入成功,但是图片表和支付流水表插入失败。猜测是因为这个错误没有被当前事务所在范围捕获,所以事务仍旧正常提交了,所以订单编号仍旧被插入了
- 如果调用FutureTask.get(), 加过期时间
会报执行超时,且订单表插入也不成功,因为超时错误是在事务范围内被抛出来的,所以可以被捕获。
4.原因
- 首先就是上述操作涉及到外键锁的问题。是的,外键操作也是会加锁的。
具体可以看别人的这篇文章
https://blog.csdn.net/lan12334321234/article/details/70049370
- 我们通过 SELECT * FROM information_schema.innodb_trx;在navicate上实时查看可以看到如下情况
可以看到明显存在锁竞争的情况。
而且有一个很有意思的点是:lock_id和thread_id更靠近RUNNING_trx的线程等待一段时间后 trx_weight升级了,另一个则完全消失。
-
如果不使用FutureTask.get()那么是没有问题的。
-
所以可以想见,是FutureTask.get()阻塞主线程,导致数据库中主线程的锁无法释放,而使用get()方法的线程有期望获得锁。造成阻塞等待。