事务一直running?记录一次事务异常导致的下单阻塞

场景:同事在处理下单流程的时候,突然发现请求接口没有反应,一直处于阻塞状态。

  1. 在检查具体的业务代码之后,发现并没有明显的错误。
  2. 思来想去,再次把目光投向了MySQL上面。第一步,先检查了一下是否有一直执行中的事务。
  3. 起初怀疑,嵌套事务的写法的saveapoint会不会对事务的执行造成影响,后面调整之后发现也还是一样。
  4. 悲观锁(没有)
  5. 更新语句也以主键作为更新条件,对当行库存数据修改时,锁的颗粒应该是行锁级别(个人见解,不对的话希望指导)

当下单接口请求时,更新库存的sql语句出现了lock_wait的状态。

SELECT * from information_schema.INNODB_TRX;
show processlist;

image.png

诡异的一点,在对应一直在running的事务,发现了trx_query是为空的,(经人指点之后,说这是事务没有提交也没有回滚时出现的状态)。在 kill 对应的trx_mysql_thread_id 之后,让测试并发的请求该接口,也没有出现事务卡死的情况。但是,==总会隔一段时间就出现==,真是烦人的小妖精。
但后面的我细心地发现,每次出现这个状况时,但是以整点为一个单位地出现,有时候是19:30:05 或者是15:00:03。
突然我就问了同事一句,你是不是crontab在跑什么定时脚本也在业务上面回滚库存。他突然说好像有一个的定时任务,扫描前天订单,进行库存修正。
检查了代码之后,发现这玩意,的确是直接暴力扫描昨日的未支付订单,然后逐条更新库存信息,同时批量修改订单状态。
上部分代码:

        $time = time() - 86400;
        $where[] = ['order_status', '=', '0'];
        $where[] = ['create_time', '<', $time];
        $this->where($where)->update([
            'order_status' => -1,
            'close_time' => time(),
            'update_time' => time()
        ]);

感慨这没有命中索引的批量修改.

在自己模拟RR级别事务之后,确切认识到应该是这个定时任务造成的行写锁(且事务长时间没有commit,行X锁未释放),导致了下单时无法进行数据写入,事务阻塞导致接口异常。

上一下自己模拟的代码:
大致流程为:

  1. 定时脚本开启事务,修改对应行,不进行事务提交
  2. 此时执行下单流程时,session1事务未释放对应行锁,session2的update语句则会一直处理lock_wait状态,等待session1的释放。这就解释了上面出现的一直lock_wait的状况。
Session 1:
#T1时刻;
START TRANSACTION;
select id,store_count from yipinfenxiao.goods_attrvalue where id = 72;
update yipinfenxiao.goods_attrvalue SET store_count = 79 WHERE id = 72;
select id,store_count from yipinfenxiao.goods_attrvalue where id = 72;
#T3时刻;
#下面的是模拟其他修改
update yipinfenxiao.goods_attrvalue SET store_count = 79 WHERE id = 72;
update yipinfenxiao.goods_attrvalue SET store_count = 79 WHERE id = 73;
update yipinfenxiao.goods_attrvalue SET store_count = 79 WHERE id = 74;
COMMIT;

Session 2:
#T2时刻;
START TRANSACTION;
select id,store_count from yipinfenxiao.goods_attrvalue where id = 72;
update yipinfenxiao.goods_attrvalue SET store_count = 70 WHERE id = 72;
select id,store_count from yipinfenxiao.goods_attrvalue where id = 72;
COMMIT;

最后解决,就是把未支付订单的自动取消放置队列进行处理,避免大事务的产生。但是,还是至于定时任务为何一直处于running状态,暂时也还没发现,也是只是怀疑没有命中索引的sql update语句。长路漫漫,接着搬砖,有空了再进行排查。

本作品采用《CC 协议》,转载必须注明作者和本文链接
大人中
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2

在你提供的定时脚本部分代码中,没有发现有使用事务的语句,我认为,即使该更新语句的where条件未命中索引,执行时间也应该是短暂的,除非你的数据记录大到一定规模,比如? 另外,无论什么时候,请千万不要在事务中进行耗时的操作,例如:在事务中进行调用第三方API等操作,这可能导致你的事务久久无法结束。

3年前 评论
cheer (楼主) 3年前
忆往昔弹指间 (作者) 3年前
cheer (楼主) 3年前
忆往昔弹指间 (作者) 3年前

取消订单返还库存,为什么不使用主键做条件,单条执行呢?

3年前 评论
cheer (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!