專案需求:腳本啟動後 sleep(3600)(1 小時),醒來後從資料庫讀取資料處理。測試發現:
sleep(10) → 讀庫成功sleep(3600) → 讀庫失敗(回傳空/報錯)wait_timeout = 28800 秒(8 小時),但某些雲端資料庫(RDS、Cloud SQL)或代理層會設定更短的閒置超時(如 60 秒、300 秒)sleep() 期間 PHP 進程阻塞,無法發送心跳封包,連線被伺服器/代理主動斷開<?php
require 'DbSingleton.php'; // 單例 PDO 連線
$data = DbSingleton::getInstance()->query('SELECT * FROM tasks')->fetchAll();
sleep(3600); // 1 小時
// 連線可能已被斷開
$data = DbSingleton::getInstance()->query('SELECT * FROM tasks')->fetchAll(); // 失敗
?>
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| 睡前斷開、醒來重連 | 手動關閉連線,睡眠後重新建立 | 改動最小,相容單例模式 | 需修改所有長睡眠點 |
| 每次查詢前檢查連線存活 | 封裝 ping() 或 getAttribute(PDO::ATTR_CONNECTION_STATUS),失效則重連 |
透明化,業務碼無感 | 單例類需支援重連邏輯 |
| 啟用 PDO 持久連線 | new PDO(..., [PDO::ATTR_PERSISTENT => true]) |
PHP 自動管理連線池 | 易導致連線洩漏、超出 max_connections,需極謹慎 |
| 改用工作隊列 | 將「延遲任務」推入隊列,由 Worker 消費 | 架構正確,解耦、可重試、可監控 | 引入新基礎設施 |
<?php
class Db {
private static ?PDO $pdo = null;
private static array $config = [];
public static function init(array $config): void {
self::$config = $config;
}
public static function getPDO(): PDO {
if (self::$pdo === null || !self::isConnected()) {
self::connect();
}
return self::$pdo;
}
private static function connect(): void {
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
self::$pdo = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
private static function isConnected(): bool {
try {
// 輕量級 ping,不發送完整查詢
return self::$pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS) === 'connected';
} catch (Throwable) {
return false;
}
}
// 代理查詢方法
public static function query(string $sql, array $params = []): PDOStatement {
$stmt = self::getPDO()->prepare($sql);
$stmt->execute($params);
return $stmt;
}
}
// 使用:完全不用管 sleep 多久
Db::init([...]);
$data = Db::query('SELECT * FROM tasks')->fetchAll();
sleep(3600);
$data = Db::query('SELECT * FROM tasks')->fetchAll(); // 自動重連成功
?>
長時間 sleep() 佔用 PHP 進程、阻塞記憶體、無法水平擴展。正確做法:
總結:單例模式下 sleep() 跨越 MySQL 閒置超時會導致連線失效,必須加入自動重連邏輯;但更應從架構層面移除長阻塞,採用非同步任務隊列。