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

一、业务背景

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

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 校验都通过而重复。为了解决这个问题供选方案如下:

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

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'

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

上一篇:Mybatis-plus逻辑删除


下一篇:【mybatis-plus】条件查询