什么是逻辑删除
逻辑删除意味着已“不需要”数据,但并未使用delete语句实际从数据库中删除,而是使用标志位将其设置为已删除。
为什么需要逻辑删除
逻辑删除数据一般有以下原因:
防止数据被错误删除,无法取回数据;
这些数据还具有业务价值,如用户注册信息
可以删除这些数据,但这些数据有相关数据,不能删除这些相关数据。
逻辑删除数据可以保证数据的安全性和完整性。 但是,逻辑删除带来的一些问题:
数据库中的数据冗馀,查询变慢
在编写sql进行数据处理时,必须排除逻辑上删除的数据。 其结果是,sql变得复杂,容易发生错误。 特别是涉及多个表的查询时。
在进行逻辑删除时,还需要考虑如何处理与其相关的数据;
此外,如果要求数据表中的字段是唯一的,并强制用户表中的登录用户名字段等约束条件,则如果该字段被设计为逻辑删除,则有新的相同用户名记录将无法插入。 但是,如果不将该字段设置为唯一性约束,则每次插入数据时都需要进行查询以查看是否存在未删除的同名记录,这是低效的,并且在高并发系统中难以保证正确性。
因此,是否需要逻辑删除数据需要根据具体的业务场景和逻辑删除的利弊进行综合考虑。
网友的几点建议
总的来说,中小型项目的逻辑删除带来的好处有限,但也存在很多问题。 平时就备份数据,可以避免物理删除的危险。 但是,在心里应该通关。 如果项目规模较大,对数据安全要求较高,则使用逻辑删除而不是物理删除是必然的。 在以后的数据库设计中,可以先尝试小范围的逻辑删除,在开发模型成熟后,可以全面使用逻辑删除而不是物理删除。
逻辑删除是如何设计的
设计方案1 :将字段删除字段添加到表中
deleted字段的值为0表示未删除数据,值为1表示已删除数据。
插入数据时,此值默认为0。 删除数据时将此值设置为1。 查询和更新数据时,只查询和更新条件为“‘deleted=0”的未删除的数据。
这个方案比较简单,但有点问题。 例如,假设表中的字段user_name设置了唯一性约束。 但是,仅进行逻辑删除是无法将数据插入到同一user_name中的。
但是,如果不将该字段设置为唯一性约束,则每次插入数据时都需要进行查询以查看是否存在未删除的同名记录,这是低效的,并且在高并发系统中难以保证正确性。
但是,在您的服务运行一段时间后,发现数据库中存在多个字段,name=a且is_delete=0。 大部分是出于以下原因(
这个问题有以下两种解决方案。
解决方案1 :向数据库中添加新的delete_token列。 如果需要删除记录,请将此字段设置为UUID,并将name、delete_token设置为唯一键。 因此,如果is_delete=0,则delete_token保留一个密钥,但正如本文的博客作者所言,UUID占用很大的空间,因此不建议使用。 评论用户针对该问题提出了优化对策。 将删除了记录的delete_token设置为该记录的id。
我个人认为,索引过大是弊端之一,这种方法还会遇到需要批量删除时必须一行一行删除每个记录的麻烦问题。 例如,此表中还有一个名为age的字段。 现在需要删除age 18的记录,共有50个。 业务需要在每个delete_token字段中插入UUID,因此需要将其拆分为50个更新操作。 这样的代价显然难以接受。
解决方案2 :将删除标记设置为默认值(例如0 ),并在唯一字段和删除标记中添加唯一的键约束。 如果需要删除记录,请将删除标志设置为NULL。
由于NULL没有与其他字段组合唯一键的效果,因此删除记录时(删除标志设置为NULL时)将取消对唯一键的约束。 另外,该方法可以很好地解决批量删除的问题(如果为NULL则结束),消耗的空间也少(1位连接索引)。
设计方案1 :表的备份
将删除的数据备份到其他备份表,然后删除。 如果有级联数据,还需要删除备份。 否则就不存在数据完整性。
使用MyBatis-Plus删除逻辑
在这里,使用MyBatis-Plus的逻辑删除功能,实现上面介绍的方案1。
MyBatis-plus(MP )是对MyBatis的增强,与MyBatis的本机功能完全兼容,而且几乎可以省略任何单表操作的添加、删除、变更方法,大大提高了开发效率。 的详细使用方法请参考官方网站
下面,就MP的逻辑删除功能进行说明。
步骤1 :配置
mybatis-plus:
glo
bal-config:db-config:
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
# logic-delete-field: flag
# 逻辑已删除值(默认为 1)
logic-delete-value: 1
# 逻辑未删除值(默认为 0)
logic-not-delete-value: 0
step2: 添加注解
@TableLogic()
@TableField(select = false)
private Integer deleted;
step3: 使用
@Test
public void apiTest(){
// UPDATE test.user SET deleted=1 WHERE user_id=? AND deleted=0
logger.info("开始逻辑删除");
int count = userDAO.deleteById(356);
// SELECT * FROM test.user WHERE user_id=? AND deleted=0
logger.info("开始查询");
User user = userDAO.selectById(357);
// UPDATE test.user SET user_name=?, telephone_no=?, id_card_no=?, identity_type=?, sex=?, birth_date=?, marital_status=?, asset_code=?, asset_branch_code=?, issuing_authority=?, job_type=?, address=?, work_unit=?, create_time=? WHERE user_id=? AND deleted=0
logger.info("开始更新");
userDAO.updateById(user);
}
MP的逻辑删除功能使用起来非常简单。但是需要我们注意以下几点:
开启逻辑删除功能后,MP在删除、查询和更新时会自动加上条件deleted=0,也就是只对没有删除的数据进行操作;
虽然MP对开启逻辑删除的表的插入操作没什么限制,但是还是建议在建表时,对deleted字段做默认限制,默认为0(未删除),插入数据时这个值可以不用设置;
对于自己在xml文件中定义的接口方法,MP是不会自动对其开启逻辑删除功能的,需要我们自己维护逻辑删除功能;
查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段;
下面是使用 QueryWrapper进行查询时的sql,我们发现前面的deleted=0条件会让后面我们自己加的deleted条件失效
SELECT * FROM test.user WHERE deleted=0 AND (user_id = ? AND deleted = ? AND user_name = ?)
追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段,原因和上面的原因是一样的。
参考