文章

逻辑删除和唯一索引冲突的解决方案

背景

在“阿里巴巴 java 开发手册”中强制建议:业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引

说明:不要以为唯一索引影响了 insert 性能,这个速度损耗可以忽略,但提高查找速度是明显的;名外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然会有脏数据产生。
但是,引入逻辑删除后,唯一索引的功能会与其冲突。
说明:逻辑删除的实现,只是将删除状态更新为“删除”,并未真正删除该数据,依旧占用唯一索引资源,在插入数据时,可能会出现不期望的结果。

问题

例如,我们有一张表,包含 idbiz_codedelete_flag 这 3 个字段,其中 id 是主键,biz_code 是业务上具有唯一特性的字段。delete_flag 是逻辑删除字段。delete_flag 的值为 0 表示未删除,1 表示已删除,在字段 biz_code 上上创建唯一索引。

插入冲突

当我们先插入一条 biz_codePHIXLIN 的数据,然后删除这条数据。理论上这条数据已经不在业务的考虑之中(死去的东西应该归阎王管,不归我管),但我们再次插入一个 biz_codePHIXLIN 的数据,就会报错(死去的东西复活了,该死)。

删除冲突

出现了上述的插入冲突,那么我们又如何解决呢?将逻辑删除字段 delete_flagbiz_code 创建联合唯一索引,这样就可以解决插入冲突了?看似解决了问题,重复上述步骤,当我们删除第二次插入的 biz_code=PHIXLIN 的数据,还是会报错。

解决方案

想要解决上述问题,需要考虑如何在逻辑删除后让其唯一约束失效,Mysql 官方文档中有这样的解释:

A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. This constraint does not apply to NULL values except for the BDB storage engine. For other engines, a UNIQUE index allows multiple NULL values for columns that can contain NULL.
刚好可以利用这个特性,将逻辑删除字段 delete_flag 设计成 0 为未删除,NULL 为已删除,这样构建出的联合索引,在逻辑删除之后就失去了唯一性,插入就不会产生冲突了。

在 MyBatis-Plus 中,相关配置如下:

  1. 配置文件的方式
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: delete_flag
      logic-not-delete-value: 0
      logic-delete-value: NULL
  1. 注解的方式
/**
 * 是否有效,0- 有效,NUll- 删除
 */
@TableLogic(value = "0", delval = "NULL")
@TableField(fill = FieldFill.INSERT)
private Integer deleteFlag;
License:  CC BY 4.0