<p>CodeIgniter 不原生支持软删除,deleted_at 字段需手动处理:CI3 完全无支持,须手写所有过滤逻辑;CI4 仅当继承 Model 且设 $useSoftDeletes = true 时,对 find* 方法自动过滤 deleted_at IS NULL,但不作用于 Query Builder 直接调用。</p>

deleted_at 字段在 CodeIgniter 中不是原生支持的软删除机制,CI3 和 CI4 都没有像 Laravel 或 ThinkPHP 那样内置 softDelete 行为。你手动加了 deleted_at 字段,但框架不会自动过滤、不会自动填充、也不会自动跳过已删记录——除非你主动干预。
deleted_at 字段必须配合手动查询条件才能生效
CI 的 $this->db->delete() 是硬删,直接执行 DELETE FROM ...。要实现软删,你得:
- 把“删除”逻辑改成
UPDATE ... SET deleted_at = NOW() - 所有读取操作(
get()、get_where()、query())都显式排除deleted_at IS NOT NULL - 否则查出来的数据里就混着已删和未删的,毫无区分
常见错误现象:
- 页面列表突然多出一堆“不该出现”的旧用户
-
where('id', 5)->get('user')->row()返回了deleted_at = '2025-11-02 14:30:00'的记录 - 以为加了字段就等于启用了软删,结果权限、统计、导出全乱套
建议做法:
- 在模型中封装两个方法:
findActive()(加WHERE deleted_at IS NULL)和findWithTrashed()(不加) - 不要用全局
where()拦截,CI3 没有模型事件钩子,CI4 虽有beforeFind,但需手动启用且不默认处理deleted_at
CI4 中可用 beforeFind 自动过滤 deleted_at
CI4 的 Model 类支持生命周期回调,可以在查询前统一加条件:
protected $allowedFields = ['name', 'email', 'deleted_at']; protected bool $useSoftDeletes = true; // 必须设为 true 才触发 beforeFind
但注意:
-
$useSoftDeletes = true只对继承CodeIgniter\Model的类有效,且仅影响该模型自身的find*方法(如find()、findAll()、where()->first()) - 它不会影响
$this->db->table()->where()->get()这类 Query Builder 直接调用 - 字段名必须是
deleted_at(不能是is_deleted或del_time),否则不识别 - 若数据库字段类型是
INT(时间戳),CI4 默认仍按DATETIME处理,可能报错或条件失效
CI3 完全没软删支持,所有逻辑得手写
CI3 的 CI_Model 是纯容器,无回调、无作用域、无自动条件注入。你只能:
- 在每个查询前手动加
$this->db->where('deleted_at IS NULL') - 封装一个基类模型,让所有业务模型继承它,并重写
get()、get_where()等方法 - 或统一用一个公共方法(如
getActive($table, $where = []))来包装查询逻辑
容易踩的坑:
-
$this->db->where('deleted_at', NULL)在 MySQL 中等价于deleted_at = NULL,不是IS NULL,结果永远查不到 - 正确写法是:
$this->db->where('deleted_at IS NULL')(字符串形式)或$this->db->where('deleted_at', null, false)(第三个参数禁用自动引号) - 如果字段允许空字符串(
'')而非NULL,那IS NULL条件完全无效,得改成IN (NULL, '')或先规范数据入库逻辑
deleted_at 看似简单,但 CI 框架本身不接管它的语义。真正难的不是加字段或改 UPDATE,而是确保所有数据出口一致过滤——控制器、API、后台导出、定时任务、关联查询,漏一处就破防。别指望配置开关一开就万事大吉。

















