PHP不支持原生定时任务,必须依赖系统级调度(如Linux的cron或Windows任务计划程序)调用PHP CLI脚本;需避免Web环境模拟、确保时区/日志/原子性,并用数据库调度表实现动态任务。

PHP 本身不支持原生定时任务,得靠系统级调度
PHP 是脚本语言,执行完就退出,sleep() 或 while(true) 在 Web 环境里根本不可靠:超时会被 Nginx/Apache 杀掉,进程可能被 PHP-FPM 回收,还容易堆积僵尸进程。真要跑定时任务,必须交给操作系统层面的 cron(Linux/macOS)或任务计划程序(Windows)来触发 PHP 脚本。
用 cron 调用 PHP CLI 脚本最稳妥
写一个纯命令行可用的 PHP 文件,确保它不依赖 Web Server 环境(比如不调用 $_GET、不输出 HTML),然后用系统 cron 定期执行它。
示例脚本 /var/www/myapp/tasks/backup.php:
// 记录日志,确认是否真的被执行
file_put_contents('/var/log/myapp/backup.log', date('Y-m-d H:i:s') . " - Backup started\n", FILE_APPEND);// 这里放你的实际逻辑,比如数据库导出、文件清理等
exec('mysqldump -u root -p123456 mydb > /backup/mydb_'.date('Ymd_His').'.sql 2>&1', $output, $return_code);
if ($return_code === 0) {
file_put_contents('/var/log/myapp/backup.log', date('Y-m-d H:i:s') . " - Backup succeeded\n", FILE_APPEND);
} else {
file_put_contents('/var/log/myapp/backup.log', date('Y-m-d H:i:s') . " - Backup failed: " . implode("\n", $output) . "\n", FILE_APPEND);
}
?>
然后在终端运行 crontab -e,添加一行:
立即学习“PHP免费学习笔记(深入)”;
0 2 * * * /usr/bin/php /var/www/myapp/tasks/backup.php
表示每天凌晨 2 点执行。注意:/usr/bin/php 是 PHP CLI 的真实路径,用 which php 查;Web 环境的 PHP 配置(如 php.ini)和 CLI 的可能不同,扩展、内存限制、open_basedir 都要单独检查。
别在 Web 请求里“模拟”定时任务
有人用 AJAX 轮询、或者在某个页面里写 time() - filemtime('last_run') > 3600 来“凑合”,这本质是伪定时:依赖用户访问、无法保证准时、并发时可能重复执行、失败了也没日志可查。
常见错误现象包括:
- 任务只在管理员刷后台时才跑,上线后就停摆
- 多个用户同时访问,触发多次相同任务(比如重复发邮件)
-
set_time_limit(0)在某些托管环境(如 shared hosting)被禁用,直接报错
需要动态控制?加一层轻量调度表 + cron 扫描
如果任务时间不固定(比如用户提交的“明天上午 10 点推送通知”),不能全靠静态 cron。这时可以建一张数据库表存待执行任务,再让 cron 每分钟跑一次扫描脚本:
CREATE TABLE scheduled_tasks (
id INT PRIMARY KEY AUTO_INCREMENT,
command VARCHAR(255) NOT NULL,
run_at DATETIME NOT NULL,
status ENUM('pending', 'running', 'done', 'failed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);扫描脚本核心逻辑:
$pdo->prepare("SELECT * FROM scheduled_tasks WHERE status = 'pending' AND run_at <= NOW() LIMIT 10")->execute();
$tasks = $pdo->fetchAll();
foreach ($tasks as $task) {
// 标记为 running,防止被其他 cron 实例重复取走
$pdo->prepare("UPDATE scheduled_tasks SET status = 'running' WHERE id = ? AND status = 'pending'")->execute([$task['id']]);
// 执行 $task['command'],比如 shell_exec() 或 include()
// 更新 status 为 done/failed
}关键点:更新必须带 AND status = 'pending' 条件,否则并发时会冲突;每次只取少量(如 10 条),避免单次执行太久被 cron 中断。
这种模式不是替代 cron,而是用 cron 做“心跳”,真正调度逻辑由 PHP 控制——平衡了灵活性和可靠性。
真正难的从来不是写几行 sleep(),而是让任务在没人盯着的时候,该跑就跑、跑坏能知、重复不发生、资源不泄漏。cron + CLI + 日志 + 原子更新,四样缺一不可。










