软删除字段单独建索引无效,因其低选择性导致优化器弃用;应将其作为复合索引最右列(如status, created_at, is_deleted)或改用deleted_at is null配合单列索引,再通过explain验证执行计划。

软删除字段(如 is_deleted、deleted_at)本身不适合单独建索引,但配合查询模式设计复合索引后,能显著避免全表扫描。
为什么 is_deleted = 0 单独建索引基本无效
数据库优化器通常不会使用只含低选择性布尔字段的索引。比如 is_deleted 只有 0/1 两个值,若 95% 的数据是未删除状态,走索引反而比全表扫描更慢——因为要先查索引再回表,I/O 开销更大。
- 执行
EXPLAIN SELECT * FROM orders WHERE is_deleted = 0时,type很可能显示ALL(全表扫描) - MySQL、PostgreSQL 等主流引擎在评估索引收益时,会直接跳过这种高重复率字段
- 即使强制用
FORCE INDEX,性能也通常不如不加索引
真正有效的做法:把软删除字段作为复合索引的最右列
复合索引中,软删除字段必须放在最后,且仅当查询条件包含其左侧所有列时,才能利用到该索引的过滤能力。
- 常见高效写法:
CREATE INDEX idx_orders_status_deleted ON orders (status, created_at, is_deleted) - 这个索引能加速:
WHERE status = 'paid' AND created_at > '2025-01-01' AND is_deleted = 0 - 但无法加速:
WHERE is_deleted = 0 AND status = 'paid'(违反最左前缀原则) - 如果业务中 80% 查询都带
status和时间范围,就把它们放前面;不要为了“看起来覆盖软删除”而颠倒顺序
用 deleted_at IS NULL 替代布尔字段更利于索引利用
把软删除实现为 deleted_at DATETIME NULL,并在这个字段上建索引,效果比 is_deleted TINYINT 更可控。
-
CREATE INDEX idx_users_deleted_at ON users (deleted_at)能被WHERE deleted_at IS NULL有效使用(MySQL 8.0+、PostgreSQL 支持该谓词走索引) - 同时支持逻辑删除时间范围查询:
WHERE deleted_at BETWEEN '2025-06-01' AND '2025-07-01' - 注意:旧版本 MySQL(如 5.7)对
IS NULL走索引支持较弱,需实测EXPLAIN - 别忘了在应用层统一用
deleted_at IS NULL替代is_deleted = 0,否则索引形同虚设
生产环境必须验证的三个点
索引是否真起作用,不能只看 DDL 语句写得漂亮,得看实际执行路径。
- 对典型查询跑
EXPLAIN,确认key字段显示索引名,rows显著小于表总行数 - 检查慢查询日志里是否有大量
is_deleted = 0类型的未命中索引查询,它们往往是漏网之鱼 - 软删除字段参与 JOIN 或 ORDER BY 时(如
JOIN orders o ON o.user_id = u.id AND o.is_deleted = 0),复合索引必须包含所有 JOIN 条件列,否则仍可能触发临时表或文件排序
最常被忽略的是:软删除索引不是“建了就完事”,它高度依赖查询写法和字段类型的一致性。哪怕只有一处业务代码用了 is_deleted = 0 而不是 deleted_at IS NULL,那一类查询就永远用不上你精心设计的索引。











