場景:同事在處理下單流程的時候,突然發現請求介面沒有反應,一直處於阻塞狀態。
- 在檢查具體的業務程式碼之後,發現並沒有明顯的錯誤。
- 思來想去,再次把目光投向了MySQL上面。第一步,先檢查了一下是否有一直執行中的事務。
- 起初懷疑,巢狀事務的寫法的saveapoint會不會對事務的執行造成影響,後面調整之後發現也還是一樣。
- 悲觀鎖(沒有)
- 更新語句也以主鍵作為更新條件,對當行庫存資料修改時,鎖的顆粒應該是行鎖級別(個人見解,不對的話希望指導)
當下單介面請求時,更新庫存的sql語句出現了lock_wait的狀態。
SELECT * from information_schema.INNODB_TRX;
show processlist;
詭異的一點,在對應一直在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鎖未釋放),導致了下單時無法進行資料寫入,事務阻塞導致介面異常。
上一下自己模擬的程式碼:
大致流程為:
- 定時指令碼開啟事務,修改對應行,不進行事務提交
- 此時執行下單流程時,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 協議》,轉載必須註明作者和本文連結