codeigniter可通过deleted_at字段设计、模型层拦截、关联查询过滤、索引优化及归档清理实现可靠软删除。需统一重载find/findall/delete方法,join时on中加deleted_at is null,建部分索引,并定期归档后物理删除。

CodeIgniter 本身不内置软删除功能,但可通过字段设计、查询封装和模型增强实现可靠的逻辑删除,替代直接 DELETE。关键不是“加个字段就完事”,而是让软删除贯穿查询、更新、关联与清理全流程。
字段设计:优先用 deleted_at,慎用 is_deleted
在目标表中添加 deleted_at DATETIME NULL DEFAULT NULL 字段(MySQL)或 deleted_at TIMESTAMP WITH TIME ZONE NULL(PostgreSQL)。相比布尔型 is_deleted,它具备三项不可替代优势:
- 可审计性:能精确回溯删除时间,支持按时间段还原、归档或统计
-
状态无歧义:
NULL明确表示“从未删除”,非NULL表示“已删除且可知时间”,避免is_deleted = 0被人工误设为1后又改回0的逻辑混乱 -
索引友好:MySQL/PostgreSQL 对
WHERE deleted_at IS NULL的部分索引优化成熟,而is_deleted = 0在复杂OR条件下易被优化器跳过
模型层统一拦截:覆盖所有常规查询
CodeIgniter 4 的 Model 类支持重写 find()、findAll()、where() 等方法。应在基础模型中强制过滤:
- 重载
find()和findAll(),自动附加deleted_at IS NULL条件 - 重写
delete()方法:不执行DELETE,改为UPDATE SET deleted_at = NOW() WHERE id = ? AND deleted_at IS NULL(幂等,防重复) - 提供显式绕过方法,如
withTrashed()或onlyTrashed(),供后台回收站、审计报表等场景调用
关联查询与统计必须同步过滤
软删除影响远不止单表——JOIN、COUNT、分页总数都可能出错。例如查“用户订单数”,若订单表启用软删除但未在关联中过滤,结果会虚高:
-
LEFT JOIN 时,在
ON子句中补条件:ON orders.user_id = users.id AND orders.deleted_at IS NULL -
COUNT 统计 改为
COUNT(CASE WHEN orders.deleted_at IS NULL THEN 1 END)或COUNT(orders.id)(因软删行的id仍存在,但deleted_at非空,需确保聚合前已过滤) -
分页带统计字段(如“该用户已删订单数”)建议用子查询或窗口函数单独计算,避免主查询
WHERE干扰
索引与性能:不建对索引,软删除就是慢查询源头
当表数据超 10 万行、软删比例超 20%,缺失索引将导致全表扫描:
- 必须建部分索引:
CREATE INDEX idx_users_active ON users (id) WHERE deleted_at IS NULL;(PostgreSQL) - MySQL 可建函数索引(8.0+)或普通索引:
ALTER TABLE users ADD INDEX idx_deleted_at (deleted_at);,再配合WHERE deleted_at IS NULL使用 - 高频查询字段(如
name、email)应建复合部分索引:CREATE INDEX idx_users_name_active ON users (name) WHERE deleted_at IS NULL;
清理与归档:软删除不是永久存档
长期堆积的已删数据会导致表膨胀、备份变慢、查询延迟上升。需配套归档策略:
- 编写定时 Artisan 命令(CI4 可用 CLI 脚本),每月导出
deleted_at 的数据到 SQL 文件或 Parquet - 归档后,在低峰期执行物理删除:
DELETE FROM users WHERE deleted_at - 物理删除前务必检查外键约束行为(推荐设为
SET NULL或RESTRICT),避免级联误删











