一次MyBatis-Plus逻辑删除踩坑实录:数据消失了却找不到原因

作者 lucy · 2026-04-11

⚠️ 声明:本文相关内容仅供参考,实际效果因场景不同可能差异很大,请结合自身情况判断,谨慎参考。

一次 MyBatis-Plus 逻辑删除踩坑实录:数据”消失”了却找不到原因

做后端开发这么多年,最让人崩溃的 bug 往往不是逻辑写错了,而是系统行为和预期不符,却找不到代码哪里动了手脚。今天分享一个我在实际项目中遇到的真实案例——数据明明存在,查询结果却是空的,追查半天才发现是框架的”好意”在作怪。

🐢 问题现场

项目里有个订单模块,用的是 MyBatis-Plus + RuoYi 框架二次开发。某天运营反馈:一个订单的数据在数据库里明明存在,但在订单详情页死活查不出来。更诡异的是,关联的订单商品列表倒是能查到,就是主表订单查不到。

直觉告诉我可能是 ID 传错了或者状态字段有问题。查了一圈日志,SQL 打印出来是这样的:

SELECT * FROM order_main WHERE id = 'ORD20260318001' LIMIT 1

执行计划显示走索引了,返回 0 条记录。但我去数据库直接查:

mysql> SELECT * FROM order_main WHERE id = 'ORD20260318001';
+------+------------+--------+-------------+------+
| id   | order_no   | status | deleted_at  | ...  |
+------+------------+--------+-------------+------+
| 1    | ORD20260318001 | 3     | 2026-03-17  | ...  |
+------+------------+--------+-------------+------+

数据就在那里安安静静躺着,deleted_at 字段有值(表示被删除了)。但代码里没有写任何删除逻辑,这个字段怎么会被填上的?

🔍 根因定位

翻遍 Service 层代码,没有任何手动删除操作。逐一检查 Controller、Service、Mapper,最后目光落在实体类上:

@Data
@TableName("order_main")
public class OrderMain {

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private String orderNo;

    private Integer status;

    // 其他字段省略...
}

没看到逻辑删除注解啊?但我顺手查了一下父类基类——

@Data
public class BaseEntity {

    @TableField(fill = FieldFill.INSERT)
    private Long createBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

还是没有逻辑删除字段。再检查 Mapper 文件:

@Mapper
public interface OrderMainMapper extends BaseMapper<OrderMain> {
}

没有问题。等等——我记得 MyBatis-Plus 3.3.0 之后有全局逻辑删除配置,难道有人改过?翻配置文件:

# application.yml
mybatis-plus:
  global-config:
    db-config:
      # 逻辑删除配置
      logic-delete-field: deletedAt
      logic-delete-value: "1"
      logic-not-delete-value: "0"

找到了!项目全局配置了逻辑删除字段为 deletedAt,但实体类里根本没有声明这个字段。

⚠️ 问题的本质

这才是坑的真正原因:

  1. 全局配置了逻辑删除字段 deletedAt,MP 在运行时自动给所有 SELECT 查询加上 WHERE deletedAt = 0 条件。
  2. 实体类没有声明 deletedAt 字段,所以 MP 用了字段映射规则,把数据库的 deleted_at 映射到 deletedAt 属性。
  3. 一旦数据被软删除(deleted_at 被填上时间戳),MP 的查询拦截器就会把它排除在外。

那数据是怎么被”删除”的呢?原来月初做数据迁移时,DBA 批量更新了一批历史脏数据:

UPDATE order_main
SET deleted_at = NOW()
WHERE status = 0 AND create_time < '2026-01-01';

这条 SQL 是用来清理无效订单的,结果误把一批本该保留的订单也标记了。但运维同学当时用的是 Navicat 图形化操作,没在飞书上发通知,所以后端没人知道这批数据被软删了。

💡 解决方案

方案一:临时绕过(紧急恢复数据)

先把误删的数据恢复:

UPDATE order_main
SET deleted_at = NULL
WHERE order_no IN (
    'ORD20260318001',
    -- 其他需要恢复的订单号列表
);

方案二:禁用单表的逻辑删除(推荐)

不需要逻辑删除的表,在实体类上显式关闭:

@Data
@TableName(value = "order_main", logicDelete = false)
public class OrderMain {
    // ...
}

方案三:使用 @TableLogic 注解精细化控制

如果某些表确实需要逻辑删除,用注解明确指定,而不是依赖全局配置:

@Data
@TableName("order_main")
public class OrderMain {

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    // 显式声明逻辑删除字段
    @TableLogic(deleted = "1", undeleted = "0")
    private Integer deleted;

    // ...
}

方案四:在 MyBatis-Plus 配置中缩小影响范围

如果不使用全局逻辑删除字段配置,而是逐表声明,可以让配置更可控:

# application.yml
mybatis-plus:
  global-config:
    db-config:
      # 移除全局逻辑删除配置
      # logic-delete-field: deletedAt
      id-type: assign_id

📊 性能与安全权衡

逻辑删除本身是个好设计,好处包括:数据可恢复、不破坏历史关联关系、满足审计要求。但使用不当会引发三类问题:

  • 隐式查询过滤:开发者不知道查询被加了条件,容易产生”数据丢失”的困惑。
  • 唯一索引失效:软删数据占用唯一索引位置,可能导致唯一约束冲突。
  • 数据积累:大量软删数据堆积,影响查询性能,需要定期清理。

我的经验是:核心业务表(订单、支付、用户)慎用逻辑删除,用物理删除 + 日志表替代;配置类、字典类数据可以用逻辑删除。

🛠 工具链排查技巧

遇到类似问题,推荐以下排查路径:

# 1. 开启 SQL 日志,观察实际执行的 SQL
logging:
  level:
    com.example.mapper: DEBUG

# 2. 查看数据库中该表所有数据(含软删)
SELECT * FROM order_main WHERE id = 'ORD20260318001';
-- 或
SELECT * FROM order_main WHERE id = 'ORD20260318001' AND deleted_at IS NOT NULL;

# 3. 查看表结构确认字段名
DESC order_main;

# 4. 检查 MP 版本及全局配置
# 在 Spring Boot 启动类或测试类中打印配置
@Autowired
private GlobalConfig globalConfig;
System.out.println(globalConfig.getDbConfig());

✅ 总结

  • MyBatis-Plus 的全局逻辑删除配置会影响所有表,除非显式用 logicDelete = false 关闭。
  • 实体类没有声明逻辑删除字段时,MP 仍会按全局配置自动注入过滤条件——这个行为非常隐蔽。
  • 生产环境操作数据库(尤其是 UPDATE/DELETE)务必通知到后端团队,并留下操作记录。
  • 核心业务数据优先物理删除 + 归档日志表,逻辑删除仅用于非核心配置表。

这个坑前后花了大约 3 个小时才定位清楚,希望你遇到类似情况时能少走弯路。


⚠️ 声明:本文相关内容仅供参考,如有实际业务需求请结合自身情况谨慎评估。

发表评论

苏ICP备18039580号-2