Lock wait timeout exceeded; try restarting transaction

2016/07/16 Mysql

排查过程:

查看数据库 INNODB_LOCKS,未发现有死锁的记录。 查看 SHOW PROCESSLIST; 也未见异常进程; 通过Mysql的控制台,查询慢日志,也未找到有关此表的Mysql慢日志信息。 查询Mysql默认的所等待超时时间: show variables like ‘innodb_lock_wait_timeout’; Variable_name Value innodb_lock_wait_timeout 50 那Spring事务默认的事务超时时间是-1,表示事务超时将依赖于数据库的事务超时时间,当前为50秒,也就是存在事务等待锁超过50秒还未拿到锁导致事务超时了。

业务重现:

后来我到业务控制台上进行业务重现(批量商品打标),在查看 INNODB_LOCKS: product-put-tag-tx-lock 发现后台针对同一条数据存在两个事务,推测是第一个事务当前还在执行(可能比较慢),第二个是个事务又提交上来了(操作重复),所以对同一条数据尝试加X锁,发现已经有锁了,所以第二个事务会处于锁等待,由于我们Spring事务未配置超时时间,所以当前的事务超时时间为50秒,等待50秒之后就会出现超时,此时Mysql会抛出“Lock wait timeout exceeded; try restarting transaction”异常;除此之外,我们还需要考虑接口调用超时时间给业务带来的影响。

解决方案:

考虑到批量打标可以允许部分失败的场景,所以取出事务,捕获异常并进入异常队列; 由于单个打标非常耗时,批量更会增加执行时间,所以修改同步操作为异步操作; 把预先批量处理的业务拆分到单个业务处理逻辑中,减少预处理成功后续失败的场景; 由于打标可能出现网络超时,所以添加自动重试机制,减少错误概率;

批处理优化建议:

减少批处理的最小化单元,尽量减小锁的范围; 修改同步执行时间为异步执行,并且捕获异常; 一些异常场景不影响事务整体提交,允许部分场景下的异常; 设置合理的超时时间; 设置合理的执行数量限制;

其它思考:

假设一:

我们的打标任务被一个要求拥有事务保证的接口调用的时候,怎么保证整体的事务性?

答:这个属于分布式事务的范畴,可以参考分布式事务环节。

假设二:

我们的批量操作为内部接口,需要保证内部的事务性,比如我们通过购物车进行下单,为了提高系统的性能,需要一次对多个SKU进行扣库存逻辑,如果存在一个SKU的库存不够扣时,就需要整体回滚,在高并发场景下,扣库存就成为了耗时操作,这种场景下我们怎么处理呢 ?

答:在高并发场景下,如果我们只是采用原生Mysql的事务操作来做批量库存更新的话,会导致大量请求处于锁等待场景,为了加快处理,我们的做法是将库存数据放到redis中,把数据库操作转化为redis操作,如果使用单机版的redis,虽然redis提供事务功能,但是事务并不具备完整性,因为不支持回滚操作,其次一般公司随着业务的发展,单机不能满足性能要求,会搭建Redis-Cluster集群,Redis-Cluster不支持事务操作,multi-key操作也必须在同一个节点中才能使用,虽然我们库存操作的是Redis读操作,但是写的时候还是用的Mysql的悲观锁,也未能解决这个写入的性能问题。

Search

    Post Directory