灰气球

灰气球

MySQL 软删除与唯一性约束共存方案

2023-09-06

一、背景

今天运营反馈了一个策略新建失败的问题。失败原因是新增策略名称与存量数据的策略名称重复,且策略名称为唯一索引,insert时触发数据库唯一键约束导致插入失败。但占用该名称的策略已经被删除了,业务上处于不可见状态。
这是由软删除设计与唯一性约束引发的冲突问题,怎么解呢?

二、示例环境

表名 : t_strategy

字段名 描述 类型
id 策略ID VARCHAR(32)
name 策略名称 VARCHAR(64)
is_deleted 删除状态(0:生效中;1:已删除;) TINYINT(4)

业务唯一键 : name
数据库唯一键 : name

三、方案选型

3.1 物理删除

物理删除没有这个烦恼,但得从业务和所在环境的角度上考虑是否可行。

所在的业务和团队是否允许物理删除?
对于存量重复记录如何订正?

跑题了~

3.2 is_deleted 存储删除时间

is_deleted 为 0 时,代表记录生效中;is_deleted 非 0 时,代表记录已删除,且字内容为删除时间。
数据库唯一键调整为 : name + is_deleted

示例环境的 is_deleted 类型为 TINYINT ,该方案涉及表结构变更。
如上,所在的业务和环境是否允许此操作?

3.3 is_deleted 存储业务唯一键下的自增值

is_deleted 为 0 时,代表记录生效中;is_deleted 非 0 时,代表记录已删除,且字内容为“自增值”。
数据库唯一键调整为 : name + is_deleted

TINYINT 的值范围有限,能否满足业务诉求?
“自增”逻辑需要自行实现,且依赖一次查询。

3.4 补充业务唯一键

3.4.1 新增 deleted_time 字段

deleted_time 即删除时间。
deleted_time 默认值为 NULL 或者 0 ;记录被删除时,将 deleted_time 赋值为 now()。
数据库唯一键调整为 : name + deleted_time

逻辑自洽,通俗易懂
实现上比较简单,在软删时将 deleted_time 置为 now() 即可。

3.4.2 新增 deleted_primary_key 字段

deleted_primary_key 即删除的主键。
deleted_primary_key 默认值为 NULL 或者 0 ;记录被删除时,将 deleted_primary_key 赋值为行主键。
数据库唯一键调整为 : name + deleted_primary_key

实现上也比较简单,在软删时将 deleted_primary_key 置为 id 即可。

3.5 删除数据分表存储

删除数据由删除表统一保存,原表不再保存删除数据,仅原表保持uk校验。
is_deleted 默认值为 0 ;记录被删除时,将该数据插入到删除表。

如果所在的业务和环境允许物理删除,那么压根不存在软删问题;如果不允许,记录删除时,将 is_deleted 赋值为 1。
当业务主键冲突时有两种情况,业务失败或者数据覆盖。
技术实现相比上述几个方案更为复杂,不建议使用。

常规方案推荐

3.2 is_deleted 存储删除时间 > 3.4.1 新增 deleted_time 字段 > 3.4.2 新增 deleted_primary_key 字段

is_deleted 不需要或者所在业务和环境允许表结构变更,优先选取方案 : 3.2 is_deleted 存储删除时间,否则往下走。
如果 deleted_time 能满足未来并发诉求,则用方案 3.4.1 新增 deleted_time 字段;否者采用方案 4.2 新增 deleted_primary_key 字段
从业务语义上看,删除时间本身是带业务含义的,只是当前业务没有用到。而被删除的主键,仅仅只是为了解决技术上的问题,没有带来增量的业务价值。