ThinkPHP5.1软删除字段默认值必须为NULL,否则查询失效;TP5.1.9+和TP6支持$defaultSoftDelete配置,但需确保字段类型与配置一致,且软删除仅在模型实例调用时生效。

ThinkPHP5.1软删除字段必须为NULL,否则查询失效
TP5.1默认只认 delete_time IS NULL 作为“未删除”状态,字段类型可以是 DATETIME 或 INT,但**默认值必须设为 NULL**。如果建表时写成 DEFAULT '0000-00-00 00:00:00' 或 DEFAULT 0,会导致普通查询(如 User::select())把已删和未删数据一起查出来——因为框架生成的 WHERE 条件是 delete_time IS NULL,而 0 或非法时间字符串不满足该条件。
常见错误现象:
- 模型用了
SoftDelete,delete()能写入时间,但列表里仍显示已删用户 -
fetchSql(true)打印出的 SQL 没有WHERE delete_time IS NULL - 手动执行
SELECT * FROM user WHERE delete_time IS NULL能过滤,但 TP 查询不能
解决办法只有两个:
- 建表时确保字段定义为
delete_time DATETIME NULL DEFAULT NULL(推荐) - 或升级到 TP5.1.9+,并在模型中加
protected $defaultSoftDelete = 0;,同时把字段类型改为INT并设DEFAULT 0
TP6软删除支持 $defaultSoftDelete 且更灵活
TP6 原生支持 $defaultSoftDelete 属性,无需版本判断。你可以自由选择字段类型和默认值,只要模型配置与数据库一致即可:
立即学习“PHP免费学习笔记(深入)”;
- 字段是
DATETIME→protected $defaultSoftDelete = null;(保持默认) - 字段是
INT(时间戳)→protected $defaultSoftDelete = 0; - 字段是
TINYINT(软删标记,如 0/1)→protected $defaultSoftDelete = 0;,并配合$type = ['delete_flag' => 'integer']
关键点在于:TP6 的 withoutTrashed() 作用域会根据 $defaultSoftDelete 的值自动选用 whereNull() 或 where('field', $value)。而 TP5.1.9+ 虽也支持该属性,但底层逻辑仍偏脆弱——比如你设了 $defaultSoftDelete = 0 却用 DATETIME 字段,框架不会报错,但生成的 SQL 是 WHERE delete_time = 0,MySQL 会静默转成 '0000-00-00 00:00:00',结果永远查不到数据。
模型调用方式决定软删除是否生效
无论 TP5.1 还是 TP6,软删除只在**模型方法调用链中生效**。以下操作完全绕过软删除机制:
-
Db::name('user')->where('id', 1)->delete()→ 物理删除 -
User::where('id', 1)->delete()→ 如果没走模型实例(比如没先find()),也可能跳过软删除逻辑 -
(new User())->where('id', 1)->delete()→ 不触发SoftDeletetrait 中的delete()重写
必须保证是模型实例调用 delete():
User::find(1)->delete()-
User::destroy(1)(静态方法,TP 内部会实例化) -
User::whereIn('id', [1,2])->delete()(批量,同样走模型逻辑)
注意:restore() 和 onlyTrashed() 同样只对模型有效;用 Db 直查 delete_time IS NOT NULL 可以拿到数据,但无法调用 restore()。
字段类型不匹配时,TP5.1比TP6更容易静默失败
TP5.1 对字段类型的容忍度低,且错误不抛异常:
- 模型设
$deleteTime = 'delete_time',但数据库字段是INT、默认值却为NULL→ 查询时生成WHERE delete_time IS NULL,而 INT 字段存不了NULL(除非显式允许),结果全被过滤掉 - 字段是
DATETIME,模型却设$defaultSoftDelete = 0→ 生成WHERE delete_time = 0,MySQL 返回空集,无提示
TP6 在类型不一致时会更早暴露问题,比如你在 $type 里声明 'delete_time' => 'datetime',但数据库是 INT,则 whereNull('delete_time') 会变成无效条件,或触发类型转换警告(取决于严格模式)。所以实际迁移时,别只改模型配置,务必核对字段 DDL。
最易被忽略的一点:TP5.1 的 $defaultSoftDelete 是补丁式支持,它只影响查询作用域,不影响 delete() 写入逻辑——写入始终用 time() 或 date('Y-m-d H:i:s'),不会按 $defaultSoftDelete 值来填。这意味着你设了 $defaultSoftDelete = 0,但 delete() 仍往 INT 字段写时间戳,不是写 0。



















