Еще один способ предотвратить повторную вставку бизнес-данных

MySQL

Прямо в точку

Недавно было много дождей, и случайно я придумал другое решение проблемы предотвращения повторной вставки (Вариант 3).

сцена

В проекте электронной коммерции продукт может быть привязан к нескольким этикеткам, а этикетка может быть привязана к нескольким продуктам, поэтому должна быть промежуточная таблица для связывания продукта и этикетки, предполагая, что таблица ассоциацииgoods_labelСтруктура выглядит следующим образом:

поле тип Примечания
id bigint первичный ключ
goods_id bigint идантификационный номер продукта
label_id bigint идентификатор тега
is_delete tinyint Идентификатор надгробия, 1 удаляется, 2 не удаляется

A и B одновременно выполняют операции связывания этикеток на продуктах:

A ->  绑定商品1和标签1
B ->  绑定商品1和标签1

Тогда наше ожидание состоит в том, что только одна из операций А и В увенчается успехом, а другой напомнит ему, что операция не удалась.

Далее будет несколько вариантов достижения желаемых ожиданий~

Вариант 1: распределенная блокировка

Поддельный код:

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 "操作超时"
}

Это очень часто и очень часто... без следа души

Вариант 2: Совместный уникальный индекс

будет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, goods_id, label_id, is_delete) values ($id, $goods_id, $label_id, 2)
if row > 0{
    return "成功"
}
return "失败"

Код намного проще, но структура индекса таблицы будет сложной.tpsотклонить,is_deleteПоле также ограничено только 1 и 2 результатами в одной и той же записи.

Вариант 3: Предварительно удалить (самоназвание)

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:

  • тег 1:Сначала вставьте данные, сначала установите состояние на удаление.
  • тег 2:Затем обновите состояние ранее вставленных данных до восстановленных, ноПри условии, что текущая ассоциация не существует.

Можно обнаружить, что в случае сбоя выполнения тега 2 вся транзакция будет отброшена, а операция тега 1 также будет отменена, поэтому грязные данные генерироваться не будут.

Сравнение схем

  • Вариант первый:Традиционный, но функциональный, без души.
  • Вариант 2:Ограничение слишком велико. Если уникальных условий слишком много, индекс будет сложнее. Кроме того, в некоторых сценариях is_delete один и тот же ресурс может удаляться несколько раз, поэтому эффект уникального индекса не может быть достигнут. Преимущество в том, что бизнес-код очень лаконичен!
  • третье решение:Я иногда капризничаю, не знаю хорошо это или плохо, но для достижения цели xD