1. 现象
a.读库/获锁超时
很多select for update出现报错
Error happened when ..................
org.springframework.dao.CannotAcquireLockException:
### Error querying database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may exist in class path resource [mapper/mysql/xxxxMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select * from ......... for update
### 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
部分表未按时写入数据
b. fullGc出现,cpu出现波动
c. 重启恢复
2. 追问题
a. 数据库?DBA反馈cpu和连接数没有波动。升级到mysql8后,performan_schema未开,无lock表现场。
b. 事务未commit导致for update未放锁导致其他线程无法获锁?有可能。for update到底是表锁还是行锁?这个现象看应该是表锁,因为没有使用PK作为条件搜索,使用的是组合索引内容。
c. 确认未按时写入数据是事件第一受影响者。从其入手,发现应触发时间和实际执行时间在更早时间出现偏离。确认是程序导致的问题。
d. 之前确认有一个从年初就未建立分区表写入出错的问题,应该和这个触发有关。但是没有明确直接联系。
e. 排除其他ERROR后,着重查看:在最早错误发生时间前后,写入分区表相关的INFO级别日志。看到相关错误体很大,打出来后有20M。
f. 打印这个消息体INFO有2遍。此消息应当写入数据库。20M对象在内存有3份,引起GC。
g. 此消息由于写入分区表错误,会扔给MQ,重复消费,反复发生f问题,导致短时间内内存被吃完。
3.事件整理
a. 出现20M消息体,由于写日志+DB,一次性消费需要60M内存。
b. 再加上写分区失败后,扔队列后反复消费,导致60M*n的内存消耗,引起了GC。
c.很多事务开头都是同一张表的select for update,gc引起事务未commit,导致select for update的行锁?被长期持有,导致很多事务抛错获锁超时;并且事务为commit导致很多表的写入也被延迟。
d. kill -9强制重启时,消息未来得及扔到队列,导致循环消费终端,应用恢复。
4. 处理
a. 删除日志打印部分,打印消息体大小方便追踪
b. 跟进失败异常频繁消费问题
c. 修复partition问题
5. 追溯难点
a. 难以确认问题起点,是db引起gc,还是反过来
b. 报错问题和异常日志较多,无法定位gc起因
6.存疑
for update使用非PK时到底是行锁还是表锁 - 待试验
7.参考
锁相关
深入浅出MySQL 8.0 lock_sys锁相关优化 - 腾讯云专区 - 博客园
MySQL中for update的作用和用法_mysql <> for update-优快云博客
MySQL 技巧:数据库实现 乐观锁 (版本控制/条件过滤)| 悲观锁(for update)_mysql的乐观锁写法-优快云博客
spring transaction有关for update
本文分析了一次生产环境中遇到的SpringBoot应用因forupdate引发的锁超时问题,涉及数据库性能、事务管理、日志打印和内存消耗。通过排查和处理,作者揭示了事务未commit导致的锁持有以及分区表写入错误的影响。
16万+





