Session 是 Web 應用識別用戶狀態的核心。PHP 採用獨特的「文件存儲 + 機率性垃圾回收(GC)」模型,這與常駐進程語言截然不同。許多開發者遇到「設定 1 小時卻 24 分鐘失效」、「共享主機 Session 互踢」、「高併發下 Session 檔案鎖競爭」等疑難雜症,根因在於不理解 PHP Session 的底層運作邏輯。本文從原理、參數、陷阱、優化四個維度系統闡述。
HTTP 請求-響應結束即斷開連接,Server 無法感知 Client 是否「在線」。Session 通過 session_id 在 Client(Cookie/URL)與 Server(文件/資料庫)建立映射,**模擬** 有狀態連接。Server 需定期掃描判斷 Session 是否超時失效——這在 PHP 中由 GC 完成。
| 參數 | 預設值 | 含義 |
|---|---|---|
session.gc_maxlifetime | 1440 (24分) | Session 資料最大生存秒數,**判定過期門檻** |
session.gc_probability | 1 | GC 觸發機率分子 |
session.gc_divisor | 100 | GC 觸發機率分母,機率 = probability/divisor = 1% |
session.save_path 下所有 sess_* 檔案mtime(最後修改時間)time() - mtime > gc_maxlifetime,刪除檔案$_SESSION 寫入操作,mtime 不更新,GC 誤判為閒置gc_maxlifetime = 72001440session.save_path = /tmp站點 B 的 GC 執行時,**掃描所有 sess_* 檔案**,刪除 mtime > 1440 的檔案 → 站點 A 合法 Session 被誤刪。同時,其他站點可讀取/竄改 Session 檔案,極大安全風險。
<?php
// 在 session_start() 之前調用
session_save_path('/var/www/mysite/tmp/session');
// 確保目錄存在、權限 700、屬於當前用戶
if (!is_dir(session_save_path())) {
mkdir(session_save_path(), 0700, true);
}
session_start();
?>
或在 php.ini / .user.ini 設定:
session.save_path = "/var/www/mysite/tmp/session"
PHP 預設 Session Handler 使用 flock() 獨佔鎖同一 Session 檔案。同一用戶並發請求(如 AJAX 多請求)會**串行等待**,嚴重拖慢響應。
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| 關閉 session.auto_start,按需啟動 | 減少無用 Session 建立 | 簡單 | 無法解決並發鎖 |
session_write_close() 早釋放鎖 | 寫完 Session 立即釋放鎖,後續邏輯繼續執行 | 後續無法寫入 Session | |
| 使用 Redis/Memcached 存儲 | 實作 SessionHandlerInterface,原子操作替代檔案鎖 | 高併發、分佈式、精確過期、共享 | 需額外組件、網絡開銷 |
| 資料庫存儲 + 行鎖 | 用 SELECT ... FOR UPDATE 或樂觀鎖 | 持久化、ACID | 較慢、連接池壓力 |
目錄分級 session.save_path = "3;/tmp" | 建立 3 級子目錄分散檔案 | 緩解單目錄 inode 耗盡 | 不解決鎖競爭 |
| ReiserFS / tmpfs 掛載 | 針對大量小檔案優化的文件系統/記憶體盤 | IO 極快 | 運維複雜、重啟丟失 |
<?php
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 = VALUES(data), expiry = VALUES(expiry)
');
return $stmt->execute([$id, $data, $expiry]);
}
public function destroy($id) {
return $this->pdo->prepare('DELETE FROM sessions WHERE id = ?')->execute([$id]);
}
public function gc($maxlifetime) {
return $this->pdo->prepare('DELETE FROM sessions WHERE expiry < ?')->execute([time()]);
}
}
// 註冊
$handler = new PdoSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
?>
**優勢**:資料庫層面精確過期(expiry 欄位)、無 GC 機率問題、天然支援多機共享、可直接 SQL 查詢在線用戶。
session.cookie_lifetime:瀏覽器端 Cookie 存活時間,**0 表瀏覽器關閉即失效**gc_maxlifetime + GC 決定session.save_path,權限 700gc_maxlifetime 符合業務需求(如 86400)gc_probability/divisor 調整至 10/100 或更高(檔案存儲時)session_write_close() 釋放鎖session.cookie_secure = 1、session.cookie_httponly = 1、session.use_only_cookies = 1PHP Session 看似簡單,實則蘊含「機率性 GC、檔案鎖、共享目錄污染、Cookie/Server 雙重過期」等複雜機制。從「會用 Session」進階到「駕馭 Session」,關鍵在於:**隔離存儲、替換 Handler、精確過期、釋放鎖、安全配置**。掌握這些,Session 相關疑難雜症將不再是問題。