逻辑删除记录时-保证业务的唯一性约束

目录

业务背景


业务背景

通常业务系统的一些记录表都会有一些唯一性约束,例如相同用户下不允许重名;通常可以对指定列创建唯一性索引即可,例如:

CREATE TABLE `novel`
(
    `id`                      bigint(20) NOT NULL AUTO_INCREMENT,
    `novel_id`                bigint(20)                                                   DEFAULT NULL,
    `user_id`                     varchar(32)                                                  DEFAULT NULL,
    `name`                    varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `novel_name_unique_idx` (`user_id`,  `name`) COMMENT '名字唯一性索引'
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 ;

可以使用数据库唯一性约束保证不重复。但是线上系统通常对删除操作都是逻辑删除,并非物理删除,逻辑删除会有一个deleted字段标记是否删除,例如:

CREATE TABLE `novel`
(
    `id`                      bigint(20) NOT NULL AUTO_INCREMENT,
    `novel_id`                bigint(20)                                                   DEFAULT NULL,
    `deleted`                 int(11)                                                      DEFAULT '0',
    `user_id`                     varchar(32)                                                  DEFAULT NULL,
    `name`                    varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `novel_name_unique_idx` (`user_id`, `deleted`, `name`) COMMENT '名字唯一性索引'
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 ;

这种场景就会存在问题:相同名字的Novel只允许删除一次,删除两次会导致唯一性约束校验失败;如果不适用数据库索引而使用逻辑代码校验(一个账号允许多个用户同时登陆进行创建Novel),例如:

public void save(Novel novel){

    //1: 业务逻辑条件校验
    //2: 名字重复检查
    if(novel.getName not in database ){
    //3: save novel
    }else{
        return "名字冲突";
    }
}

由于并发问题会导致两个线程同时在执行if novel.getName not in database 校验都通过而重复。为了解决这个问题供选方案如下:

分布式锁(创建串行化)

创建时候按照User_id获取分布式锁,每次只有一个登陆的用户允许创建,没有获取的分布式锁的进行等待。

弊端:对于高并发业务QPS上不去,影响并发。

分布式锁(对User_id与name加锁)

对于创建Novel时候事先对该用户预留该名字,相同用户再次预留该名字时候就会失败。

弊端:首次预留名字的创建失败,导致第二次预约的登陆用户也不可用该名字,体验不好。

拓展的数据库唯一性索引(推荐方案)

CREATE TABLE `novel`
(
    `id`                      bigint(20) NOT NULL AUTO_INCREMENT,
    `novel_id`                bigint(20)                                                   DEFAULT NULL,
    `deleted`                 int(11)                                                      DEFAULT '0',
    `user_id`                     varchar(32)                                                  DEFAULT NULL,
    `name`                    varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
    `deleted_version`         bigint(20)                                                   DEFAULT '0' COMMENT '删除版本号,删除时候值为ID,新建为0',
    PRIMARY KEY (`id`),
    UNIQUE KEY `novel_name_unique_idx` (`user_id`, `deleted`, `name`, `deleted_version`) COMMENT '名字唯一性索引'
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 ;

由于之前的唯一性索引只有user_id,deleted,name 二次删除时候会冲突,因此可以再加一个字段解决此问题,也就是deleted_version字段;该字段用户首次创建时候值默认为0,当用户删除时候SQL如下:

update novel set deleted_version = id , deleted = true  where novel_id = 1 and user_id = 'zhangsan'

就可以解决二次删除的冲突问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值