防止業務數據重複插入的另外一種思路

開門見山

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

一個場景

電商項目中,一個商品能夠綁定多個標籤,一個標籤能夠綁定多個商品,因此確定會存在一箇中間表對商品和標籤的關聯,假設關聯表goods_label結構以下:bash

字段 類型 註釋
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的操做只會成功其一,另外一個提醒他操做失敗。ui

接下來將會有幾種方案來達到咱們想要的預期~spa

方案一:分佈式鎖

僞代碼:code

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, goods_id, label_id, is_delete) values ($id, $goods_id, $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, goods_id, label_id, is_delete) values ($id, $goods_id, $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:get

  • tag 1: 先插入數據,先將狀態置爲已刪除。
  • tag 2: 再將以前插入的數據狀態更新爲未刪除,可是前提是當前關聯不存在

能夠發現,若是tag 2執行失敗,整個事務會回滾掉,那麼tag 1的操做也會撤銷,因此也不會產生髒數據。

方案對比

  • 方案一: 傳統,但很實用,沒有靈魂。
  • 方案二: 侷限性太大,若是惟一的條件太多會致使索引更加複雜,另外is_delete有些場景也可能容許同類資源刪除屢次,這樣的話沒法達到惟一索引的效果。優勢是業務代碼很簡潔!
  • 方案三: 本人偶爾突發奇想,不知道好壞,可是能達到目的 xD
相關文章
相關標籤/搜索