news 2026/2/28 2:18:17

MyBatisPlus逻辑删除坑?我们避免使用软删设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatisPlus逻辑删除坑?我们避免使用软删设计

MyBatisPlus逻辑删除坑?我们避免使用软删设计

在一次金融级用户中心系统的重构中,我们曾为“用户注销是否可恢复”争论了整整两天。团队最初一致认为:必须支持撤销删除,于是果断启用了 MyBatisPlus 的逻辑删除功能——只需加个@TableLogic注解,删除变更新,查询自动过滤,开发效率拉满。

三个月后,问题接踵而至:一位已“注销”的用户无法重新注册,报表系统统计出的活跃用户数离谱地高,订单服务还在给早已“删除”的用户生成交易记录……我们这才意识到,看似优雅的“软删”,正在悄悄腐蚀系统的可靠性与一致性

这不是 MyBatisPlus 的错,而是我们对“删除”这一操作的本质理解出现了偏差。


MyBatisPlus 的逻辑删除机制本质上是一种 SQL 拦截策略。当你调用removeById()时,它不会执行DELETE,而是改写成一条UPDATE语句,将标记字段(如deleted = 1)置位。同时,在所有查询中自动附加AND deleted = 0条件,试图让上层业务“无感”。

实现方式也很简洁:

@Data @TableName("user") public class User { private Long id; private String name; @TableLogic private Integer deleted; // 0-未删除, 1-已删除 }

配合全局配置:

mybatis-plus: global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0

再注册拦截器:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new LogicDeleteInnerInterceptor()); return interceptor; }

一切看起来天衣无缝。但正是这种“透明封装”,埋下了隐患的种子。


数据膨胀不是缓慢老化,而是性能雪崩的前兆

逻辑删除最直接的问题是:数据只增不减。

你以为只是“标记一下”,但实际上,每一条被“删除”的记录仍在表中占据空间。InnoDB 的页机制并不会因为deleted=1就释放存储,反而随着数据量增长,全表扫描、备份恢复、主从同步的延迟都会显著上升。

更致命的是索引效率。假设你在name字段上有索引,但没有把deleted包含进去,那么查询:

SELECT * FROM user WHERE name = 'Alice' AND deleted = 0;

很可能仍需回表扫描大量已删除的数据块。除非你建立复合索引(deleted, name),否则这个查询的成本会随着历史数据积累线性甚至指数级上升。

我们曾在一个日增万级用户的项目中观察到:上线半年后,单表行数突破 500 万,其中 80% 是逻辑删除数据。一次简单的分页查询响应时间从 50ms 暴涨到 1.2s,DBA 最终建议:“要么清理数据,要么换数据库。”


关联查询中的“幽灵数据”:你以为删了,其实还在

多表关联是逻辑删除的重灾区。

设想一个常见场景:订单(order)和订单项(order_item)。主表做了逻辑删除,但从表没跟上。这时执行:

SELECT o.id, oi.product_name FROM `order` o LEFT JOIN order_item oi ON o.id = oi.order_id WHERE o.user_id = 123;

即使订单已被“删除”,它的订单项依然能被查出来——这不仅是数据冗余,更是潜在的信息泄露。

更复杂的情况出现在缓存层面。比如用户服务将用户信息缓存到 Redis,当执行逻辑删除时,缓存并未失效。其他服务(如订单、积分)继续基于旧缓存做判断,导致权限控制失效。

要解决这个问题,你得引入事件驱动机制:删除时发消息通知所有依赖方刷新缓存。但这又增加了系统间的耦合度,违背了微服务的设计初衷。


唯一约束冲突:删了也占着茅坑

这是我们在实际项目中最痛的一个点。

用户表中email字段有唯一索引。用户 A 注销账号后,其邮箱仍被deleted=1的记录占用。新用户 B 想注册相同邮箱,系统提示“邮箱已被使用”——合理吗?显然不合理。

你可能会说:“加个条件(email, deleted)联合唯一不就行了?” 可这样一来,允许多个deleted=1存在,意味着你可以反复“恢复”同一个用户,也可能导致状态混乱。

MySQL 8.0 支持函数索引,可以用:

CREATE UNIQUE INDEX uk_email_active ON user(email) WHERE deleted = 0;

但如果你还在用 5.7 或更低版本,这条路走不通。而现实中,大量生产环境仍在使用老版本数据库。

最终我们不得不妥协:要么允许邮箱复用(破坏唯一性),要么让用户永远不能再注册原邮箱(破坏体验)。无论哪种选择,都是技术限制带来的业务妥协。


“删除”不该是一个通用状态

很多团队滥用逻辑删除的根源,在于把“删除”当成了万能状态开关。

实际上,“删除”在不同场景下含义完全不同:

  • 用户主动注销 → 应进入待归档流程;
  • 账号违规被封 → 实际是禁用,应设status = 'LOCKED'
  • 审核未通过 → 状态应为'REJECTED'
  • 数据迁移完成 → 才是真正意义上的归档或物理删除。

用一个deleted字段概括所有情况,等于抹杀了业务语义的差异性。久而久之,代码里到处都是if (!user.getDeleted()),却没人能说清“未删除”到底代表什么状态。

我们后来的做法是:彻底移除deleted字段,代之以明确的状态机:

