目錄
- 為啥要解決數據重復插入?
- 解決方案實戰
- 可落地小總結
一、為啥要解決數據重復插入?
問題起源,微信小程序抽風 wx.request() 重復請求服務器提交數據。后端服務也很簡單,偽代碼如下:
class SignLogService { public void saveSignLog(SignLogDO log) { // 簡單插入做記錄 SignLogDAO.insert(log); } }
發現數據庫會存在重復數據行,提交時間一模一樣。但業務需求是不能有多余的 log 出現,這明顯是個問題。
問題是,重復請求導致的數據重復插入。這問題造成的后果很明顯:
- 數據冗余,可能不單單多一條
- 有些業務需求不能有多余數據,造成服務問題
問題如圖所示:
解決方式:如何將 同請求 A,不執行插入,而是讀取前一個請求插入的數據並返回。解決后流程應該如下:
二、解決方案實戰
1.單庫單表解決方案
- 唯一索引 + 唯一字段
- 冪等
上面說的那種業務場景:sign_log 表會有 user_id、sign_id、sign_time 等。那么每次簽到,每個人每天只有一條簽到記錄。
數據庫層采取唯一索引的形式,保證數據記錄唯一性。即 UNIQUE 約束,UNIQUE 約束唯一標識數據庫表中的每條記錄。另外,user_id,sign_id,sign_time 三個組合適唯一字段。創表的偽代碼如下:
CREATE TABLE sign_log ( id int NOT NULL, user_id int NOT NULL, sign_id int, sign_time int, CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time) )
重點是 CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)
。有個小問題,數據量大的時候,每條記錄都會有對應的唯一索引,比較耗資源。那么這樣就行了嗎?
答案是不行,服務不夠健壯。第一個請求插入成功,第二個請求直接報錯,Java 服務會拋出 DuplicateKeyException
。
簡單的冪等寫法操作即可,偽代碼如下:
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 冪等處理 SignLogDO insertLog = null; try { insertLog = signLogDAO.insert(log); } catch (DuplicateKeyException e) { insertLog = selectByUniqueKeys(userId,signId,signTime); } return insertLog; } }
的確,流量不是很大,也不算很高並發。重復寫問題,這樣處理即可。那大流量、高並發場景咋搞
2.分庫分表解決方案
流量大了后,單庫單表會演變成分庫分表。那么基於單表的唯一索引形式,在碰到分表就無法保證呢,插入的地方可能是兩個分表 A1 和 A2。
解決思路:將數據的唯一性條件放到其他存儲,並進行鎖控制
還是上面的例子,每天,每次簽到,每個人只有一條簽到記錄。那么使用分布式鎖 Redis 的解決方案。大致偽代碼如下:
a.加鎖
// 加鎖 jedis.set(lockKey, requestId, "NX", "PX", expireTime);
- lockKey 最簡單的是 user_id + sign_id + sign_time
- expireTime 設置為一天
b.解鎖
// 解鎖 jedis.eval(script, lockKey,requestId);
c.冪等代碼加強
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 冪等校驗 SignLogDO existLog = selectByUniqueKeys(userId,signId,signTime); if(Objects.nonNull(existLog)) { return existLog; } // 加鎖 jedis.set SignLogDO insertLog = signLogDAO.insert(log); // 解鎖 jedis.eval return insertLog; } }
這個方案還是不是很成熟,大家參考下即可。
三、可落地小總結
解決方案實戰中,了解具體術。歸納如下:
- 冪等:保證多次同意請求后結果一致
- 並發控制:單表唯一索引、分布式多表分布式鎖
- 降級兜底方案:分布式鎖鎖失效 – 考慮樂觀鎖兜底
參考資料
- 重復插入方案: http://www.bysocket.com/archives/2266
- 《阿里巴巴 Java 開發手冊》
以下專題教程也許您會有興趣

(關注微信公眾號,領取 Java 精選干貨學習資料)