Prunable 是 Laravel 的标记性 trait,仅声明模型可清理,不自动删数据;需手动实现 pruningConditions()、编写命令并注册调度任务。

直接用 Prunable trait 不解决实际问题,它只是个标记接口,真正删数据还得靠你自己写命令或调度任务。
Prunable 是什么,为什么不能直接删数据
Prunable 是 Laravel 8+ 提供的一个空 trait,只定义了一个 pruningConditions() 方法签名,不带任何实现。它的作用纯粹是「声明这个模型支持被清理」,方便框架或开发者识别哪些模型需要参与定期清理流程——但它自己不会执行 delete(),也不会注册调度任务。
常见错误现象:加了 use Prunable; 后运行 php artisan schedule:run,数据纹丝不动;查文档发现没说明怎么触发,以为漏配了啥。
- 它不自动绑定到任何 Artisan 命令或调度逻辑
- 它不提供默认时间条件(比如“30天前”),必须手动实现
pruningConditions() - 它不处理软删除、事务、大表分块等现实约束
Prunable 的正确用法:配合自定义命令 + schedule
真正落地要三步走:声明可清理 → 定义条件 → 写命令调用 → 加入调度。其中第二步和第三步才是核心。
示例:给 Notification 模型加自动清理能力
use Illuminate\Database\Eloquent\Prunable;
class Notification extends Model
{
use Prunable;
public function pruningConditions()
{
return $this->where('created_at', '<=', now()->subDays(7))
->where('read_at', '<=', now()->subDays(7));
}
}
然后创建命令类:
php artisan make:command PruneNotifications
在 handle() 中调用:
Notification::prune();
最后在 app/Console/Kernel.php 中注册调度:
$schedule->command('prune:notifications')->daily();
-
prune()是 Eloquent 自带的静态方法,会自动调用模型的pruningConditions() - 它底层仍是
delete(),所以软删除模型会设deleted_at,硬删需额外处理 - 不支持分块(
chunkById),大数据量时可能锁表或超时
Prunable 遇到软删除时的坑
如果模型同时用了 SoftDeletes 和 Prunable,prune() 默认只软删,但你很可能想彻底物理清掉已软删的老数据。
这时候 prune() 不管用,得另起逻辑:
- 先查出已软删且超期的记录:
Notification::onlyTrashed()->whereNotNull('deleted_at')->where('deleted_at', 'subMonths(6)) - 再用
DB::table('notifications')->whereNotNull('deleted_at')->delete()物理删,跳过模型层 - 或者遍历后调
$item->forceDelete(),但注意它会触发事件,且不能链式调用
别指望 prune() 自动区分“该软删还是该硬删”——它没这判断逻辑。
大表清理卡住?Prunable 不帮你扛性能问题
当 Notification 表有 500 万行,prune() 一行 delete() 就可能让 MySQL 报 Lock wait timeout exceeded,或者命令执行超时退出。
这时必须绕开 Prunable 的封装,自己控制节奏:
- 用
Notification::onlyTrashed()->where(...)->chunkById(1000, fn($items) => $items->each->forceDelete()) - 确保
created_at或deleted_at字段有单独索引,否则chunkById也慢 - 生产环境避开高峰期,加日志记录每次删了多少条,方便监控和中断恢复
Prunable 只是个起点标记,不是银弹。真要稳,得亲手把条件、分块、索引、调度、错误重试这些全串起来。


















