在併發插入更新時死鎖的問題
問題:
-
在多個執行緒同時對一張表進行批量更新時,有可能會引起資料庫表死鎖。
-
同一使用者在極短時間內連續請求更新同一條記錄(併發)時,儘管不是批量,但會造成業務邏輯結果不符合預期。
針對問題1
原因:
在採用INNODB的MySQL中,更新操作預設會加行級鎖,行級鎖是基於索引的,在分析死鎖之前需要查詢一下mysql的執行計劃,看看是否用到了索引,用到了哪個索引,對於沒有用索引的操作會採用表級鎖。如果操作用到了主鍵索引會先在主鍵索引上加鎖,然後在其他索引上加鎖,否則加鎖順序相反。在併發度高的應用中,批量更新一定要帶上記錄的主鍵,優先獲取主鍵上的鎖,這樣可以減少死鎖的發生。
優化:
首先就是寫出最優化的 sql,尤其是索引相關的。
少用或者棄用 sql 級別的校驗,例如:
ON DUPLICATE KEY UPDATE; NOT EXISTS;
儘量使用程式碼級別的判斷,以避免資料庫出現死鎖的現象。
出現死鎖問題的解決方式:
對插入更新的語句塊,進行同步處理。
// 對插入語句進行同步處理,雖然效能會下降一些,但能保證事務的正確提交,不會造成死鎖 synchronized (this) { dao.addOrUpdateEntities(entities); }
或者改批量更新為迴圈單條順序更新(禁用並行)。
for (Entity e : entities) { dao.addOrUpdateEntity(e); }
針對問題2
兩種解決方式:
-
在程式碼層面增加分散式事務鎖,保證併發事務正確提交。
// 使用分散式事務鎖,避免扣減老師推薦名額時出現的併發問題 RedisCache cache = RedisManager.getCache(RedisManager.Cache.DEFAULT); String cacheKey = "submitRecommendInfo:record_id:" + reqEntity.getRecordId(); if (cache.getLock(cacheKey, System.currentTimeMillis() + 3000, 5000)) { processRecommend(reqEntity, divisionEntity); cache.releaseLock(cacheKey); }
-
在請求前端進行優化,使用者提交一次後,禁掉提交按鈕的點選事件,避免重複提交