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

Posted javartisan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逻辑删除记录时-保证业务的唯一性约束相关的知识,希望对你有一定的参考价值。

目录

业务背景


业务背景

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

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'

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

以上是关于逻辑删除记录时-保证业务的唯一性约束的主要内容,如果未能解决你的问题,请参考以下文章

项目中遇到逻辑删除问题

SQL 如何在update 时保证唯一性?

MySQL 表的约束

MySQL 表的约束

MySQL的数据完整性约束

MySQL表的约束