lock wait timeout exceeded; try restarting transaction!
背景
今天群消息忽然炸了!说是全域CRM下单时,一旦选择X5pro机型创建订单行时,就一直转圈圈,等待很久后弹窗报错:
还有业务反应,订单表导入时,一两条也会报错,报错信息也是: SQL错误[1205] [40001]: Lock wait timeout exceeded; try restarting transaction`
。而且订单模块页面也很卡!
调查
报错日志给的很清楚:com,mysql.cj,jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
,而且结合后面的 INSERT INTO lnk_sale_order_item (row id, ...
很明显就是:锁等待超时——往数据库lnk_sale_order_item表插入数据时发生了锁等待超时。
那这个插入语句为什么会出现锁等待超时呢?这个insert语句事务(A)在等什么锁,又是别的什么事务(B)一直占用了这个锁呢?
mysql 什么锁,可能会导致其它事务的insert语句被阻塞?回顾下mysql锁类型不难得到答案:
记录锁:如果事务B正在执行insert或update但是一直没提交,而事务A执行的insert语句试图插入冲突的唯一键或主键,将被阻塞,直到B提交后,A才会报唯一键冲突!而如果B执行时间太长,A就会因为等待行锁超过mysql设置值(默认50s) 从而报错锁等待超时!
间隙锁:如果事务B正在更新某个范围的数据却一直没提交,而事务A要插入的数据正好处于这个范围,那么也会导致事务A等待间隙锁超时!
临键锁:如果事务B正在更新某个范围的数据却一直没提交, 则既不能在这个范围内新插入数据,也不能更改这个范围的数据,如果事务A正好是要在这个范围插入或者更改数据,则可能导致等待临键锁超时!
表锁:lnk_sale_order_item表被锁定。当发生较为严重的死锁、资源耗尽等情况时,InnoDB可能会锁表。
元数据锁(MDL):当事务B在执行DDL语句时,会对整个表加上MDL锁,如果事务B一直不提交,那么其它事务对当前表的CRUD操作都会被阻塞。就可能导致锁等待超时!
结合上面的分析,可以在案发现场时,看看数据库事务信息和锁的情况:
-- 当前在InnoDB内部执行的所有事务信息, 包含事务是否在等待锁,事务何时开始以及事务正在执行的SQL语句
select * from information_schema.INNODB_TRX;
-- 当前出现的锁
select * from information_schema.INNODB_LOCK_WAITS;
-- 锁等待的对应关系
select * from information_schema.INNODB_LOCKS;
由于此篇是后记,案发现场已经没了,这里没办法把当时上述sql查询的结果贴出来。
我们可以通过上述sql定位到占用锁的事务,找到其线程id, kill 掉。但这只能是临时解决办法,关键还是需要找到程序中的事务B, 排查其占用锁时间过长的原因。
为解线上燃眉之急,当时立刻重启系统,但业务发现,重启之后过不了多久创建订单行时还是会卡死,然后报上述错误。但有一个新的发现:如果下单的物料是x5pro,则会一直转圈,然后其它的物料下单也慢,也会影响其他人下单。重启后如果不选x5pro,那么问题就不存在。
猜想,可能是选择X5Pro后走了某个逻辑,这个逻辑里八成含有慢查询,导致事务耗时太久。通过代码逐行翻阅,发现果然X5Pro这个物料在下单时会触发一个政策匹配逻辑,其中含有2个慢查询,在查询语句中嵌套了 EXISTS,而且涉及的是数据量较大的订单头表和订单行表,sql执行时间可能达到4、5分钟!
解决
将这两个慢查询EXISTS改通过LEFT JOIN达到同样的效果,重新发布后问题得到解决。
追问?
1.在上面的分析过程中,定位到问题的转折点是“下单下X5Pro就会导致卡顿”,从而通过程序代码定位到慢查询。如何直接从mysql中查询出慢查询呢?
可以通过上面提到的sql语句进行分析!后面专文分析。
2.虽然最后通过慢查询定位到了持续占锁的事务代码,但还有一个问题没有追究到底——这个事务到底占用的是什么锁?
由于现场不在,而且复现需要一定的并发量,测试环境不好满足。这个问题可以通过
SHOW ENGINE INNODB STATUS
语句进行分析。