软删除查询变慢主因是deleted_at或is_deleted字段未建索引,导致WHERE deleted_at IS NULL等条件触发全表扫描(type=ALL);必须显式创建B-Tree单列或联合索引,并验证EXPLAIN中key非NULL。

软删除后查询变慢,大概率不是框架问题,而是 DeletedAt 或 is_deleted 字段没建索引,导致每次查询都全表扫描。
为什么加了软删除,EXPLAIN 显示 type=ALL?
MyBatis-Plus 或 GORM 启用逻辑删除后,会在所有 SELECT 语句末尾自动拼上 AND is_deleted = 0(或 AND deleted_at IS NULL)。如果该字段没有索引,优化器只能走全表扫描。
- MySQL 对
IS NULL、= 0这类条件能走索引,但前提是字段本身有单列索引或作为联合索引最左前缀 -
deleted_at DATETIME类型字段,必须显式创建 B-Tree 索引,不能依赖主键或其它复合索引“顺带覆盖” - PostgreSQL 中若使用
boolean类型的is_deleted,需配合WHERE is_deleted = false创建部分索引(如CREATE INDEX idx_user_active ON users (id) WHERE is_deleted = false)才能高效
MyBatis-Plus 的 @TableLogic 字段必须加数据库索引吗?
必须。注解只控制 Java 层行为,不自动建库表索引。不加索引时,哪怕只查 1 条记录,也会扫完整张百万级用户表。
- MySQL 示例:
ALTER TABLE user ADD INDEX idx_is_deleted (is_deleted); - 注意:仅加索引还不够——若查询还带
ORDER BY created_at LIMIT 10,而is_deleted和created_at是分开索引,仍可能回表或排序失效;此时应建联合索引idx_isdel_created (is_deleted, created_at) - MyBatis-Plus 默认把
is_deleted当Integer处理,值为0(未删)/1(已删),确保 SQL 中写的是is_deleted = 0,而非is_deleted != 1(后者无法走索引)
GORM 的 DeletedAt 字段为什么加了 gorm:"index" 还没效果?
gorm:"index" 只是告诉 GORM “迁移时建索引”,但它不会触发已有表的索引创建。上线后新增配置,老表依然无索引。
- 检查真实索引是否存在:
SHOW INDEX FROM user;看输出里是否有deleted_at相关行 - 手动补建(MySQL):
CREATE INDEX idx_user_deleted_at ON user (deleted_at); - 若字段类型是
datetime且常用于范围查询(如“查最近 7 天未删用户”),建议建(deleted_at, created_at)联合索引,避免二次过滤 - GORM v2 默认对
DeletedAt使用NULL表示未删,所以查询条件是deleted_at IS NULL—— 这个表达式在 MySQL 8.0+ 支持索引,但低版本需确认是否命中(用EXPLAIN验证key列非NULL)
最容易被忽略的一点:软删除字段的索引不是“建了就完事”,它必须和你实际查询的 WHERE 组合、ORDER BY 字段一起评估。单列索引在多条件场景下常常失效,而联合索引顺序一旦写反(比如 (created_at, is_deleted)),WHERE is_deleted = 0 就完全用不上。










