Lock wait timeout exceeded; try restarting transaction






排查過程:

查看數據庫 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的悲觀鎖,也未能解決這個寫入的性能問題。


更多文章請關注訂yue號:java_xuetang