直接用crontab调用Yii控制台命令最稳妥,因Web请求生命周期短、易超时且并发不安全;应编写分批删除、带休眠和日志的console命令,并配置绝对路径crontab定时执行。

直接用 crontab 调用 Yii 控制台命令是最稳妥、最可控的方式,别碰 sleep 或后台常驻 PHP 进程。
为什么不能在 Web 请求里做定时清理
Web 请求生命周期短、超时限制严(通常 30–60 秒),而数据清理常涉及大批量查询、事务、I/O,极易被中断或触发 max_execution_time 错误;更危险的是,多个并发请求可能同时触发同一清理逻辑,导致重复删除、数据错乱或锁表。Yii 的 console 应用专为长时间、独占式任务设计,天然规避这些问题。
怎么写一个安全的清理命令(以清理过期订单为例)
在 console/controllers 下新建 CleanupController.php:
namespace console\controllers;
<p>use yii\console\Controller;
use yii\db\Query;
use yii\helpers\Console;</p><p>class CleanupController extends Controller
{
public function actionOrders($days = 90)
{
$cutoff = time() - ($days * 86400);</p><pre class="brush:php;toolbar:false;"> // 先查再删,避免大事务卡死
$ids = (new Query())
->select('id')
->from('order')
->where(['andWhere(['status' => 'cancelled'])
->column();
if (empty($ids)) {
$this->stdout("No expired cancelled orders found.\n", Console::FG_GREEN);
return self::EXIT_CODE_NORMAL;
}
// 分批删除,每批最多 1000 条
foreach (array_chunk($ids, 1000) as $batch) {
\Yii::$app->db->createCommand()
->delete('order', ['id' => $batch])
->execute();
usleep(10000); // 小休眠,减轻 DB 压力
}
$this->stdout("Deleted " . count($ids) . " orders.\n", Console::FG_YELLOW);
}}
- 用
$days参数控制保留窗口,方便测试和灰度 - 不直接
DELETE ... WHERE大范围扫描,先SELECT id拿主键,再分批删,降低锁粒度和事务体积 - 加
usleep()防止瞬间高负载冲击数据库 - 输出带颜色提示,便于日志识别状态
crontab 怎么配才不出错
运行 crontab -e,添加这一行(路径必须绝对):
0 2 * * * /usr/bin/php /var/www/myapp/yii cleanup/orders --days=180 >> /var/log/yii-cleanup.log 2>&1
-
/usr/bin/php必须写全路径,避免 cron 环境找不到 PHP -
/var/www/myapp/yii是项目根目录下的yii文件,不是console.php或其他入口 -
--days=180是传递给命令的参数,等价于$days = 180 -
>> /var/log/yii-cleanup.log 2>&1把 stdout 和 stderr 合并追加到日志,方便排查失败原因(比如 DB 连接失败、权限不足) - 别用
@daily—— 它等价于0 0 * * *,但某些旧版 cron 解析不稳定,显式写0 2 * * *更可靠
磁盘空间没释放?先看是不是文件句柄还挂着
如果清理脚本本身也操作日志(比如用了 FileTarget),删完后 df -h 发现空间没回来,跑这句检查:
lsof +L1 | grep 'runtime/logs'
若输出类似 php 12345 user 10w REG 8,1 1073741824 123456 /path/to/runtime/logs/app.error.log (deleted),说明某个长进程(如 queue worker、debug watch)还在往已删文件写。此时:
- 重启对应进程(如
supervisorctl restart my-queue) - 或改用
logrotate+copytruncate,避免删文件,只清空内容 - 不要依赖“删了就立刻释放”,这是 Linux 文件系统行为,不是 Yii 的问题
真正容易被忽略的点是:清理命令的事务隔离级别和数据库连接池配置。如果用的是 MySQL,默认 REPEATABLE READ 可能导致大查询期间 MVCC 版本堆积;建议在命令开头加 \Yii::$app->db->beginTransaction(); 并手动控制提交节奏,而不是靠框架自动 commit。











