记录mysql8 CannotAcquireLockException异常解决过程

## 环境

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端扩容成本比数据库也低。

- 为什么单独跑测试,与正式环境不报错?  这个问题需要研究下,暂时没有研究。有进展再更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容