set_time_limit() 用於設定腳本最大執行時間,但許多開發者發現它「不生效」。本文深入解析其運作機制、sleep()、資料庫查詢、系統呼叫等不計入執行時間的細節,並提供正確使用姿勢與替代方案。
根據 PHP 官方文檔:只計算 PHP 引擎執行腳本本身的 CPU 時間,不包含:
sleep()、usleep()、time_nanosleep() 睡眠時間mysqli_query()、PDO::query())file_get_contents()、curl_exec()、fsockopen())system()、exec()、shell_exec())例外:Windows 環境下計算的是「真實牆鐘時間」(Wall-clock time),包含上述所有等待。
<?php
set_time_limit(5); // 設定 5 秒
for ($i = 0; $i < 100; $i++) {
echo date('H:i:s') . "\n";
sleep(1); // 睡眠 1 秒,不計入執行時間!
}
// 結果:腳本跑超過 5 秒也不會中止
?>
<?php
set_time_limit(10);
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 這個查詢執行 30 秒,但 PHP 執行時間幾乎為 0
$stmt = $pdo->query('SELECT SLEEP(30)');
// 不會在 10 秒後中止!(Windows 除外)
?>
max_execution_time = 0(無限制),set_time_limit() 有效。max_execution_time = 30,受 php.ini、.htaccess、nginx fastcgi_read_timeout 多重限制。<?php
set_time_limit(300); // 5 分鐘
// 大量運算迴圈
for ($i = 0; $i < 1_000_000; $i++) {
// 複雜計算...
if ($i % 10000 === 0) {
// 定期檢查記憶體、時間,避免佔用過久
}
}
?>
不要試圖在單一 HTTP 請求跑超長任務。正確架構:
// Web 請求:快速建立任務,立即回應
// 1. 寫入任務隊列
$taskId = $redis->lpush('task_queue', json_encode(['type'='report', 'params'=[$year, $month]]));
// 2. 立即回應前端
echo json_encode(['status'='accepted', 'task_id'=$taskId]);
// ---------------------------------------
// Worker 進程(Supervisor / Systemd 管理):
// 3. 獨立 CLI 腳本消費隊列
// php worker.php
// 可無限制執行時間、獨立記憶體、可並行、可監控、可重試
?>
<?php
// 1. 忽略用戶斷線
ignore_user_abort(true);
// 2. 設定較長時間(配合 php.ini max_execution_time)
set_time_limit(300); // 5 分鐘
// 3. 輸出緩衝控制,讓瀏覽器不超時
header('Content-Type: text/plain; charset=utf-8');
header('X-Accel-Buffering: no'); // 關閉 Nginx proxy_buffering
ob_implicit_flush(true);
echo "任務啟動...\n";
ob_flush(); flush();
// 長時間操作(如資料庫批次更新)
foreach ($batches as $batch) {
$pdo->exec($batch);
echo "完成批次 {$batch['id']}\n";
ob_flush(); flush(); // 實時輸出進度
}
echo "任務完成。";
?>
實際生效的最小值:
min(
php.ini: max_execution_time,
.htaccess: php_value max_execution_time,
nginx: fastcgi_read_timeout (預設 60s),
apache: Timeout (預設 300s),
php 代碼: set_time_limit(),
瀏覽器: 無固定,通常 60-300s
)
關鍵:Nginx + PHP-FPM 架構下,fastcgi_read_timeout 往往是最嚴格的限制(預設 60s)。需同步調整 Nginx 設定。
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| set_time_limit() | 純 CPU 密集型短任務 | 簡單、無額外依賴 | 不含 I/O 等待、受 Web 伺服器限制 |
| pcntl_alarm() + signal | CLI 精確控制 | 真實時間、可捕獲訊號處理 | 需 pcntl 擴充、僅限 CLI、Unix-like |
| 背景任務隊列 + Worker | 所有長時間/異步任務 | 解耦、可擴展、可監控、不阻塞 Web | 架構複雜度高、需維護隊列/Worker |
| ReactPHP / Swoole 協程 | 高併發非同步 I/O | 單進程高併發、原生非阻塞 | 學習曲線陡、生態相容性需驗證 |
set_time_limit() 不是萬能鑰匙,它只管 PHP 引擎的 CPU 時間。理解「什麼不計入」是避免誤用的關鍵。對於長時間任務,背景隊列 + Worker 是工業級標準解法,不要強行在 HTTP 請求中硬扛。若必須在 Web 執行,請同步調整 Nginx/Apache 超時、啟用 ignore_user_abort()、使用輸出緩衝控制實時回傳進度。