凌晨3点发生的线上日志异常Lock Wait Timeout(MySQL)

背景

我在某公司负责后端技术管理,业务上线后一段时间每天都会跟进日志。后台进入新业务系统开发就有一段时间没有跟进线上日志情况。最近新业务系统开发接近尾声,于是开始看线上的日志情况,发现存在一部分error级别日志。于是开始分析产生error日志的原因。

错误日志的关键字:

Lock wait timeout exceeded; try restarting transaction

具体的日志如下所示:

content:
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

[2021-11-02 03:00:51]
content:
### The error may exist in URL [jar:file:/app/admin-service-cloud.jar!/BOOT-INF/classes!/mapper/admin/practice/practiceMapper.xml]

[2021-11-02 03:00:51]
content:
### The error may involve defaultParameterMap

[2021-11-02 03:00:51]
content:
### The error occurred while setting parameters

[2021-11-02 03:00:51]
content:
### SQL: update user_pracctice set state = 0, progress_info = SUBSTRING_INDEX(progress_info, ',' , 1)             where course_id in (select id from course where course_type = 2)              and timestampdiff(HOUR , study_time, now()) > 1

[2021-11-02 03:00:51]
content:
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

原因分析

Lock wait timeout exceeded 顾名思义就是去拿锁,但是其他的线程还没有释放,拿就等待呗。也不能等太久了,就出现了超时异常。而这个错误是MySQL数据库抛出来的,所以就把方向锁定在MySQL层面解决。

这个问题就好比我们去厕所拉粑粑,占用了厕所间资源,然后其他人只能在厕所外边等,这些人等了120秒他憋不住了就不可预测的拉到酷儿里面了。

image

那么这个问题如何去解决他能,抱着具体问题具体分析的态度看了下凌晨3点的任务调度系统

### 进一步分析 凌晨3点的调度任务

打开xxl-job调度系统,3点会执行5个任务

image

这五个任务主要的工作就是从业务数据库中统计数据并批量写入到统计数据库中。

有没有可能说就是这些SQL导致了业务数据库对应的表产生了锁没有释放。

于是,通知对应的开发人员将SQL提取出来复现错误。

### SQL性能与锁分析

MySQL的默认隔离级别是可重复读,这个隔离级别在做数据查询和更新的时候会对数据加锁。直到事务提交才会释放。

最终经过多次调试,把当前会话的隔离级别更变为读未提交,不会产生大批量的行级锁了

BEGIN;

-- 设置当前会话读未提交
set session transaction isolation level read uncommitted;

-- 设置当前会话读已提交
set session transaction isolation level read committed;

-- 查询当前会话隔离级别
SHOW VARIABLES LIKE "%isolation%"

UPDATE user_practice_statistics pra  SET pra.continue_day = 0;

-- 设置当前会话可重复读
set session transaction isolation level repeatable read;

-- 回滚
ROLLBACK;

-- 提交
COMMIT;

分析这个过程大概花了半天时间,就是不断的尝试,最终找到了无锁的执行方式。由于我们的统计脚本是统计昨日并非实时统计,因此这种读未提交隔离级别不会影响统计结果。并且性能还得到了巨大的提升,并且在同时间的其他业务任务不会受影响。

结束

对于生产环境各种情况都会发生,具体问题具体分析,解决方案不是完美的,他能解决现有的问题,随着系统的访问量增加,架构的调整,处理方式也不在适用,只有不断的探索才行。

来源: 雨林博客(www.yl-blog.com)