## 环境
java的spring cloud 多模块项目,mysql8, gradle5.6, flyway6.1, spring boot 5.1.9
报错的代码为主从表在一个事务中创建,并且从表有设置外键。
执行 `gradle moduleName:test`然后开启三个线程跑整个模块的测试,则报错,内容如下。
```
Request processing failed; nested exception is org.springframework.dao.CannotAcquireLockException: xxxMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction
```
如单独执行这个测试,则不会报错。并且生产环境也不报错。(生产环境连接池用的durid,测试用的com.mysql.cj.jdbc.Driver,不清楚是否有差异)
## 错误排查过程
首先读了mysql的官方文档,认真看了propagation,isolation等配置,尝试了各种配置均无果。另外确实默认配置是符合要求的。
然后翻阅文档,查询了一些mysql层面的一些数据,如下
```
SELECT * FROM performance_schema.data_locks;
SELECT * FROM information_schema.INNODB_TRX;
```
查询到了相关事务信息,也一直很郁闷,data_locks中同一个event_transaction_id为啥新启了一个thread,并且eventId也不同了。并且在innodb_trx中根据transaction_id能看到这个事务阻塞了另外一个从表插入的事务。但是这行没有额外信息,trx_query为空,无法定位问题。继续郁闷中。。。
接着继续看了mysql文档,看到可以查看thread
```
SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM performance_schema.threads;
```
data_lock_waits没有提供太多信息,对错误排查帮助不大。
threads里面帮助很大,能够看到两个信息,一个processlist_command为sleep的主表插入sql(很重要),还有一个为query的从表插入sql. 终于可以定性。主表插入从表插入在同一个事物,导致从表插入的外键检查触发,最终一直等待中,没有通过。
刚好有通过钉钉群58集团到家业务cto做的架构师分享中提到,他们的数据库不允许加外键。然后我尝试把外键去掉,果然测试通过了。直接问题得到解决。
## 结论
同一个事物,主从表共同创建,并且有外键。导致的lock超时未释放。去掉外键即可。
## 延伸
- 链接: https://pan.baidu.com/s/1_lcz4jC17eNO4kTDRKiUig 提取码: ykmf 可以查看上面描述的mysql schema数据
- 保持外键,怎么解决? 没有研究,应该需要手动控制事物,先提交主表的。一致性方面不知道解决方案是啥
- 为什么不研究保持外键的解决方案? 进一步了解,现在的开发生态下,还是不用外键为好。把控制交给app端,而不是数据库端。减少数据库的压力。app端扩容成本比数据库也低。
- 为什么单独跑测试,与正式环境不报错? 这个问题需要研究下,暂时没有研究。有进展再更新。