Session 是 PHP 維持狀態的核心機制。本文彙整 8 大類常見問題,提供成因分析、解決方案與生產環境最佳實踐。
修改 php.ini 兩個關鍵參數:
; 客戶端 Cookie 存活時間(秒),0 = 瀏覽器關閉即失效
session.cookie_lifetime = 2592000 ; 30 天
; 伺服端 Session 資料最大閒置時間(秒),超過將被 gc 清理
session.gc_maxlifetime = 2592000 ; 30 天
注意:session.gc_maxlifetime 需搭配 session.gc_probability/session.gc_divisor 機率回收,若多站點共用儲存路徑,最短的 gc_maxlifetime 會影響所有站點。建議各站點設定獨立 session.save_path。
| 原因 | 解決 |
|---|---|
session_start() 前有輸出(echo、空白、BOM) | 確保 session_start() 在所有輸出之前,檔案儲存為無 BOM UTF-8 |
| 儲存路徑不可寫 | 檢查 session.save_path 目錄存在且 PHP 進程有寫入權限 |
| Session ID 非法 | 檢查 Cookie/URL 傳遞的 Session ID 格式 |
PHP 可透過 URL 重寫自動附加 Session ID(需啟用 session.use_trans_sid = 1 且編譯時加入 --enable-trans-sid)。但缺點明顯:
建議:強制要求 Cookie,拒絕無 Cookie 訪問並提示用戶啟用。
session.gc_maxlifetime 是否過小(預設 1440 秒 = 24 分鐘)。session.cookie_lifetime 是否為 0(瀏覽器關閉即失效)。url_rewriter.tags 是否包含 form=、a=href 等,影響透明 SID 嵌入。實作 SessionHandlerInterface(PHP 5.4+)或使用 session_set_save_handler():
class PdoSessionHandler implements SessionHandlerInterface {
private $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function open($savePath, $sessionName) { return true; }
public function close() { return true; }
public function read($id) {
$stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id=? AND expiry>?");
$stmt->execute([$id, time()]);
return $stmt->fetchColumn() ?: '';
}
public function write($id, $data) {
$expiry = time() + ini_get('session.gc_maxlifetime');
$stmt = $this->pdo->prepare("
INSERT INTO sessions (id, data, expiry) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE data=?, expiry=?
");
return $stmt->execute([$id, $data, $expiry, $data, $expiry]);
}
public function destroy($id) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id=?");
return $stmt->execute([$id]);
}
public function gc($maxlifetime) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE expiry < ?");
return $stmt->execute([time()]);
}
}
// 註冊
$handler = new PdoSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
資料庫儲存方案最準確:
// 寫入時更新 last_active
$stmt = $pdo->prepare("UPDATE sessions SET last_active=? WHERE id=?");
$stmt->execute([time(), $sessionId]);
// 統計 5 分鐘內活躍
$stmt = $pdo->prepare("SELECT COUNT(*) FROM sessions WHERE last_active > ?");
$stmt->execute([time() - 300]);
$online = $stmt->fetchColumn();
注意:Session 檔案掃描法(glob(session_save_path().'/sess_*'))不準確,因 gc 未必即時清理過期檔案。
Session 啟動會發送 Cache-Control: no-cache 等標頭,導致瀏覽器不快取頁面,回退時重新請求伺服器。解決:
// 在 session_start() 之前呼叫
session_cache_limiter('private, must-revalidate');
// 或
session_cache_limiter('private'); // 允許私有快取
或在頁面輸出自訂快取標頭:
header('Cache-Control: private, max-age=300');
不會。每個瀏覽器擁有獨立 Session ID,伺服器端儲存獨立 Session 資料。若需「踢下線」或「單點登入」,需在資料庫記錄 user_id → session_id 映射,登入時檢查並刪除舊 Session。
session.cookie_httponly = 1、session.cookie_secure = 1(HTTPS)、session.cookie_samesite = 'Lax'。session_regenerate_id(true) 於登入、權限變更時。Session 核心在於「伺服端儲存 + 客戶端識別碼」。掌握 php.ini 關鍵參數、儲存驅動替換、安全標頭設定、多伺服器共享方案,即能駕馭各種 Session 相關挑戰。