防止業務資料重複插入的另一種思路
阿新 • • 發佈:2019-12-31
開門見山
近日降雨頗多,偶然迸發靈感,對於防止重複插入這個問題想到了另一種解決方案(方案三)。
一個場景
電商專案中,一個商品可以繫結多個標籤,一個標籤可以繫結多個商品,所以肯定會存在一箇中間表對商品和標籤的關聯,假設關聯表goods_label
結構如下:
欄位 | 型別 | 註釋 |
---|---|---|
id | bigint | 主鍵 |
goods_id | bigint | 商品id |
label_id | bigint | 標籤id |
is_delete | tinyint | 邏輯刪除標識,1為已刪除,2為未刪除 |
A和B同時對商品做標籤繫結的操作:
A -> 繫結商品1和標籤1
B -> 繫結商品1和標籤1
複製程式碼
那麼我們的期望是A和B的操作只會成功其一,另一個提醒他操作失敗。
接下來將會有幾種方案來達到我們想要的預期~
方案一:分散式鎖
虛擬碼:
var goods_id = 1
var label_id = 1
var id = getId()
var count = select count(0) from goods_label where goods_id = $goods_id and label_id = $label_id and is_delete = 2
if count > 0{
return "已存在關聯"
}
var l = lock.try(GOODS_LABEL_LOCK_KEY + goods_id)
if l != nil{
try{
var row = insert into goods_label(id,goods_id,label_id,is_delete) values ($id,$goods_id,$label_id,2)
if row > 0{
return "成功"
}
return "失敗"
} finally{
l.release()
}
}else{
return "操作超時"
}
複製程式碼
這種很常用,也很普通...沒有一絲靈魂
方案二:聯合唯一索引
將goods_id
和label_id
以及is_delete
設為聯合唯一索引,那麼虛擬碼可以這樣寫:
var goods_id = 1
var label_id = 1
var id = getId()
var count = select count(0) from goods_label where goods_id = $goods_id and label_id = $label_id and is_delete = 2
if count > 0{
return "已存在關聯"
}
var row = insert into goods_label(id,2)
if row > 0{
return "成功"
}
return "失敗"
複製程式碼
程式碼簡潔了不少,但是表的索引結構會複雜導致tps
下降,is_delete
欄位也被限制了同一條記錄只會存在1和2兩個結果。
方案三:預刪除(自命名)
var goods_id = 1
var label_id = 1
var id = getId()
var count = select count(0) from goods_label where goods_id = $goods_id and label_id = $label_id and is_delete = 2
if count > 0{
return "已存在關聯"
}
var trans = beginTrans()
// 1
var row = insert into goods_label(id,1)
if row > 0{
// 2
row = update goods_label set is_delete = 2 where id = $id and (select t.count from (select count(0) as count from goods_label where goods_id = $goods_id and label_id = $label_id and is_delete = 2) t) = 0
if row > 0{
trans.commit()
return "成功"
}
trans.rollback()
return "失敗"
}
return "失敗"
複製程式碼
這種方案會執行兩條操作事務sql:
- tag 1: 先插入資料,先將狀態置為已刪除。
- tag 2: 再將之前插入的資料狀態更新為未刪除,但是前提是當前關聯不存在。
可以發現,如果tag 2執行失敗,整個事務會回滾掉,那麼tag 1的操作也會撤銷,所以也不會產生髒資料。
方案對比
- 方案一: 傳統,但很實用,沒有靈魂。
- 方案二: 侷限性太大,如果唯一的條件太多會導致索引更加複雜,另外is_delete有些場景也可能允許同類資源刪除多次,這樣的話無法達到唯一索引的效果。優點是業務程式碼很簡潔!
- 方案三: 本人偶爾突發奇想,不知道好壞,但是能達到目的 xD