用 schedule 配合 delete() 最稳妥:laravel 调度器天然解决触发主体、执行时机和去重问题;避免 crontab 环境变量等维护隐患;推荐 daily 调度自定义命令,eloquent 时间范围 + delete() 安全高效。

用 schedule 配合 delete() 最稳妥
Laravel 自带的命令调度器是清理过期数据的事实标准,不是因为多高级,而是它天然解决「谁来触发」「什么时候触发」「怎么避免重复执行」这三个实际问题。硬写个脚本丢到系统 crontab 里,容易漏掉环境变量、APP_ENV 判断、队列连接等细节,反而更难维护。
实操建议:
- 在
app/Console/Kernel.php的schedule()方法里加一行:$schedule->command('cleanup:expired')->daily(); - 运行
php artisan make:command CleanupExpired创建命令类,在handle()里用 Eloquent 的时间范围 +delete(),比如:Order::where('created_at', 'subDays(90))->delete(); - 别用
chunkById()或循环each()去删几万条——除非你明确需要触发模型事件或软删除钩子;纯硬删直接delete()更快、更少锁表 - 如果表很大(比如日志表),先确认 MySQL 是否启用了
innodb_file_per_table,否则DELETE不释放磁盘空间
softDeletes() 和硬删选哪个
软删除不是“更安全”,只是把逻辑判断从 SQL 移到 PHP 层。如果你的业务里根本没人查历史过期记录,还留着 deleted_at 字段,只会拖慢查询、增大索引体积、增加备份大小。
实操建议:
- 新项目别默认加
softDeletes(),先想清楚:这个“过期”是不是真要可恢复?比如用户注销后保留 30 天数据供申诉,才值得软删 - 已用软删除的表,清理时别只清
deleted_at IS NOT NULL,得配合whereDate('deleted_at', ',否则可能误删刚软删还没到清理周期的数据 - 真要软删后彻底清库,用
forceDelete(),但注意它不触发deleting/deleted事件
大表删数据卡住或超时怎么办
直接 delete() 几十万行,常见现象是:HTTP 请求超时、Artisan 命令卡死、MySQL 报 Lock wait timeout exceeded。本质是事务太长、binlog 写入压力大、二级索引更新慢。
实操建议:
- 用分块删:比如
Model::where(...)->chunkById(1000, function ($items) { $items->each->delete(); });,注意chunkById比chunk更稳,避免 OFFSET 越来越慢 - 如果连
chunkById都慢,说明索引没建对——确保WHERE条件字段有单独索引,比如删created_at ,就得有 <code>created_at索引 - 生产环境别在高峰期跑清理命令;加个
if (app()->environment('production')) { return; }开关,上线前手动开 - 删完立刻执行
OPTIMIZE TABLE table_name;(仅 MyISAM 或小数据量 InnoDB),但线上大表慎用,会锁表
为什么不能只靠数据库的 EVENT 或 PARTITION
MySQL EVENT 看似省事,但它脱离 Laravel 应用上下文:拿不到 config()、不走服务容器、没法用模型观察者、异常不会进 Laravel 日志。分区表则要求你一开始就把时间字段设为分区键,后期改结构成本高,且 Laravel 迁移不原生支持分区语法。
实操建议:
- EVENT 只适合极简单场景,比如每小时 truncate 一张临时日志表;只要涉及关联查询、软删除、业务规则判断,一律回 Laravel 命令
- 分区表不是银弹:按月分区后,
WHERE created_at BETWEEN ? AND ?能命中分区,但WHERE status = 'pending' AND created_at 就可能全表扫 - 如果真要用分区,别用 Laravel 迁移建,用 raw query +
DB::unprepared(),并确保 DB 用户有ALTER权限
最常被忽略的是清理动作的幂等性——命令挂了重跑,不能删错数据。所以条件里尽量用绝对时间点(如 now()->subDays(30)),别用相对偏移(如 MAX(created_at) - INTERVAL 30 DAY),后者每次执行结果都可能不同。











