1. 程式人生 > 程式設計 >防止業務資料重複插入的另一種思路

防止業務資料重複插入的另一種思路

開門見山

近日降雨頗多,偶然迸發靈感,對於防止重複插入這個問題想到了另一種解決方案(方案三)。

一個場景

電商專案中,一個商品可以繫結多個標籤,一個標籤可以繫結多個商品,所以肯定會存在一箇中間表對商品和標籤的關聯,假設關聯表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_idlabel_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