在开发过程中有时候会使用逻辑删除,与之相对的则是物理删除。逻辑删除的作用有几个

  • 防止数据误删除,方便数据找回。
  • 数据存在一定的商业价值,可以积累起来。
  • 数据与其他数据存在关联,不能删除。

当然也有它的缺点,比如数据冗余、影响查询效率、查询的 SQL 书写复杂度增加、存在唯一索引时,容易发生插入错误。

下面就来介绍如何在使用逻辑删除的同时,建立唯一索引(或者不建立索引但是能保持数据的唯一性)。

解决方案:

  1. 不使用逻辑删除,直接物理删除。
  2. 使用备份表,或者叫历史表,将原来的数据进行物理删除,同时保存一份到历史表中。
  3. 引入 redis ,使用 redis set ,通过 set 的特性来判重。
  4. 删除状态字段 is_deleted 采用 bigint 类型,删除时不再是将字段置为 1,而是将当前时间戳写入。重新建立唯一索引时,将 is_deleted 字段组合进去,比如 uk(S, is_deleted) S是原来建立唯一索引的字段集合。
  5. 保留删除状态字段,引入另一个字段 del_tid ,默认为 0,删除时将该行记录的主键填入(所以字段类型要和主键一致)。重新建立唯一索引时,将 del_tid 字段组合进去,比如 uk(S, del_tid ) S是原来建立唯一索引的字段集合。

方案比较

第一种方式显然不符合这次讨论的情况,因为我们需要使用逻辑删除。

第二种方案就需要再多维护一个历史表,如存在关联查询,则需要多关联一个历史表,会降低查询效率。

第三种方案因为引入了 redis ,会增加系统的复杂度,而且使用 redis 做缓存还需要考虑,高并发下 mysql 和 缓存的一致性,但是具备一定的可行性。

第四种和第五种方案大同小异,都是将标志字段更新为能保证唯一性的值。两者的选用应该根据实际情况使用:

如果是在项目的开发阶段,那么就是用第四种,这样除了 is_deleted 字段,不用再加一个 del_tid

如果是项目已经在线上工作了,那么可以采用第五种,这样不用大改原来的业务逻辑,也可以避免发生唯一索引报错。

第四种方式示例:

在更新时,将 is_deleted 字段置为时间戳,这里使用 now() 函数。

ALTER TABLE `dict` ADD UNIQUE UK_DICT_NAME (`name`, `is_deleted`);
1
<update id="deleteLogicallyBatch" parameterType="list" >
    update `dict` set is_deleted = now() where tid in
    <foreach collection="tids" item="tid" open="(" close=")" separator=",">
        #{tid}
    </foreach>
</update>
1
2
3
4
5
6

请根据自己的实际情况,去更新 is_deleted 的值,我这里的数据都是人工添加删除的,所以我的时间戳精确到秒就没了。

上次更新: 2023/10/15