ALTER TABLE user ADD COLUMN status ENUM('ACTIVE', 'LOCKED', 'PENDING_DELETION', 'ARCHIVED') DEFAULT 'ACTIVE';

每个状态对应清晰的业务行为,权限控制、展示逻辑、API 访问都基于status判断,不再依赖“是否删除”。


我们的选择:回归物理删除 + 可追溯机制

经过多次事故复盘,我们决定关闭 MyBatisPlus 的逻辑删除功能,并推行一套新的数据生命周期管理方案:

1. 正常删除 → 物理删除 + 异步归档
-- 删除前先归档 INSERT INTO user_archive SELECT *, NOW() AS archived_at FROM user WHERE id = ?; -- 再执行物理删除 DELETE FROM user WHERE id = ?;

归档表独立存储,保留 6~12 个月,支持按需恢复。

2. 删除操作纳入审批流

敏感操作(如删除核心用户)需走工单审批,触发企业微信/钉钉通知,防止误删。

3. 审计日志独立化

所有数据变更通过监听 Binlog 投递至 Kafka,写入 Elasticsearch,构建统一审计平台。支持按时间、操作人、字段变化进行检索。

4. 提供“软性撤销”能力

对于普通用户注销,设置 7 天冷静期。期间可在前端自助恢复,后台通过快照还原数据;超过期限则自动执行物理删除。

这套机制的核心思想是:真正的安全不是“删不掉”,而是“删了也能找回来”


软删真的有必要吗?

回顾整个过程,我们发现启用逻辑删除的动机往往来自两个误区:

  1. 怕删错:认为物理删除等于“永久丢失”;
  2. 图省事:觉得加个字段比设计归档流程简单。

但现实是:

  • 如果没有完善的备份与审计体系,逻辑删除照样“找不回”;
  • 如果缺乏跨系统协同,软删反而制造更多不一致;
  • 长期来看,数据膨胀带来的维护成本远高于一次规范的物理删除。

只有在极少数合规场景下,比如 GDPR 要求的“被遗忘权”,才需要延迟删除。即便如此,最佳实践也是“定时任务+物理清除”,而非长期保留标记数据。


结语

MyBatisPlus 的逻辑删除功能本身并无过错,它是工具,不是设计哲学。问题在于我们太容易被“自动化”迷惑,误以为加上一个注解就能解决复杂的业务问题。

软件工程的本质,从来不是“什么都不删”,而是“删了也能追溯,错了也能纠正”。

当我们放弃“靠不删来保安全”的思维定式,转而构建可靠的归档机制、清晰的状态模型和完整的审计链路时,才能真正实现既高效又安全的数据治理。

下次当你准备加上@TableLogic时,不妨先问一句:
我们是真的需要恢复数据,还是只是懒得设计恢复路径?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/17 7:13:27

QuickLook性能优化终极指南:3步解决低配置电脑卡顿问题

QuickLook性能优化终极指南:3步解决低配置电脑卡顿问题 【免费下载链接】QuickLook 项目地址: https://gitcode.com/gh_mirrors/qui/QuickLook 你是否在使用QuickLook预览文件时遇到过这些困扰:窗口打开缓慢、图片加载卡顿、视频播放掉帧&#x…

作者头像 李华
网站建设 2026/2/25 9:27:17

3分钟上手ezdata:用自然语言解锁数据查询新姿势

你是否曾为复杂的SQL语法而头疼?是否因为不懂技术而无法直接获取想要的数据?ezdata的AI数据查询功能正在彻底改变这一现状。通过自然语言数据分析,任何人都能像聊天一样轻松获取数据结果,让智能取数工具成为你的数据分析助手。 【…

作者头像 李华
网站建设 2026/2/25 10:47:20

你真的会用httpx吗?HTTP/2连接管理的秘密都在这3个参数里

第一章:你真的了解HTTP/2连接复用吗 HTTP/1.1 中的持久连接(Persistent Connection)虽然减少了 TCP 握手开销,但依然存在队头阻塞问题。HTTP/2 引入了多路复用(Multiplexing)机制,真正实现了在同…

作者头像 李华
网站建设 2026/2/27 5:22:23

突破传统边界:Brush 3D高斯泼溅技术深度解析与实战应用

在3D图形技术快速迭代的今天,传统渲染方法正面临着性能瓶颈和硬件依赖的双重挑战。Brush项目通过创新的高斯泼溅算法,为实时3D重建和渲染领域带来了革命性突破。这项技术不仅能够在多样化的硬件平台上实现高效渲染,还能提供前所未有的视觉质量…

作者头像 李华
网站建设 2026/2/27 1:15:51

Gumbo HTML5解析器架构深度解析:高性能源码实现原理

Gumbo HTML5解析器架构深度解析:高性能源码实现原理 【免费下载链接】gumbo-parser An HTML5 parsing library in pure C99 项目地址: https://gitcode.com/gh_mirrors/gum/gumbo-parser 作为纯C99实现的HTML5标准解析库,Gumbo以其卓越的架构设计…

作者头像 李华
网站建设 2026/2/26 6:24:29

谷歌镜像不稳定?我们提供多地节点分发支持

谷歌镜像不稳定?我们提供多地节点分发支持 在AI语音技术快速普及的今天,越来越多开发者和企业希望将高质量文本转语音(TTS)能力集成到自己的产品中。然而,一个看似简单的需求——下载模型权重文件,却常常因…

作者头像 李华